new file mode 100644
@@ -0,0 +1,80 @@
+From f642bba8c6c59352ab7259dc5321805cd6236638 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 1/4] 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: Deepak Rathore <deeratho@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.35.6
new file mode 100644
@@ -0,0 +1,180 @@
+From 105b2b59292de54565e27b4cb88e7b7e6ff855f5 Mon Sep 17 00:00:00 2001
+From: Trond Myklebust <trond.myklebust@hammerspace.com>
+Date: Mon, 10 Nov 2025 11:28:39 -0500
+Subject: [PATCH 2/4] mountd: Separate lookup of the exported directory and the
+ mount path
+
+When the caller asks to mount a path that does not terminate with an
+exported directory, we want to split up the lookups so that we can
+look up the exported directory using the mountd privileged credential,
+and the remaining subdirectory lookups using the RPC caller's
+credential.
+
+CVE: CVE-2025-12801
+Upstream-Status: Backport [https://git.linux-nfs.org/?p=steved/nfs-utils.git;a=commit;h=42f01e6a78fed98f12437ac8b28cfb12b6bad056]
+
+Reviewed-by: Jeff Layton <jlayton@kernel.org>
+Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
+Signed-off-by: Steve Dickson <steved@redhat.com>
+(cherry picked from commit 42f01e6a78fed98f12437ac8b28cfb12b6bad056)
+Signed-off-by: Deepak Rathore <deeratho@cisco.com>
+---
+ support/include/nfsd_path.h | 1 +
+ support/misc/nfsd_path.c | 31 ++++++++++++++++++
+ utils/mountd/mountd.c | 63 +++++++++++++++++++++++++++++++------
+ 3 files changed, 86 insertions(+), 9 deletions(-)
+
+diff --git a/support/include/nfsd_path.h b/support/include/nfsd_path.h
+index f600fb5a..3e5a2f5d 100644
+--- a/support/include/nfsd_path.h
++++ b/support/include/nfsd_path.h
+@@ -18,6 +18,7 @@ char * nfsd_path_prepend_dir(const char *dir, const char *pathname);
+
+ int nfsd_path_stat(const char *pathname, struct stat *statbuf);
+ int nfsd_path_lstat(const char *pathname, struct stat *statbuf);
++int nfsd_openat(int dirfd, const char *path, int flags);
+
+ int nfsd_path_statfs(const char *pathname,
+ struct statfs *statbuf);
+diff --git a/support/misc/nfsd_path.c b/support/misc/nfsd_path.c
+index caec33ca..dfe88e4f 100644
+--- a/support/misc/nfsd_path.c
++++ b/support/misc/nfsd_path.c
+@@ -203,6 +203,37 @@ nfsd_realpath(const char *path, char *resolved_buf)
+ return realpath_buf.res_ptr;
+ }
+
++struct nfsd_openat_t {
++ const char *path;
++ int dirfd;
++ int flags;
++ int res_fd;
++ int res_error;
++};
++
++static void nfsd_openatfunc(void *data)
++{
++ struct nfsd_openat_t *d = data;
++
++ d->res_fd = openat(d->dirfd, d->path, d->flags);
++ if (d->res_fd == -1)
++ d->res_error = errno;
++}
++
++int nfsd_openat(int dirfd, const char *path, int flags)
++{
++ struct nfsd_openat_t open_buf = {
++ .path = path,
++ .dirfd = dirfd,
++ .flags = flags,
++ };
++
++ nfsd_run_task(nfsd_openatfunc, &open_buf);
++ if (open_buf.res_fd == -1)
++ errno = open_buf.res_error;
++ return open_buf.res_fd;
++}
++
+ struct nfsd_rw_data {
+ int fd;
+ void* buf;
+diff --git a/utils/mountd/mountd.c b/utils/mountd/mountd.c
+index 39afd4aa..f43ebef5 100644
+--- a/utils/mountd/mountd.c
++++ b/utils/mountd/mountd.c
+@@ -392,7 +392,10 @@ get_rootfh(struct svc_req *rqstp, dirpath *path, nfs_export **expret,
+ struct nfs_fh_len *fh;
+ char rpath[MAXPATHLEN+1];
+ char *p = *path;
++ char *subpath;
+ char buf[INET6_ADDRSTRLEN];
++ size_t epathlen;
++ int dirfd;
+
+ if (*p == '\0')
+ p = "/";
+@@ -412,12 +415,21 @@ get_rootfh(struct svc_req *rqstp, dirpath *path, nfs_export **expret,
+ *error = MNT3ERR_ACCES;
+ return NULL;
+ }
+- if (nfsd_path_stat(exp->m_export.e_path, &estb) < 0) {
+- xlog(L_WARNING, "can't stat export point %s: %s",
++
++ dirfd = nfsd_openat(AT_FDCWD, exp->m_export.e_path, O_PATH);
++ if (dirfd == -1) {
++ xlog(L_WARNING, "can't open export point %s: %s",
+ p, strerror(errno));
+ *error = MNT3ERR_NOENT;
+ return NULL;
+ }
++ if (fstat(dirfd, &estb) == -1) {
++ xlog(L_WARNING, "can't stat export point %s: %s",
++ p, strerror(errno));
++ *error = MNT3ERR_ACCES;
++ close(dirfd);
++ return NULL;
++ }
+ if (exp->m_export.e_mountpoint &&
+ !check_is_mountpoint(exp->m_export.e_mountpoint[0]?
+ exp->m_export.e_mountpoint:
+@@ -426,18 +438,51 @@ get_rootfh(struct svc_req *rqstp, dirpath *path, nfs_export **expret,
+ xlog(L_WARNING, "request to export an unmounted filesystem: %s",
+ p);
+ *error = MNT3ERR_NOENT;
++ close(dirfd);
+ return NULL;
+ }
+
+- if (nfsd_path_stat(p, &stb) < 0) {
+- xlog(L_WARNING, "can't stat exported dir %s: %s",
+- p, strerror(errno));
+- if (errno == ENOENT)
+- *error = MNT3ERR_NOENT;
+- else
+- *error = MNT3ERR_ACCES;
++ epathlen = strlen(exp->m_export.e_path);
++ if (epathlen > strlen(p)) {
++ xlog(L_WARNING, "raced with change of exported path: %s", p);
++ *error = MNT3ERR_NOENT;
++ close(dirfd);
+ return NULL;
+ }
++ subpath = &p[epathlen];
++ while (*subpath == '/')
++ subpath++;
++ if (*subpath != '\0') {
++ int fd;
++
++ /* Just perform a lookup of the path */
++ fd = nfsd_openat(dirfd, subpath, O_PATH);
++ close(dirfd);
++ if (fd == -1) {
++ xlog(L_WARNING, "can't open exported dir %s: %s", p,
++ strerror(errno));
++ if (errno == ENOENT)
++ *error = MNT3ERR_NOENT;
++ else
++ *error = MNT3ERR_ACCES;
++ return NULL;
++ }
++ if (fstat(fd, &stb) == -1) {
++ xlog(L_WARNING, "can't open exported dir %s: %s", p,
++ strerror(errno));
++ if (errno == ENOENT)
++ *error = MNT3ERR_NOENT;
++ else
++ *error = MNT3ERR_ACCES;
++ close(fd);
++ return NULL;
++ }
++ close(fd);
++ } else {
++ close(dirfd);
++ stb = estb;
++ }
++
+ if (!S_ISDIR(stb.st_mode) && !S_ISREG(stb.st_mode)) {
+ xlog(L_WARNING, "%s is not a directory or regular file", p);
+ *error = MNT3ERR_NOTDIR;
+--
+2.35.6
new file mode 100644
@@ -0,0 +1,464 @@
+From 23e06d86004a8d5e3549e026183263584d056bc7 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 3/4] 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]
+
+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: Deepak Rathore <deeratho@cisco.com>
+---
+ aclocal/libtirpc.m4 | 12 +++
+ 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, 368 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 ef48a2ae..06629db9 100644
+--- a/aclocal/libtirpc.m4
++++ b/aclocal/libtirpc.m4
+@@ -31,6 +31,18 @@ AC_DEFUN([AC_LIBTIRPC], [
+ [AC_DEFINE([HAVE_TIRPC_GSS_SECCREATE], [1],
+ [Define to 1 if your tirpc library provides rpc_gss_seccreate])],,
+ [${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 f9993e3a..7ea2d798 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 file.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.35.6
new file mode 100644
@@ -0,0 +1,253 @@
+From d2e0fff6ad07e71d405ddbbe7eb3a07407c9a204 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 4/4] 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: Deepak Rathore <deeratho@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 3cca68c3..ddf0c143 100644
+--- a/nfs.conf
++++ b/nfs.conf
+@@ -46,6 +46,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
@@ -24,6 +24,10 @@ 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://0004-Use-nogroup-for-nobody-group.patch \
file://0005-find-OE-provided-Kerberos.patch \
+ file://CVE-2025-12801-depended_p1.patch \
+ file://CVE-2025-12801-depended_p2.patch \
+ file://CVE-2025-12801-depended_p3.patch \
+ file://CVE-2025-12801.patch \
"
SRC_URI[sha256sum] = "11c4cc598a434d7d340bad3e072a373ba1dcc2c49f855d44b202222b78ecdbf5"