File mountd-Separate-lookup-of-the-exported-directory-and-the-m.patch of Package nfs-utils
From: Trond Myklebust <trond.myklebust@hammerspace.com>
Date: Mon, 10 Nov 2025 11:28:39 -0500
Subject: mountd: Separate lookup of the exported directory and the mount path
Git-repo: git://git.linux-nfs.org/projects/steved/nfs-utils.git
Git-commit: 42f01e6a78fed98f12437ac8b28cfb12b6bad056
References: CVE-2025-12801 bsc#1259204
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.
Reviewed-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
Signed-off-by: Steve Dickson <steved@redhat.com>
Acked-by: Anthony Iliopoulos <ailiop@suse.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 f600fb5ab95b..3e5a2f5d3f08 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 caec33ca5e3e..dfe88e4f6f8b 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 39afd4aa1ea0..f43ebef5bd03 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;