git: a1d74b2dab78 - main - Allow realpath to work for file mounts

From: Doug Rabson <dfr_at_FreeBSD.org>
Date: Mon, 19 Dec 2022 16:50:34 UTC
The branch main has been updated by dfr:

URL: https://cgit.FreeBSD.org/src/commit/?id=a1d74b2dab78d56582126b4944b435d00747f601

commit a1d74b2dab78d56582126b4944b435d00747f601
Author:     Doug Rabson <dfr@FreeBSD.org>
AuthorDate: 2022-12-04 15:53:07 +0000
Commit:     Doug Rabson <dfr@FreeBSD.org>
CommitDate: 2022-12-19 16:46:27 +0000

    Allow realpath to work for file mounts
    
    For file mounts, the directory vnode is not available from namei and this
    prevents the use of vn_fullpath_hardlink. In this case, we can use the
    vnode which was covered by the file mount with vn_fullpath.
    
    This also disallows file mounts over files with link counts greater than
    one to ensure a deterministic path to the mount point.
    
    Reviewed by:    mjg, kib
    Tested by:      pho
---
 sys/kern/vfs_cache.c | 28 ++++++++++++++++++++++++++--
 sys/kern/vfs_mount.c |  5 +++++
 2 files changed, 31 insertions(+), 2 deletions(-)

diff --git a/sys/kern/vfs_cache.c b/sys/kern/vfs_cache.c
index f2dd8328278d..47065cf85bb5 100644
--- a/sys/kern/vfs_cache.c
+++ b/sys/kern/vfs_cache.c
@@ -3147,12 +3147,36 @@ kern___realpathat(struct thread *td, int fd, const char *path, char *buf,
 	    pathseg, path, fd, &cap_fstat_rights);
 	if ((error = namei(&nd)) != 0)
 		return (error);
-	error = vn_fullpath_hardlink(nd.ni_vp, nd.ni_dvp, nd.ni_cnd.cn_nameptr,
-	    nd.ni_cnd.cn_namelen, &retbuf, &freebuf, &size);
+
+	if (nd.ni_vp->v_type == VREG && nd.ni_dvp->v_type != VDIR &&
+	    (nd.ni_vp->v_vflag & VV_ROOT) != 0) {
+		/*
+		 * This happens if vp is a file mount. The call to
+		 * vn_fullpath_hardlink can panic if path resolution can't be
+		 * handled without the directory.
+		 *
+		 * To resolve this, we find the vnode which was mounted on -
+		 * this should have a unique global path since we disallow
+		 * mounting on linked files.
+		 */
+		struct vnode *covered_vp;
+		error = vn_lock(nd.ni_vp, LK_SHARED);
+		if (error != 0)
+			goto out;
+		covered_vp = nd.ni_vp->v_mount->mnt_vnodecovered;
+		vref(covered_vp);
+		VOP_UNLOCK(nd.ni_vp);
+		error = vn_fullpath(covered_vp, &retbuf, &freebuf);
+		vrele(covered_vp);
+	} else {
+		error = vn_fullpath_hardlink(nd.ni_vp, nd.ni_dvp, nd.ni_cnd.cn_nameptr,
+		    nd.ni_cnd.cn_namelen, &retbuf, &freebuf, &size);
+	}
 	if (error == 0) {
 		error = copyout(retbuf, buf, size);
 		free(freebuf, M_TEMP);
 	}
+out:
 	vrele(nd.ni_vp);
 	vrele(nd.ni_dvp);
 	NDFREE_PNBUF(&nd);
diff --git a/sys/kern/vfs_mount.c b/sys/kern/vfs_mount.c
index 8001604d2855..8de9d3c4fff8 100644
--- a/sys/kern/vfs_mount.c
+++ b/sys/kern/vfs_mount.c
@@ -1108,6 +1108,11 @@ vfs_domount_first(
 	if (vfsp->vfc_flags & VFCF_FILEMOUNT) {
 		if (error == 0 && vp->v_type != VDIR && vp->v_type != VREG)
 			error = EINVAL;
+		/*
+		 * For file mounts, ensure that there is only one hardlink to the file.
+		 */
+		if (error == 0 && vp->v_type == VREG && va.va_nlink != 1)
+			error = EINVAL;
 	} else {
 		if (error == 0 && vp->v_type != VDIR)
 			error = ENOTDIR;