For file mounts lookup has no directory to return as ni_dvp: the mount
root is a VREG vnode with VV_ROOT set. It used to return vp_crossmp;
since 29d1a3248a6d it returns the covered vnode, which for a file mount
is itself a regular file. Either way ni_dvp is not a directory, and
execve(2) stores it as p_textdvp. vn_fullpath_hardlink() appends the
saved name and restarts the walk from p_textdvp assuming it is a
directory, so a later kern.proc.pathname query either
- takes the lock-free namecache reverse walk, which does not check the type and so "succeeds" on the covered file, returning a bogus doubled path -- a relative execve(2) of a file mount reports .../cover/cover in AT_EXECPATH on today's main; or
- falls back to vn_fullpath_dir() once the covered file has no namecache entry, tripping VNPASS(vp->v_type == VDIR || VN_IS_DOOMED(vp)) on INVARIANTS kernels.
That is PR 282596: www/foreign-cdm execs its Widevine worker from a
single-file nullfs mount, and htop or procstat -b against that process
panics the box once the entry has been evicted under namecache pressure.
Resolve file mounts in vn_fullpath_hardlink() itself via the covered
vnode, the same way a1d74b2dab78 handled realpath(2); the resulting
path is unambiguous since file mounts require a link count of 1. This
covers all callers: kern.proc.pathname, AT_EXECPATH for a relative
execve of a file mount, and vn_path_to_global_path_hardlink(). Drop
the now-redundant special case in kern___realpathat().
The pre-lock VV_ROOT test is made without the vnode lock and is only
a hint: recheck it once the lock is held, before dereferencing
v_mount, and return ENOENT when it no longer holds. That matches
vn_lock(), which returns ENOENT when the vnode is reclaimed during
the same window.
Since vn_fullpath() resolves into its own MAXPATHLEN buffer, check the
result against the caller-supplied size and return ENAMETOOLONG when
it does not fit, as the dropped special case did via the check in
kern___realpathat(); without this, __realpathat(2) would copy out past
the size given by the caller. Take the branch before the size
validation and work buffer allocation so that undersized buffers keep
returning ENAMETOOLONG rather than EINVAL and the work buffer is not
allocated just to be freed.
PR: 282596
Signed-off-by: Nick Price <nick@spun.io>