vfs_domount() records the mount point in f_mntonname, choosing how to
resolve it by vnode type: vn_path_to_global_path() for a directory, and
vn_path_to_global_path_hardlink() for a regular file, which is what a
nullfs file mount lands on.
Unlike its directory counterpart, the hardlink variant does not produce a
global path. It calls vn_fullpath_hardlink(), which resolves against the
calling thread's root directory (pwd->pwd_rdir) rather than against
rootvnode, so a chrooted or jailed caller records a path relative to its
own root while every consumer treats f_mntonname as global.
Inside a jail this makes the mount impossible to remove, two ways over:
- prison_canseemount() compares f_mntonname against the jail's pr_path. It no longer matches, so the jail cannot see its own mount and prison_enforce_statfs() blanks f_fsid and reports "[restricted]". unmount(2) with MNT_BYFSID is therefore unavailable as well.
- kern_unmount() globalizes the path it is given and compares it against f_mntonname. That never matches, so unmount(2) by path returns EINVAL. From outside the jail the recorded path does not resolve either.
The mount ends up with no path in any namespace, and only
"umount -f <fsid>" issued from the host can remove it.
A directory mount made the same way records its global path, is visible
from inside the jail, and unmounts normally. That asymmetry is the bug.
Practical impact: this leaks two mounts per container for podman and
buildah on FreeBSD, whose OCI runtime (sysutils/ocijail) nullfs-mounts
/etc/resolv.conf and /run/.containerenv as files. It also breaks
"podman build --squash", which reuses one rootfs directory across build
steps, so the leaked mount from step N sits exactly where step N+1 wants
to mount and the build fails with EBUSY.
The fix gives the shared implementation a flag selecting the root to
resolve against. vn_fullpath_hardlink() keeps its KPI and its behaviour
for the other callers - kern___realpathat(), and AT_EXECPATH in
kern_exec.c and kern_proc.c - which all want the caller's view.
Introduced in fa7217a71232 / 78d35459a258, which added
vn_path_to_global_path_hardlink() for this file-mount case and reused
vn_fullpath_hardlink() without accounting for the different root. The
affected code is identical in main, stable/15, stable/14 and
releng/15.1.