From 8a19b1f0dd1f09d440a6a94748fe403f7ef8da81 Mon Sep 17 00:00:00 2001
From: Trond Myklebust <trond.myklebust@hammerspace.com>
Date: Mon, 10 Nov 2025 11:28:39 -0500
Subject: [PATCH 2/4] mountd: Separate lookup of the exported directory and the
 mount path

When the caller asks to mount a path that does not terminate with an
exported directory, we want to split up the lookups so that we can
look up the exported directory using the mountd privileged credential,
and the remaining subdirectory lookups using the RPC caller's
credential.

CVE: CVE-2025-12801
Upstream-Status: Backport [https://git.linux-nfs.org/?p=steved/nfs-utils.git;a=commit;h=42f01e6a78fed98f12437ac8b28cfb12b6bad056]

Reviewed-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
Signed-off-by: Steve Dickson <steved@redhat.com>
(cherry picked from commit 42f01e6a78fed98f12437ac8b28cfb12b6bad056)
Signed-off-by: Deepak Rathore <deeratho@cisco.com>
---
 support/include/nfsd_path.h |  1 +
 support/misc/nfsd_path.c    | 31 ++++++++++++++++++
 utils/mountd/mountd.c       | 63 +++++++++++++++++++++++++++++++------
 3 files changed, 86 insertions(+), 9 deletions(-)

diff --git a/support/include/nfsd_path.h b/support/include/nfsd_path.h
index f600fb5a..3e5a2f5d 100644
--- a/support/include/nfsd_path.h
+++ b/support/include/nfsd_path.h
@@ -18,6 +18,7 @@ char *		nfsd_path_prepend_dir(const char *dir, const char *pathname);
 
 int 		nfsd_path_stat(const char *pathname, struct stat *statbuf);
 int 		nfsd_path_lstat(const char *pathname, struct stat *statbuf);
+int		nfsd_openat(int dirfd, const char *path, int flags);
 
 int		nfsd_path_statfs(const char *pathname,
 				   struct statfs *statbuf);
diff --git a/support/misc/nfsd_path.c b/support/misc/nfsd_path.c
index caec33ca..dfe88e4f 100644
--- a/support/misc/nfsd_path.c
+++ b/support/misc/nfsd_path.c
@@ -203,6 +203,37 @@ nfsd_realpath(const char *path, char *resolved_buf)
         return realpath_buf.res_ptr;
 }
 
+struct nfsd_openat_t {
+	const char *path;
+	int dirfd;
+	int flags;
+	int res_fd;
+	int res_error;
+};
+
+static void nfsd_openatfunc(void *data)
+{
+	struct nfsd_openat_t *d = data;
+
+	d->res_fd = openat(d->dirfd, d->path, d->flags);
+	if (d->res_fd == -1)
+		d->res_error = errno;
+}
+
+int nfsd_openat(int dirfd, const char *path, int flags)
+{
+	struct nfsd_openat_t open_buf = {
+		.path = path,
+		.dirfd = dirfd,
+		.flags = flags,
+	};
+
+	nfsd_run_task(nfsd_openatfunc, &open_buf);
+	if (open_buf.res_fd == -1)
+		errno = open_buf.res_error;
+	return open_buf.res_fd;
+}
+
 struct nfsd_rw_data {
 	int             fd;
 	void*           buf;
diff --git a/utils/mountd/mountd.c b/utils/mountd/mountd.c
index 39afd4aa..f43ebef5 100644
--- a/utils/mountd/mountd.c
+++ b/utils/mountd/mountd.c
@@ -392,7 +392,10 @@ get_rootfh(struct svc_req *rqstp, dirpath *path, nfs_export **expret,
 	struct nfs_fh_len *fh;
 	char		rpath[MAXPATHLEN+1];
 	char		*p = *path;
+	char		*subpath;
 	char		buf[INET6_ADDRSTRLEN];
+	size_t		epathlen;
+	int		dirfd;
 
 	if (*p == '\0')
 		p = "/";
@@ -412,12 +415,21 @@ get_rootfh(struct svc_req *rqstp, dirpath *path, nfs_export **expret,
 		*error = MNT3ERR_ACCES;
 		return NULL;
 	}
-	if (nfsd_path_stat(exp->m_export.e_path, &estb) < 0) {
-		xlog(L_WARNING, "can't stat export point %s: %s",
+
+	dirfd = nfsd_openat(AT_FDCWD, exp->m_export.e_path, O_PATH);
+	if (dirfd == -1) {
+		xlog(L_WARNING, "can't open export point %s: %s",
 		     p, strerror(errno));
 		*error = MNT3ERR_NOENT;
 		return NULL;
 	}
+	if (fstat(dirfd, &estb) == -1) {
+		xlog(L_WARNING, "can't stat export point %s: %s",
+		     p, strerror(errno));
+		*error = MNT3ERR_ACCES;
+		close(dirfd);
+		return NULL;
+	}
 	if (exp->m_export.e_mountpoint &&
 		   !check_is_mountpoint(exp->m_export.e_mountpoint[0]?
 				  exp->m_export.e_mountpoint:
@@ -426,18 +438,51 @@ get_rootfh(struct svc_req *rqstp, dirpath *path, nfs_export **expret,
 		xlog(L_WARNING, "request to export an unmounted filesystem: %s",
 		     p);
 		*error = MNT3ERR_NOENT;
+		close(dirfd);
 		return NULL;
 	}
 
-	if (nfsd_path_stat(p, &stb) < 0) {
-		xlog(L_WARNING, "can't stat exported dir %s: %s",
-				p, strerror(errno));
-		if (errno == ENOENT)
-			*error = MNT3ERR_NOENT;
-		else
-			*error = MNT3ERR_ACCES;
+	epathlen = strlen(exp->m_export.e_path);
+	if (epathlen > strlen(p)) {
+		xlog(L_WARNING, "raced with change of exported path: %s", p);
+		*error = MNT3ERR_NOENT;
+		close(dirfd);
 		return NULL;
 	}
+	subpath = &p[epathlen];
+	while (*subpath == '/')
+		subpath++;
+	if (*subpath != '\0') {
+		int fd;
+
+		/* Just perform a lookup of the path */
+		fd = nfsd_openat(dirfd, subpath, O_PATH);
+		close(dirfd);
+		if (fd == -1) {
+			xlog(L_WARNING, "can't open exported dir %s: %s", p,
+			     strerror(errno));
+			if (errno == ENOENT)
+				*error = MNT3ERR_NOENT;
+			else
+				*error = MNT3ERR_ACCES;
+			return NULL;
+		}
+		if (fstat(fd, &stb) == -1) {
+			xlog(L_WARNING, "can't open exported dir %s: %s", p,
+			     strerror(errno));
+			if (errno == ENOENT)
+				*error = MNT3ERR_NOENT;
+			else
+				*error = MNT3ERR_ACCES;
+			close(fd);
+			return NULL;
+		}
+		close(fd);
+	} else {
+		close(dirfd);
+		stb = estb;
+	}
+
 	if (!S_ISDIR(stb.st_mode) && !S_ISREG(stb.st_mode)) {
 		xlog(L_WARNING, "%s is not a directory or regular file", p);
 		*error = MNT3ERR_NOTDIR;
-- 
2.35.6

