nfs-utils: fix CVE-2025-12801

- This patch applies the upstream fix [5] as referenced in [7].
- To successfully apply the fixed commit, apply the dependent commits [2] to [4]
  which are included in v2.8.6, as referenced in [7].
- Additionally, include dependent commit [1] from v2.8.3, as referenced in [8]
  under the [2.5.4-38.2] description, along with compilation fix commit [6]
  from v2.7.1
- Reference:
  [1] https://git.linux-nfs.org/?p=steved/nfs-utils.git;a=commit;h=cd90f2925790
  [2] https://git.linux-nfs.org/?p=steved/nfs-utils.git;a=commit;h=7e8b36522f58
  [3] https://git.linux-nfs.org/?p=steved/nfs-utils.git;a=commit;h=42f01e6a78fe
  [4] https://git.linux-nfs.org/?p=steved/nfs-utils.git;a=commit;h=51738ae56d92
  [5] https://git.linux-nfs.org/?p=steved/nfs-utils.git;a=commit;h=f36bd900a899
  [6] https://git.linux-nfs.org/?p=steved/nfs-utils.git;a=commit;h=a2c95e4f557a
  [7] https://security-tracker.debian.org/tracker/CVE-2025-12801
  [8] https://linux.oracle.com/errata/ELSA-2026-3940.html

(From OE-Core rev: a866d0438d30b1625450f68fea19e9315a4e4b36)

Signed-off-by: Sudhir Dumbhare <sudumbha@cisco.com>
Signed-off-by: Yoann Congal <yoann.congal@smile.fr>
Signed-off-by: Paul Barker <paul@pbarker.dev>
This commit is contained in:
Sudhir Dumbhare
2026-06-23 03:25:41 -07:00
committed by Paul Barker
parent d144337355
commit 2c373fb9b9
7 changed files with 1484 additions and 0 deletions

View File

