For file mounts, lookup returns the mount root vnode withhas no directory to return as ni_dvp set: the mount
to vp_crossmp, which cannot beroot is a VREG vnode with VV_ROOT set. It used to walk up the directory tree.return vp_crossmp;
execve(2) of such a file stores vp_crossmp as p_textdvpsince 29d1a3248a6d it returns the covered vnode, and a laterwhich for a file mount
kern.proc.pathname query on the process descends into itis itself a regular file. Either way ni_dvp is not a directory, trippingand
VNPASS(vp->v_type == VDIR || VN_IS_DOOMED(vp)) inexecve(2) stores it as p_textdvp. vn_fullpath_dir()hardlink() appends the
on INVARIANTS kernels. This is readily triggered by www/foreign-cdm,saved name and restarts the walk from p_textdvp assuming it is a
which execs its Widevine worker from a single-file nullfs mount: anydirectory, so a later kern.proc.pathname query either
- takes the lock-free namecache reverse walk, which does not check the
subsequent htop or procstat -b against that process panics the box. 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>