| Message ID | 20260623102540.601753-1-sudumbha@cisco.com |
|---|---|
| State | New |
| Headers | show |
| Series | [scarthgap,v2] nfs-utils: fix CVE-2025-12801 | expand |
On Tue Jun 23, 2026 at 12:25 PM CEST, Sudhir Dumbhare via lists.openembedded.org wrote: > From: Sudhir Dumbhare <sudumbha@cisco.com> > > - 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 > > Signed-off-by: Sudhir Dumbhare <sudumbha@cisco.com> > --- > Changes v1 -> v2: > - Fully backport commit cd90f29257904f36509ea5a04a86f42398fbe94a for completeness. I understand that I need to drop the v1. What was the issue with the partial cd90f2925 commit? This is a really big CVE patch. Anything to simplify its review would be welcome. Regards, > > .../nfs-utils/CVE-2025-12801-build-fix.patch | 44 ++ > .../CVE-2025-12801-dependent_p1.patch | 450 +++++++++++++++++ > .../CVE-2025-12801-dependent_p2.patch | 81 +++ > .../CVE-2025-12801-dependent_p3.patch | 181 +++++++ > .../CVE-2025-12801-dependent_p4.patch | 468 ++++++++++++++++++ > .../nfs-utils/nfs-utils/CVE-2025-12801.patch | 254 ++++++++++ > .../nfs-utils/nfs-utils_2.6.4.bb | 6 + > 7 files changed, 1484 insertions(+) > create mode 100644 meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-build-fix.patch > create mode 100644 meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p1.patch > create mode 100644 meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p2.patch > create mode 100644 meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p3.patch > create mode 100644 meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p4.patch > create mode 100644 meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801.patch > > diff --git a/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-build-fix.patch b/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-build-fix.patch > new file mode 100644 > index 0000000000..d7aaca2242 > --- /dev/null > +++ b/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-build-fix.patch > @@ -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 > + > diff --git a/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p1.patch b/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p1.patch > new file mode 100644 > index 0000000000..c1fb7c2f12 > --- /dev/null > +++ b/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p1.patch > @@ -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 > + > diff --git a/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p2.patch b/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p2.patch > new file mode 100644 > index 0000000000..f088eadb4b > --- /dev/null > +++ b/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p2.patch > @@ -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 > + > diff --git a/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p3.patch b/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p3.patch > new file mode 100644 > index 0000000000..901069e3b9 > --- /dev/null > +++ b/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p3.patch > @@ -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 > + > diff --git a/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p4.patch b/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p4.patch > new file mode 100644 > index 0000000000..4ef529e737 > --- /dev/null > +++ b/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p4.patch > @@ -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 > + > diff --git a/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801.patch b/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801.patch > new file mode 100644 > index 0000000000..9f01604af0 > --- /dev/null > +++ b/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801.patch > @@ -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 > + > diff --git a/meta/recipes-connectivity/nfs-utils/nfs-utils_2.6.4.bb b/meta/recipes-connectivity/nfs-utils/nfs-utils_2.6.4.bb > index 2f2644f9a8..91c74fe5ef 100644 > --- a/meta/recipes-connectivity/nfs-utils/nfs-utils_2.6.4.bb > +++ b/meta/recipes-connectivity/nfs-utils/nfs-utils_2.6.4.bb > @@ -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" >
On Tue Jun 23, 2026 at 12:33 PM CEST, Yoann Congal wrote: > On Tue Jun 23, 2026 at 12:25 PM CEST, Sudhir Dumbhare via lists.openembedded.org wrote: >> From: Sudhir Dumbhare <sudumbha@cisco.com> >> >> - 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 >> >> Signed-off-by: Sudhir Dumbhare <sudumbha@cisco.com> >> --- >> Changes v1 -> v2: >> - Fully backport commit cd90f29257904f36509ea5a04a86f42398fbe94a for completeness. > > I understand that I need to drop the v1. > What was the issue with the partial cd90f2925 commit? Oh right: https://lore.kernel.org/all/c00944a7d36d2836e7dad02aa060e57d8ad5215f.camel@pbarker.dev/ Got it! > This is a really big CVE patch. Anything to simplify its review would be > welcome. > > Regards, >> >> .../nfs-utils/CVE-2025-12801-build-fix.patch | 44 ++ >> .../CVE-2025-12801-dependent_p1.patch | 450 +++++++++++++++++ >> .../CVE-2025-12801-dependent_p2.patch | 81 +++ >> .../CVE-2025-12801-dependent_p3.patch | 181 +++++++ >> .../CVE-2025-12801-dependent_p4.patch | 468 ++++++++++++++++++ >> .../nfs-utils/nfs-utils/CVE-2025-12801.patch | 254 ++++++++++ >> .../nfs-utils/nfs-utils_2.6.4.bb | 6 + >> 7 files changed, 1484 insertions(+) >> create mode 100644 meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-build-fix.patch >> create mode 100644 meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p1.patch >> create mode 100644 meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p2.patch >> create mode 100644 meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p3.patch >> create mode 100644 meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p4.patch >> create mode 100644 meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801.patch >> >> diff --git a/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-build-fix.patch b/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-build-fix.patch >> new file mode 100644 >> index 0000000000..d7aaca2242 >> --- /dev/null >> +++ b/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-build-fix.patch >> @@ -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 >> + >> diff --git a/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p1.patch b/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p1.patch >> new file mode 100644 >> index 0000000000..c1fb7c2f12 >> --- /dev/null >> +++ b/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p1.patch >> @@ -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 >> + >> diff --git a/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p2.patch b/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p2.patch >> new file mode 100644 >> index 0000000000..f088eadb4b >> --- /dev/null >> +++ b/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p2.patch >> @@ -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 >> + >> diff --git a/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p3.patch b/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p3.patch >> new file mode 100644 >> index 0000000000..901069e3b9 >> --- /dev/null >> +++ b/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p3.patch >> @@ -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 >> + >> diff --git a/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p4.patch b/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p4.patch >> new file mode 100644 >> index 0000000000..4ef529e737 >> --- /dev/null >> +++ b/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p4.patch >> @@ -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 >> + >> diff --git a/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801.patch b/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801.patch >> new file mode 100644 >> index 0000000000..9f01604af0 >> --- /dev/null >> +++ b/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801.patch >> @@ -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 >> + >> diff --git a/meta/recipes-connectivity/nfs-utils/nfs-utils_2.6.4.bb b/meta/recipes-connectivity/nfs-utils/nfs-utils_2.6.4.bb >> index 2f2644f9a8..91c74fe5ef 100644 >> --- a/meta/recipes-connectivity/nfs-utils/nfs-utils_2.6.4.bb >> +++ b/meta/recipes-connectivity/nfs-utils/nfs-utils_2.6.4.bb >> @@ -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" >>
diff --git a/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-build-fix.patch b/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-build-fix.patch new file mode 100644 index 0000000000..d7aaca2242 --- /dev/null +++ b/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-build-fix.patch @@ -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 + diff --git a/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p1.patch b/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p1.patch new file mode 100644 index 0000000000..c1fb7c2f12 --- /dev/null +++ b/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p1.patch @@ -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 + diff --git a/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p2.patch b/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p2.patch new file mode 100644 index 0000000000..f088eadb4b --- /dev/null +++ b/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p2.patch @@ -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 + diff --git a/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p3.patch b/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p3.patch new file mode 100644 index 0000000000..901069e3b9 --- /dev/null +++ b/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p3.patch @@ -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 + diff --git a/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p4.patch b/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p4.patch new file mode 100644 index 0000000000..4ef529e737 --- /dev/null +++ b/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801-dependent_p4.patch @@ -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 + diff --git a/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801.patch b/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801.patch new file mode 100644 index 0000000000..9f01604af0 --- /dev/null +++ b/meta/recipes-connectivity/nfs-utils/nfs-utils/CVE-2025-12801.patch @@ -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 + diff --git a/meta/recipes-connectivity/nfs-utils/nfs-utils_2.6.4.bb b/meta/recipes-connectivity/nfs-utils/nfs-utils_2.6.4.bb index 2f2644f9a8..91c74fe5ef 100644 --- a/meta/recipes-connectivity/nfs-utils/nfs-utils_2.6.4.bb +++ b/meta/recipes-connectivity/nfs-utils/nfs-utils_2.6.4.bb @@ -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"