@@ -0,0 +1,44 @@
From 30e0f57fff545b0bb3071fa071c7b12c2923bac8 Mon Sep 17 00:00:00 2001
From: Steve Dickson <steved@redhat.com>
Date: Mon, 22 Jan 2024 13:23:57 -0500
Subject: [PATCH] reexport.c: Some Distros need the following include to
avoid the following error
reexport.c: In function connect_fsid_service:
reexport.c:41:28: error: implicit declaration of function offsetof [-Werror=implicit-function-declaration]
41 | addr_len = offsetof(struct sockaddr_un, sun_path) + strlen(addr.sun_path);
| ^~~~~~~~
reexport.c:19:1: note: offsetof is defined in header <stddef.h>; did you forget to #include <stddef.h>?
18 | #include "xlog.h"
+++ |+#include <stddef.h>
19 |
reexport.c:41:37: error: expected expression before struct
41 | addr_len = offsetof(struct sockaddr_un, sun_path) + strlen(addr.sun_path);
| ^~~~~~
cc1: some warnings being treated as errors
CVE: CVE-2025-12801
Upstream-Status: Backport [https://git.linux-nfs.org/?p=steved/nfs-utils.git;a=commit;h=a2c95e4f557a71b482bb62bad6d93ddde51e5dc6]
Signed-off-by: Steve Dickson <steved@redhat.com>
(cherry picked from commit a2c95e4f557a71b482bb62bad6d93ddde51e5dc6)
Signed-off-by: Sudhir Dumbhare <sudumbha@cisco.com>
---
support/reexport/reexport.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/support/reexport/reexport.c b/support/reexport/reexport.c
index 78516586..16dde0fb 100644
--- a/support/reexport/reexport.c
+++ b/support/reexport/reexport.c
@@ -8,6 +8,7 @@
#include <sys/types.h>
#include <sys/vfs.h>
#include <errno.h>
+#include <stddef.h>
#include "nfsd_path.h"
#include "conffile.h"
--
2.44.4

View File

@@ -0,0 +1,450 @@
From bbec1c68cbf9a9b3b28aad213b4573d288879a6f Mon Sep 17 00:00:00 2001
From: Christopher Bii <christopherbii@hyub.org>
Date: Wed, 15 Jan 2025 12:10:48 -0500
Subject: [PATCH] NFS export symlink vulnerability fix
Replaced dangerous use of realpath within support/nfs/export.c with
nfsd_realpath variant that is executed within the chrooted thread
rather than main thread.
Implemented nfsd_path.h methods to work securely within chrooted
thread using nfsd_run_task() help
CVE: CVE-2025-12801
Upstream-Status: Backport [https://git.linux-nfs.org/?p=steved/nfs-utils.git;a=commit;h=cd90f29257904f36509ea5a04a86f42398fbe94a]
Signed-off-by: Christopher Bii <christopherbii@hyub.org>
Signed-off-by: Steve Dickson <steved@redhat.com>
(cherry picked from commit cd90f29257904f36509ea5a04a86f42398fbe94a)
Signed-off-by: Sudhir Dumbhare <sudumbha@cisco.com>
---
support/export/cache.c | 2 +-
support/include/nfsd_path.h | 5 +-
support/misc/nfsd_path.c | 257 +++++++++++-------------------------
support/nfs/exports.c | 3 +-
4 files changed, 83 insertions(+), 184 deletions(-)
diff --git a/support/export/cache.c b/support/export/cache.c
index 6c0a44a3..a4c339f2 100644
--- a/support/export/cache.c
+++ b/support/export/cache.c
@@ -65,7 +65,7 @@ static ssize_t cache_read(int fd, char *buf, size_t len)
return nfsd_path_read(fd, buf, len);
}
-static ssize_t cache_write(int fd, const char *buf, size_t len)
+static ssize_t cache_write(int fd, void *buf, size_t len)
{
return nfsd_path_write(fd, buf, len);
}
diff --git a/support/include/nfsd_path.h b/support/include/nfsd_path.h
index aa1e1dd0..f600fb5a 100644
--- a/support/include/nfsd_path.h
+++ b/support/include/nfsd_path.h
@@ -8,6 +8,7 @@
struct file_handle;
struct statfs;
+struct nfsd_task_t;
void nfsd_path_init(void);
@@ -23,8 +24,8 @@ int nfsd_path_statfs(const char *pathname,
char * nfsd_realpath(const char *path, char *resolved_path);
-ssize_t nfsd_path_read(int fd, char *buf, size_t len);
-ssize_t nfsd_path_write(int fd, const char *buf, size_t len);
+ssize_t nfsd_path_read(int fd, void* buf, size_t len);
+ssize_t nfsd_path_write(int fd, void* buf, size_t len);
int nfsd_name_to_handle_at(int fd, const char *path,
struct file_handle *fh,
diff --git a/support/misc/nfsd_path.c b/support/misc/nfsd_path.c
index c3dea4f0..caec33ca 100644
--- a/support/misc/nfsd_path.c
+++ b/support/misc/nfsd_path.c
@@ -19,7 +19,20 @@
#include "nfsd_path.h"
#include "workqueue.h"
-static struct xthread_workqueue *nfsd_wq;
+static struct xthread_workqueue *nfsd_wq = NULL;
+
+struct nfsd_task_t {
+ int ret;
+ void* data;
+};
+/* Function used to offload tasks that must be ran within the correct
+ * chroot environment.
+ */
+static void
+nfsd_run_task(void (*func)(void*), void* data){
+ nfsd_wq ? xthread_work_run_sync(nfsd_wq, func, data) : func(data);
+};
+
static int
nfsd_path_isslash(const char *path)
@@ -124,224 +137,119 @@ nfsd_path_init(void)
}
struct nfsd_stat_data {
- const char *pathname;
- struct stat *statbuf;
- int ret;
- int err;
+ const char *pathname;
+ struct stat *statbuf;
+ int (*stat_handler)(const char*, struct stat*);
};
static void
-nfsd_statfunc(void *data)
-{
- struct nfsd_stat_data *d = data;
-
- d->ret = xstat(d->pathname, d->statbuf);
- if (d->ret < 0)
- d->err = errno;
-}
-
-static void
-nfsd_lstatfunc(void *data)
+nfsd_handle_stat(void *data)
{
- struct nfsd_stat_data *d = data;
-
- d->ret = xlstat(d->pathname, d->statbuf);
- if (d->ret < 0)
- d->err = errno;
+ struct nfsd_task_t* t = data;
+ struct nfsd_stat_data* d = t->data;
+ t->ret = d->stat_handler(d->pathname, d->statbuf);
}
static int
-nfsd_run_stat(struct xthread_workqueue *wq,
- void (*func)(void *),
- const char *pathname,
- struct stat *statbuf)
+nfsd_run_stat(const char *pathname,
+ struct stat *statbuf,
+ int (*handler)(const char*, struct stat*))
{
- struct nfsd_stat_data data = {
- pathname,
- statbuf,
- 0,
- 0
- };
- xthread_work_run_sync(wq, func, &data);
- if (data.ret < 0)
- errno = data.err;
- return data.ret;
+ struct nfsd_task_t t;
+ struct nfsd_stat_data d = { pathname, statbuf, handler };
+ t.data = &d;
+ nfsd_run_task(nfsd_handle_stat, &t);
+ return t.ret;
}
int
nfsd_path_stat(const char *pathname, struct stat *statbuf)
{
- if (!nfsd_wq)
- return xstat(pathname, statbuf);
- return nfsd_run_stat(nfsd_wq, nfsd_statfunc, pathname, statbuf);
+ return nfsd_run_stat(pathname, statbuf, stat);
}
int
-nfsd_path_lstat(const char *pathname, struct stat *statbuf)
-{
- if (!nfsd_wq)
- return xlstat(pathname, statbuf);
- return nfsd_run_stat(nfsd_wq, nfsd_lstatfunc, pathname, statbuf);
-}
-
-struct nfsd_statfs_data {
- const char *pathname;
- struct statfs *statbuf;
- int ret;
- int err;
+nfsd_path_lstat(const char* pathname, struct stat* statbuf){
+ return nfsd_run_stat(pathname, statbuf, lstat);
};
-static void
-nfsd_statfsfunc(void *data)
-{
- struct nfsd_statfs_data *d = data;
-
- d->ret = statfs(d->pathname, d->statbuf);
- if (d->ret < 0)
- d->err = errno;
-}
-
-static int
-nfsd_run_statfs(struct xthread_workqueue *wq,
- const char *pathname,
- struct statfs *statbuf)
-{
- struct nfsd_statfs_data data = {
- pathname,
- statbuf,
- 0,
- 0
- };
- xthread_work_run_sync(wq, nfsd_statfsfunc, &data);
- if (data.ret < 0)
- errno = data.err;
- return data.ret;
-}
-
int
-nfsd_path_statfs(const char *pathname, struct statfs *statbuf)
+nfsd_path_statfs(const char* pathname, struct statfs* statbuf)
{
- if (!nfsd_wq)
- return statfs(pathname, statbuf);
- return nfsd_run_statfs(nfsd_wq, pathname, statbuf);
-}
+ return nfsd_run_stat(pathname, (struct stat*)statbuf, (int (*)(const char*, struct stat*))statfs);
+};
-struct nfsd_realpath_data {
- const char *pathname;
- char *resolved;
- int err;
+struct nfsd_realpath_t {
+ const char* path;
+ char* resolved_buf;
+ char* res_ptr;
};
static void
nfsd_realpathfunc(void *data)
{
- struct nfsd_realpath_data *d = data;
-
- d->resolved = realpath(d->pathname, d->resolved);
- if (!d->resolved)
- d->err = errno;
+ struct nfsd_realpath_t *d = data;
+ d->res_ptr = realpath(d->path, d->resolved_buf);
}
-char *
-nfsd_realpath(const char *path, char *resolved_path)
+char*
+nfsd_realpath(const char *path, char *resolved_buf)
{
- struct nfsd_realpath_data data = {
- path,
- resolved_path,
- 0
- };
-
- if (!nfsd_wq)
- return realpath(path, resolved_path);
-
- xthread_work_run_sync(nfsd_wq, nfsd_realpathfunc, &data);
- if (!data.resolved)
- errno = data.err;
- return data.resolved;
+ struct nfsd_realpath_t realpath_buf = {
+ .path = path,
+ .resolved_buf = resolved_buf
+ };
+ nfsd_run_task(nfsd_realpathfunc, &realpath_buf);
+ return realpath_buf.res_ptr;
}
-struct nfsd_read_data {
- int fd;
- char *buf;
- size_t len;
- ssize_t ret;
- int err;
+struct nfsd_rw_data {
+ int fd;
+ void* buf;
+ size_t len;
+ ssize_t bytes_read;
};
static void
nfsd_readfunc(void *data)
{
- struct nfsd_read_data *d = data;
-
- d->ret = read(d->fd, d->buf, d->len);
- if (d->ret < 0)
- d->err = errno;
+ struct nfsd_rw_data* t = (struct nfsd_rw_data*)data;
+ t->bytes_read = read(t->fd, t->buf, t->len);
}
static ssize_t
-nfsd_run_read(struct xthread_workqueue *wq, int fd, char *buf, size_t len)
+nfsd_run_read(int fd, void* buf, size_t len)
{
- struct nfsd_read_data data = {
- fd,
- buf,
- len,
- 0,
- 0
- };
- xthread_work_run_sync(wq, nfsd_readfunc, &data);
- if (data.ret < 0)
- errno = data.err;
- return data.ret;
+ struct nfsd_rw_data d = { .fd = fd, .buf = buf, .len = len };
+ nfsd_run_task(nfsd_readfunc, &d);
+ return d.bytes_read;
}
ssize_t
-nfsd_path_read(int fd, char *buf, size_t len)
+nfsd_path_read(int fd, void* buf, size_t len)
{
- if (!nfsd_wq)
- return read(fd, buf, len);
- return nfsd_run_read(nfsd_wq, fd, buf, len);
+ return nfsd_run_read(fd, buf, len);
}
-struct nfsd_write_data {
- int fd;
- const char *buf;
- size_t len;
- ssize_t ret;
- int err;
-};
-
static void
nfsd_writefunc(void *data)
{
- struct nfsd_write_data *d = data;
-
- d->ret = write(d->fd, d->buf, d->len);
- if (d->ret < 0)
- d->err = errno;
+ struct nfsd_rw_data* d = data;
+ d->bytes_read = write(d->fd, d->buf, d->len);
}
static ssize_t
-nfsd_run_write(struct xthread_workqueue *wq, int fd, const char *buf, size_t len)
+nfsd_run_write(int fd, void* buf, size_t len)
{
- struct nfsd_write_data data = {
- fd,
- buf,
- len,
- 0,
- 0
- };
- xthread_work_run_sync(wq, nfsd_writefunc, &data);
- if (data.ret < 0)
- errno = data.err;
- return data.ret;
+ struct nfsd_rw_data d = { .fd = fd, .buf = buf, .len = len };
+ nfsd_run_task(nfsd_writefunc, &d);
+ return d.bytes_read;
}
ssize_t
-nfsd_path_write(int fd, const char *buf, size_t len)
+nfsd_path_write(int fd, void* buf, size_t len)
{
- if (!nfsd_wq)
- return write(fd, buf, len);
- return nfsd_run_write(nfsd_wq, fd, buf, len);
+ return nfsd_run_write(fd, buf, len);
}
#if defined(HAVE_NAME_TO_HANDLE_AT)
@@ -352,23 +260,18 @@ struct nfsd_handle_data {
int *mount_id;
int flags;
int ret;
- int err;
};
static void
nfsd_name_to_handle_func(void *data)
{
struct nfsd_handle_data *d = data;
-
- d->ret = name_to_handle_at(d->fd, d->path,
- d->fh, d->mount_id, d->flags);
- if (d->ret < 0)
- d->err = errno;
+ d->ret = name_to_handle_at(d->fd, d->path, d->fh, d->mount_id, d->flags);
}
static int
-nfsd_run_name_to_handle_at(struct xthread_workqueue *wq,
- int fd, const char *path, struct file_handle *fh,
+nfsd_run_name_to_handle_at(int fd, const char *path,
+ struct file_handle *fh,
int *mount_id, int flags)
{
struct nfsd_handle_data data = {
@@ -377,25 +280,19 @@ nfsd_run_name_to_handle_at(struct xthread_workqueue *wq,
fh,
mount_id,
flags,
- 0,
0
};
- xthread_work_run_sync(wq, nfsd_name_to_handle_func, &data);
- if (data.ret < 0)
- errno = data.err;
+ nfsd_run_task(nfsd_name_to_handle_func, &data);
return data.ret;
}
int
-nfsd_name_to_handle_at(int fd, const char *path, struct file_handle *fh,
+nfsd_name_to_handle_at(int fd, const char *path,
+ struct file_handle *fh,
int *mount_id, int flags)
{
- if (!nfsd_wq)
- return name_to_handle_at(fd, path, fh, mount_id, flags);
-
- return nfsd_run_name_to_handle_at(nfsd_wq, fd, path, fh,
- mount_id, flags);
+ return nfsd_run_name_to_handle_at(fd, path, fh, mount_id, flags);
}
#else
int
diff --git a/support/nfs/exports.c b/support/nfs/exports.c
index 15dc574c..c47e3d0a 100644
--- a/support/nfs/exports.c
+++ b/support/nfs/exports.c
@@ -32,6 +32,7 @@
#include "xio.h"
#include "pseudoflavors.h"
#include "reexport.h"
+#include "nfsd_path.h"
#define EXPORT_DEFAULT_FLAGS \
(NFSEXP_READONLY|NFSEXP_ROOTSQUASH|NFSEXP_GATHERED_WRITES|NFSEXP_NOSUBTREECHECK)
@@ -200,7 +201,7 @@ getexportent(int fromkernel, int fromexports)
return NULL;
}
/* resolve symlinks */
- if (realpath(ee.e_path, rpath) != NULL) {
+ if (nfsd_realpath(ee.e_path, rpath) != NULL) {
rpath[sizeof (rpath) - 1] = '\0';
strncpy(ee.e_path, rpath, sizeof (ee.e_path) - 1);
ee.e_path[sizeof (ee.e_path) - 1] = '\0';
--
2.35.6

View File

@@ -0,0 +1,81 @@
From a6ddd0e9594884cf61816478e8c561f1b3aac709 Mon Sep 17 00:00:00 2001
From: Trond Myklebust <trond.myklebust@hammerspace.com>
Date: Mon, 10 Nov 2025 11:26:03 -0500
Subject: [PATCH] mountd: Minor refactor of get_rootfh()
Perform the mountpoint checks before checking the user path.
CVE: CVE-2025-12801
Upstream-Status: Backport [https://git.linux-nfs.org/?p=steved/nfs-utils.git;a=commit;h=7e8b36522f58657359c6842119fc516c6dd1baa4]
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 7e8b36522f58657359c6842119fc516c6dd1baa4)
Signed-off-by: Sudhir Dumbhare <sudumbha@cisco.com>
---
utils/mountd/mountd.c | 34 +++++++++++++++++-----------------
1 file changed, 17 insertions(+), 17 deletions(-)
diff --git a/utils/mountd/mountd.c b/utils/mountd/mountd.c
index dbd5546d..39afd4aa 100644
--- a/utils/mountd/mountd.c
+++ b/utils/mountd/mountd.c
@@ -412,6 +412,23 @@ 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",
+ p, strerror(errno));
+ *error = MNT3ERR_NOENT;
+ return NULL;
+ }
+ if (exp->m_export.e_mountpoint &&
+ !check_is_mountpoint(exp->m_export.e_mountpoint[0]?
+ exp->m_export.e_mountpoint:
+ exp->m_export.e_path,
+ nfsd_path_lstat)) {
+ xlog(L_WARNING, "request to export an unmounted filesystem: %s",
+ p);
+ *error = MNT3ERR_NOENT;
+ return NULL;
+ }
+
if (nfsd_path_stat(p, &stb) < 0) {
xlog(L_WARNING, "can't stat exported dir %s: %s",
p, strerror(errno));
@@ -426,12 +443,6 @@ get_rootfh(struct svc_req *rqstp, dirpath *path, nfs_export **expret,
*error = MNT3ERR_NOTDIR;
return NULL;
}
- if (nfsd_path_stat(exp->m_export.e_path, &estb) < 0) {
- xlog(L_WARNING, "can't stat export point %s: %s",
- p, strerror(errno));
- *error = MNT3ERR_NOENT;
- return NULL;
- }
if (estb.st_dev != stb.st_dev
&& !(exp->m_export.e_flags & NFSEXP_CROSSMOUNT)) {
xlog(L_WARNING, "request to export directory %s below nearest filesystem %s",
@@ -439,17 +450,6 @@ get_rootfh(struct svc_req *rqstp, dirpath *path, nfs_export **expret,
*error = MNT3ERR_ACCES;
return NULL;
}
- if (exp->m_export.e_mountpoint &&
- !check_is_mountpoint(exp->m_export.e_mountpoint[0]?
- exp->m_export.e_mountpoint:
- exp->m_export.e_path,
- nfsd_path_lstat)) {
- xlog(L_WARNING, "request to export an unmounted filesystem: %s",
- p);
- *error = MNT3ERR_NOENT;
- return NULL;
- }
-
/* This will be a static private nfs_export with just one
* address. We feed it to kernel then extract the filehandle,
*/
--
2.44.4

View File

@@ -0,0 +1,181 @@
From 57732919d26ce523161392d688e3b67d6fc50839 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] 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: Sudhir Dumbhare <sudumbha@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

View File

@@ -0,0 +1,468 @@
From 7eef498b6bd01adc45415b03ddf321c84f82aa45 Mon Sep 17 00:00:00 2001
From: Trond Myklebust <trond.myklebust@hammerspace.com>
Date: Mon, 10 Nov 2025 12:18:38 -0500
Subject: [PATCH] support: Add a mini-library to extract and apply RPC
credentials
Add server functionality to extract the credentials from the client RPC
call, and apply them. This is needed in order to perform access checking
on the requested path in the mountd daemon.
CVE: CVE-2025-12801
Upstream-Status: Backport [https://git.linux-nfs.org/?p=steved/nfs-utils.git;a=commit;h=51738ae56d922d4961e60dad73ad1c2d97d8d99b]
Backport Changes:
- In support/misc/Makefile.am, the non-essential file.c was omitted
as it does not exist in the current nfs-utils version.
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 51738ae56d922d4961e60dad73ad1c2d97d8d99b)
Signed-off-by: Sudhir Dumbhare <sudumbha@cisco.com>
---
aclocal/libtirpc.m4 | 11 +++
support/include/Makefile.am | 1 +
support/include/nfs_ucred.h | 44 ++++++++++
support/misc/Makefile.am | 2 +-
support/misc/ucred.c | 162 ++++++++++++++++++++++++++++++++++++
support/nfs/Makefile.am | 2 +-
support/nfs/ucred.c | 147 ++++++++++++++++++++++++++++++++
7 files changed, 367 insertions(+), 2 deletions(-)
create mode 100644 support/include/nfs_ucred.h
create mode 100644 support/misc/ucred.c
create mode 100644 support/nfs/ucred.c
diff --git a/aclocal/libtirpc.m4 b/aclocal/libtirpc.m4
index bddae022..84e18f7e 100644
--- a/aclocal/libtirpc.m4
+++ b/aclocal/libtirpc.m4
@@ -26,6 +26,17 @@ AC_DEFUN([AC_LIBTIRPC], [
[Define to 1 if your tirpc library provides libtirpc_set_debug])],,
[${LIBS}])])
+ AS_IF([test -n "${LIBTIRPC}"],
+ [AC_CHECK_LIB([tirpc], [rpc_gss_getcred],
+ [AC_DEFINE([HAVE_TIRPC_GSS_GETCRED], [1],
+ [Define to 1 if your tirpc library provides rpc_gss_getcred])],,
+ [${LIBS}])])
+
+ AS_IF([test -n "${LIBTIRPC}"],
+ [AC_CHECK_LIB([tirpc], [authdes_getucred],
+ [AC_DEFINE([HAVE_TIRPC_AUTHDES_GETUCRED], [1],
+ [Define to 1 if your tirpc library provides authdes_getucred])],,
+ [${LIBS}])])
AC_SUBST([AM_CPPFLAGS])
AC_SUBST(LIBTIRPC)
diff --git a/support/include/Makefile.am b/support/include/Makefile.am
index 1373891a..631a84f8 100644
--- a/support/include/Makefile.am
+++ b/support/include/Makefile.am
@@ -10,6 +10,7 @@ noinst_HEADERS = \
misc.h \
nfs_mntent.h \
nfs_paths.h \
+ nfs_ucred.h \
nfsd_path.h \
nfslib.h \
nfsrpc.h \
diff --git a/support/include/nfs_ucred.h b/support/include/nfs_ucred.h
new file mode 100644
index 00000000..d58b61e4
--- /dev/null
+++ b/support/include/nfs_ucred.h
@@ -0,0 +1,44 @@
+#ifndef _NFS_UCRED_H
+#define _NFS_UCRED_H
+
+#include <sys/types.h>
+
+struct nfs_ucred {
+ uid_t uid;
+ gid_t gid;
+ int ngroups;
+ gid_t *groups;
+};
+
+struct svc_req;
+struct exportent;
+
+int nfs_ucred_get(struct nfs_ucred **credp, struct svc_req *rqst,
+ const struct exportent *ep);
+
+void nfs_ucred_squash_groups(struct nfs_ucred *cred,
+ const struct exportent *ep);
+int nfs_ucred_reload_groups(struct nfs_ucred *cred, const struct exportent *ep);
+int nfs_ucred_swap_effective(const struct nfs_ucred *cred,
+ struct nfs_ucred **savedp);
+
+static inline void nfs_ucred_free(struct nfs_ucred *cred)
+{
+ free(cred->groups);
+ free(cred);
+}
+
+static inline void nfs_ucred_init_groups(struct nfs_ucred *cred, gid_t *groups,
+ int ngroups)
+{
+ cred->groups = groups;
+ cred->ngroups = ngroups;
+}
+
+static inline void nfs_ucred_free_groups(struct nfs_ucred *cred)
+{
+ free(cred->groups);
+ nfs_ucred_init_groups(cred, NULL, 0);
+}
+
+#endif /* _NFS_UCRED_H */
diff --git a/support/misc/Makefile.am b/support/misc/Makefile.am
index 8b0e9db9..ea970064 100644
--- a/support/misc/Makefile.am
+++ b/support/misc/Makefile.am
@@ -2,6 +2,6 @@
noinst_LIBRARIES = libmisc.a
libmisc_a_SOURCES = tcpwrapper.c from_local.c mountpoint.c misc.c \
- nfsd_path.c workqueue.c xstat.c
+ nfsd_path.c ucred.c workqueue.c xstat.c
MAINTAINERCLEANFILES = Makefile.in
diff --git a/support/misc/ucred.c b/support/misc/ucred.c
new file mode 100644
index 00000000..92d97912
--- /dev/null
+++ b/support/misc/ucred.c
@@ -0,0 +1,162 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <alloca.h>
+#include <errno.h>
+#include <pwd.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <grp.h>
+
+#include "exportfs.h"
+#include "nfs_ucred.h"
+
+#include "xlog.h"
+
+void nfs_ucred_squash_groups(struct nfs_ucred *cred, const struct exportent *ep)
+{
+ int i;
+
+ if (!(ep->e_flags & NFSEXP_ROOTSQUASH))
+ return;
+ if (cred->gid == 0)
+ cred->gid = ep->e_anongid;
+ for (i = 0; i < cred->ngroups; i++) {
+ if (cred->groups[i] == 0)
+ cred->groups[i] = ep->e_anongid;
+ }
+}
+
+static int nfs_ucred_init_effective(struct nfs_ucred *cred)
+{
+ int ngroups = getgroups(0, NULL);
+
+ if (ngroups > 0) {
+ size_t sz = ngroups * sizeof(gid_t);
+ gid_t *groups = malloc(sz);
+ if (groups == NULL)
+ return ENOMEM;
+ if (getgroups(ngroups, groups) == -1) {
+ free(groups);
+ return errno;
+ }
+ nfs_ucred_init_groups(cred, groups, ngroups);
+ } else
+ nfs_ucred_init_groups(cred, NULL, 0);
+ cred->uid = geteuid();
+ cred->gid = getegid();
+ return 0;
+}
+
+static size_t nfs_ucred_getpw_r_size_max(void)
+{
+ long buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
+
+ if (buflen == -1)
+ return 16384;
+ return buflen;
+}
+
+int nfs_ucred_reload_groups(struct nfs_ucred *cred, const struct exportent *ep)
+{
+ struct passwd pwd, *pw;
+ uid_t uid = cred->uid;
+ gid_t gid = cred->gid;
+ size_t buflen;
+ char *buf;
+ int ngroups = 0;
+ int ret;
+
+ if (ep->e_flags & (NFSEXP_ALLSQUASH | NFSEXP_ROOTSQUASH) &&
+ (int)uid == ep->e_anonuid)
+ return 0;
+ buflen = nfs_ucred_getpw_r_size_max();
+ buf = alloca(buflen);
+ ret = getpwuid_r(uid, &pwd, buf, buflen, &pw);
+ if (ret != 0)
+ return ret;
+ if (!pw)
+ return ENOENT;
+ if (getgrouplist(pw->pw_name, gid, NULL, &ngroups) == -1 &&
+ ngroups > 0) {
+ gid_t *groups = malloc(ngroups * sizeof(groups[0]));
+ if (groups == NULL)
+ return ENOMEM;
+ if (getgrouplist(pw->pw_name, gid, groups, &ngroups) == -1) {
+ free(groups);
+ return ENOMEM;
+ }
+ free(cred->groups);
+ nfs_ucred_init_groups(cred, groups, ngroups);
+ nfs_ucred_squash_groups(cred, ep);
+ } else
+ nfs_ucred_free_groups(cred);
+ return 0;
+}
+
+static int nfs_ucred_set_effective(const struct nfs_ucred *cred,
+ const struct nfs_ucred *saved)
+{
+ uid_t suid = saved ? saved->uid : geteuid();
+ gid_t sgid = saved ? saved->gid : getegid();
+ int ret;
+
+ /* Start with a privileged effective user */
+ if (setresuid(-1, 0, -1) < 0) {
+ xlog(L_WARNING, "can't change privileged user %u-%u. %s",
+ geteuid(), getegid(), strerror(errno));
+ return errno;
+ }
+
+ if (setgroups(cred->ngroups, cred->groups) == -1) {
+ xlog(L_WARNING, "can't change groups for user %u-%u. %s",
+ geteuid(), getegid(), strerror(errno));
+ return errno;
+ }
+ if (setresgid(-1, cred->gid, sgid) == -1) {
+ xlog(L_WARNING, "can't change gid for user %u-%u. %s",
+ geteuid(), getegid(), strerror(errno));
+ ret = errno;
+ goto restore_groups;
+ }
+ if (setresuid(-1, cred->uid, suid) == -1) {
+ xlog(L_WARNING, "can't change uid for user %u-%u. %s",
+ geteuid(), getegid(), strerror(errno));
+ ret = errno;
+ goto restore_gid;
+ }
+ return 0;
+restore_gid:
+ if (setresgid(-1, sgid, -1) < 0) {
+ xlog(L_WARNING, "can't restore privileged user %u-%u. %s",
+ geteuid(), getegid(), strerror(errno));
+ }
+restore_groups:
+ if (saved)
+ setgroups(saved->ngroups, saved->groups);
+ else
+ setgroups(0, NULL);
+ return ret;
+}
+
+int nfs_ucred_swap_effective(const struct nfs_ucred *cred,
+ struct nfs_ucred **savedp)
+{
+ struct nfs_ucred *saved = malloc(sizeof(*saved));
+ int ret;
+
+ if (saved == NULL)
+ return ENOMEM;
+ ret = nfs_ucred_init_effective(saved);
+ if (ret != 0) {
+ free(saved);
+ return ret;
+ }
+ ret = nfs_ucred_set_effective(cred, saved);
+ if (savedp == NULL || ret != 0)
+ nfs_ucred_free(saved);
+ else
+ *savedp = saved;
+ return ret;
+}
diff --git a/support/nfs/Makefile.am b/support/nfs/Makefile.am
index 2e1577cc..f6921265 100644
--- a/support/nfs/Makefile.am
+++ b/support/nfs/Makefile.am
@@ -7,7 +7,7 @@ libnfs_la_SOURCES = exports.c rmtab.c xio.c rpcmisc.c rpcdispatch.c \
xcommon.c wildmat.c mydaemon.c \
rpc_socket.c getport.c \
svc_socket.c cacheio.c closeall.c nfs_mntent.c \
- svc_create.c atomicio.c strlcat.c strlcpy.c
+ svc_create.c atomicio.c strlcat.c strlcpy.c ucred.c
libnfs_la_LIBADD = libnfsconf.la
libnfs_la_CPPFLAGS = $(AM_CPPFLAGS) $(CPPFLAGS) -I$(top_srcdir)/support/reexport
diff --git a/support/nfs/ucred.c b/support/nfs/ucred.c
new file mode 100644
index 00000000..6ea8efdf
--- /dev/null
+++ b/support/nfs/ucred.c
@@ -0,0 +1,147 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <rpc/rpc.h>
+
+#include "exportfs.h"
+#include "nfs_ucred.h"
+
+#ifdef HAVE_TIRPC_GSS_GETCRED
+#include <rpc/rpcsec_gss.h>
+#endif /* HAVE_TIRPC_GSS_GETCRED */
+#ifdef HAVE_TIRPC_AUTHDES_GETUCRED
+#include <rpc/auth_des.h>
+#endif /* HAVE_TIRPC_AUTHDES_GETUCRED */
+
+static int nfs_ucred_copy_cred(struct nfs_ucred *cred, uid_t uid, gid_t gid,
+ const gid_t *groups, int ngroups)
+{
+ if (ngroups > 0) {
+ size_t sz = ngroups * sizeof(groups[0]);
+ cred->groups = malloc(sz);
+ if (cred->groups == NULL)
+ return ENOMEM;
+ cred->ngroups = ngroups;
+ memcpy(cred->groups, groups, sz);
+ } else
+ nfs_ucred_init_groups(cred, NULL, 0);
+ cred->uid = uid;
+ cred->gid = gid;
+ return 0;
+}
+
+static int nfs_ucred_init_cred_squashed(struct nfs_ucred *cred,
+ const struct exportent *ep)
+{
+ cred->uid = ep->e_anonuid;
+ cred->gid = ep->e_anongid;
+ nfs_ucred_init_groups(cred, NULL, 0);
+ return 0;
+}
+
+static int nfs_ucred_init_cred(struct nfs_ucred *cred, uid_t uid, gid_t gid,
+ const gid_t *groups, int ngroups,
+ const struct exportent *ep)
+{
+ if (ep->e_flags & NFSEXP_ALLSQUASH) {
+ nfs_ucred_init_cred_squashed(cred, ep);
+ } else if (ep->e_flags & NFSEXP_ROOTSQUASH && uid == 0) {
+ nfs_ucred_init_cred_squashed(cred, ep);
+ if (gid != 0)
+ cred->gid = gid;
+ } else {
+ int ret = nfs_ucred_copy_cred(cred, uid, gid, groups, ngroups);
+ if (ret != 0)
+ return ret;
+ nfs_ucred_squash_groups(cred, ep);
+ }
+ return 0;
+}
+
+static int nfs_ucred_init_null(struct nfs_ucred *cred,
+ const struct exportent *ep)
+{
+ return nfs_ucred_init_cred_squashed(cred, ep);
+}
+
+static int nfs_ucred_init_unix(struct nfs_ucred *cred, struct svc_req *rqst,
+ const struct exportent *ep)
+{
+ struct authunix_parms *aup;
+
+ aup = (struct authunix_parms *)rqst->rq_clntcred;
+ return nfs_ucred_init_cred(cred, aup->aup_uid, aup->aup_gid,
+ aup->aup_gids, aup->aup_len, ep);
+}
+
+#ifdef HAVE_TIRPC_GSS_GETCRED
+static int nfs_ucred_init_gss(struct nfs_ucred *cred, struct svc_req *rqst,
+ const struct exportent *ep)
+{
+ rpc_gss_ucred_t *gss_ucred = NULL;
+
+ if (!rpc_gss_getcred(rqst, NULL, &gss_ucred, NULL) || gss_ucred == NULL)
+ return EINVAL;
+ return nfs_ucred_init_cred(cred, gss_ucred->uid, gss_ucred->gid,
+ gss_ucred->gidlist, gss_ucred->gidlen, ep);
+}
+#endif /* HAVE_TIRPC_GSS_GETCRED */
+
+#ifdef HAVE_TIRPC_AUTHDES_GETUCRED
+int authdes_getucred(struct authdes_cred *adc, uid_t *uid, gid_t *gid,
+ int *grouplen, gid_t *groups);
+
+static int nfs_ucred_init_des(struct nfs_ucred *cred, struct svc_req *rqst,
+ const struct exportent *ep)
+{
+ struct authdes_cred *des_cred;
+ uid_t uid;
+ gid_t gid;
+ int grouplen;
+ gid_t groups[NGROUPS];
+
+ des_cred = (struct authdes_cred *)rqst->rq_clntcred;
+ if (!authdes_getucred(des_cred, &uid, &gid, &grouplen, &groups[0]))
+ return EINVAL;
+ return nfs_ucred_init_cred(cred, uid, gid, groups, grouplen, ep);
+}
+#endif /* HAVE_TIRPC_AUTHDES_GETUCRED */
+
+int nfs_ucred_get(struct nfs_ucred **credp, struct svc_req *rqst,
+ const struct exportent *ep)
+{
+ struct nfs_ucred *cred = malloc(sizeof(*cred));
+ int ret;
+
+ *credp = NULL;
+ if (cred == NULL)
+ return ENOMEM;
+ switch (rqst->rq_cred.oa_flavor) {
+ case AUTH_UNIX:
+ ret = nfs_ucred_init_unix(cred, rqst, ep);
+ break;
+#ifdef HAVE_TIRPC_GSS_GETCRED
+ case RPCSEC_GSS:
+ ret = nfs_ucred_init_gss(cred, rqst, ep);
+ break;
+#endif /* HAVE_TIRPC_GSS_GETCRED */
+#ifdef HAVE_TIRPC_AUTHDES_GETUCRED
+ case AUTH_DES:
+ ret = nfs_ucred_init_des(cred, rqst, ep);
+ break;
+#endif /* HAVE_TIRPC_AUTHDES_GETUCRED */
+ default:
+ ret = nfs_ucred_init_null(cred, ep);
+ break;
+ }
+ if (ret == 0) {
+ *credp = cred;
+ return 0;
+ }
+ free(cred);
+ return ret;
+}
--
2.44.4

View File

@@ -0,0 +1,254 @@
From a94b2b6002f31acc5a66893b7c6d368c6b7b8806 Mon Sep 17 00:00:00 2001
From: Trond Myklebust <trond.myklebust@hammerspace.com>
Date: Thu, 5 Mar 2026 10:41:02 -0500
Subject: [PATCH] Fix access checks when mounting subdirectories in NFSv3
If a NFSv3 client asks to mount a subdirectory of one of the exported
directories, then apply the RPC credential together with any root
or all squash rules that would apply to the client in question.
CVE: CVE-2025-12801
Upstream-Status: Backport [https://git.linux-nfs.org/?p=steved/nfs-utils.git;a=commit;h=f36bd900a899088ca1925de079bd58d6205a1f3c]
Reviewed-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
Signed-off-by: Scott Mayhew <smayhew@redhat.com>
Signed-off-by: Steve Dickson <steved@redhat.com>
(cherry picked from commit f36bd900a899088ca1925de079bd58d6205a1f3c)
Signed-off-by: Sudhir Dumbhare <sudumbha@cisco.com>
---
nfs.conf | 1 +
support/include/nfsd_path.h | 9 ++++++++-
support/misc/nfsd_path.c | 32 ++++++++++++++++++++++++++++++--
utils/mountd/mountd.c | 28 ++++++++++++++++++++++++++--
utils/mountd/mountd.man | 26 ++++++++++++++++++++++++++
5 files changed, 91 insertions(+), 5 deletions(-)
diff --git a/nfs.conf b/nfs.conf
index 323f072b..e08cd9a9 100644
--- a/nfs.conf
+++ b/nfs.conf
@@ -45,6 +45,7 @@
# ttl=1800
[mountd]
# debug="all|auth|call|general|parse"
+# apply-root-cred=n
# manage-gids=n
# descriptors=0
# port=0
diff --git a/support/include/nfsd_path.h b/support/include/nfsd_path.h
index 3e5a2f5d..06c0f2f4 100644
--- a/support/include/nfsd_path.h
+++ b/support/include/nfsd_path.h
@@ -9,6 +9,7 @@
struct file_handle;
struct statfs;
struct nfsd_task_t;
+struct nfs_ucred;
void nfsd_path_init(void);
@@ -18,7 +19,8 @@ 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_cred_openat(const struct nfs_ucred *cred, int dirfd,
+ const char *path, int flags);
int nfsd_path_statfs(const char *pathname,
struct statfs *statbuf);
@@ -31,4 +33,9 @@ ssize_t nfsd_path_write(int fd, void* buf, size_t len);
int nfsd_name_to_handle_at(int fd, const char *path,
struct file_handle *fh,
int *mount_id, int flags);
+
+static inline int nfsd_openat(int dirfd, const char *path, int flags)
+{
+ return nfsd_cred_openat(NULL, dirfd, path, flags);
+}
#endif
diff --git a/support/misc/nfsd_path.c b/support/misc/nfsd_path.c
index dfe88e4f..6466666d 100644
--- a/support/misc/nfsd_path.c
+++ b/support/misc/nfsd_path.c
@@ -17,6 +17,7 @@
#include "xstat.h"
#include "nfslib.h"
#include "nfsd_path.h"
+#include "nfs_ucred.h"
#include "workqueue.h"
static struct xthread_workqueue *nfsd_wq = NULL;
@@ -204,6 +205,7 @@ nfsd_realpath(const char *path, char *resolved_buf)
}
struct nfsd_openat_t {
+ const struct nfs_ucred *cred;
const char *path;
int dirfd;
int flags;
@@ -220,15 +222,41 @@ static void nfsd_openatfunc(void *data)
d->res_error = errno;
}
-int nfsd_openat(int dirfd, const char *path, int flags)
+static void nfsd_cred_openatfunc(void *data)
+{
+ struct nfsd_openat_t *d = data;
+ struct nfs_ucred *saved = NULL;
+ int ret;
+
+ ret = nfs_ucred_swap_effective(d->cred, &saved);
+ if (ret != 0) {
+ d->res_fd = -1;
+ d->res_error = ret;
+ return;
+ }
+
+ nfsd_openatfunc(data);
+
+ if (saved != NULL) {
+ nfs_ucred_swap_effective(saved, NULL);
+ nfs_ucred_free(saved);
+ }
+}
+
+int nfsd_cred_openat(const struct nfs_ucred *cred, int dirfd, const char *path,
+ int flags)
{
struct nfsd_openat_t open_buf = {
+ .cred = cred,
.path = path,
.dirfd = dirfd,
.flags = flags,
};
- nfsd_run_task(nfsd_openatfunc, &open_buf);
+ if (cred)
+ nfsd_run_task(nfsd_cred_openatfunc, &open_buf);
+ else
+ nfsd_run_task(nfsd_openatfunc, &open_buf);
if (open_buf.res_fd == -1)
errno = open_buf.res_error;
return open_buf.res_fd;
diff --git a/utils/mountd/mountd.c b/utils/mountd/mountd.c
index f43ebef5..6e6777cd 100644
--- a/utils/mountd/mountd.c
+++ b/utils/mountd/mountd.c
@@ -31,6 +31,7 @@
#include "nfsd_path.h"
#include "nfslib.h"
#include "export.h"
+#include "nfs_ucred.h"
extern void my_svc_run(void);
@@ -40,6 +41,7 @@ static struct nfs_fh_len *get_rootfh(struct svc_req *, dirpath *, nfs_export **,
int reverse_resolve = 0;
int manage_gids;
+int apply_root_cred;
int use_ipaddr = -1;
/* PRC: a high-availability callout program can be specified with -H
@@ -74,9 +76,10 @@ static struct option longopts[] =
{ "log-auth", 0, 0, 'l'},
{ "cache-use-ipaddr", 0, 0, 'i'},
{ "ttl", 1, 0, 'T'},
+ { "apply-root-cred", 0, 0, 'c' },
{ NULL, 0, 0, 0 }
};
-static char shortopts[] = "o:nFd:p:P:hH:N:V:vurs:t:gliT:";
+static char shortopts[] = "o:nFd:p:P:hH:N:V:vurs:t:gliT:c";
#define NFSVERSBIT(vers) (0x1 << (vers - 1))
#define NFSVERSBIT_ALL (NFSVERSBIT(2) | NFSVERSBIT(3) | NFSVERSBIT(4))
@@ -453,11 +456,27 @@ get_rootfh(struct svc_req *rqstp, dirpath *path, nfs_export **expret,
while (*subpath == '/')
subpath++;
if (*subpath != '\0') {
+ struct nfs_ucred *cred = NULL;
int fd;
+ /* Load the user cred */
+ if (!apply_root_cred) {
+ nfs_ucred_get(&cred, rqstp, &exp->m_export);
+ if (cred == NULL) {
+ xlog(L_WARNING, "can't retrieve credential");
+ *error = MNT3ERR_ACCES;
+ close(dirfd);
+ return NULL;
+ }
+ if (manage_gids)
+ nfs_ucred_reload_groups(cred, &exp->m_export);
+ }
+
/* Just perform a lookup of the path */
- fd = nfsd_openat(dirfd, subpath, O_PATH);
+ fd = nfsd_cred_openat(cred, dirfd, subpath, O_PATH);
close(dirfd);
+ if (cred)
+ nfs_ucred_free(cred);
if (fd == -1) {
xlog(L_WARNING, "can't open exported dir %s: %s", p,
strerror(errno));
@@ -681,6 +700,8 @@ read_mountd_conf(char **argv)
ttl = conf_get_num("mountd", "ttl", default_ttl);
if (ttl > 0)
default_ttl = ttl;
+ apply_root_cred = conf_get_bool("mountd", "apply-root-cred",
+ apply_root_cred);
}
int
@@ -794,6 +815,9 @@ main(int argc, char **argv)
}
default_ttl = ttl;
break;
+ case 'c':
+ apply_root_cred = 1;
+ break;
case 0:
break;
case '?':
diff --git a/utils/mountd/mountd.man b/utils/mountd/mountd.man
index a206a3e2..f4f1fc23 100644
--- a/utils/mountd/mountd.man
+++ b/utils/mountd/mountd.man
@@ -242,6 +242,32 @@ can support both NFS version 2 and the newer version 3.
Print the version of
.B rpc.mountd
and exit.
+.TP
+.B \-c " or " \-\-apply-root-cred
+When mountd is asked to allow a NFSv3 mount to a subdirectory of the
+exported directory, then it will check if the user asking to mount has
+lookup rights to the directories below that exported directory. When
+performing the check, mountd will apply any root squash or all squash
+rules that were specified for that client.
+
+Performing lookup checks as the user requires that the mountd daemon
+be run as root or that it be given CAP_SETUID and CAP_SETGID privileges
+so that it can change its own effective user and effective group settings.
+When troubleshooting, please also note that LSM frameworks such as SELinux
+can sometimes prevent the daemon from changing the effective user/groups
+despite the capability settings.
+
+In earlier versions of mountd, the same checks were performed using the
+mountd daemon's root privileges, meaning that it could authorise access
+to directories that are not normally accessible to the user requesting
+to mount them. This option enables that legacy behaviour.
+
+.BR Note:
+If there is a need to provide access to specific subdirectories that
+are not normally accessible to a client, it is always possible to add
+export entries that explicitly grant such access. That ability does
+not depend on this option being enabled.
+
.TP
.B \-g " or " \-\-manage-gids
Accept requests from the kernel to map user id numbers into lists of
--
2.35.6

View File

@@ -33,6 +33,12 @@ SRC_URI = "${KERNELORG_MIRROR}/linux/utils/nfs-utils/${PV}/nfs-utils-${PV}.tar.x
file://0001-locktest-Makefile.am-Do-not-use-build-flags.patch \
file://0001-tools-locktest-Use-intmax_t-to-print-off_t.patch \
file://0001-reexport.h-Include-unistd.h-to-compile-with-musl.patch \
file://CVE-2025-12801-dependent_p1.patch \
file://CVE-2025-12801-dependent_p2.patch \
file://CVE-2025-12801-dependent_p3.patch \
file://CVE-2025-12801-dependent_p4.patch \
file://CVE-2025-12801.patch \
file://CVE-2025-12801-build-fix.patch \
"
SRC_URI[sha256sum] = "01b3b0fb9c7d0bbabf5114c736542030748c788ec2fd9734744201e9b0a1119d"