diff --git a/SOURCES/nfs-utils-2.3.3-Fix-access-checks-when-mounting-subdirectories-in-NF.patch b/SOURCES/nfs-utils-2.3.3-Fix-access-checks-when-mounting-subdirectories-in-NF.patch new file mode 100644 index 0000000..6b41a2e --- /dev/null +++ b/SOURCES/nfs-utils-2.3.3-Fix-access-checks-when-mounting-subdirectories-in-NF.patch @@ -0,0 +1,218 @@ +From a0dc9ad489b016ba2d84d21d4f1a1b40df5b548e Mon Sep 17 00:00:00 2001 +From: Scott Mayhew +Date: Tue, 13 Jan 2026 14:30:22 -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. +--- + nfs.conf | 1 + + utils/mountd/mountd.c | 93 ++++++++++++++++++++++++++++++++++++++++- + utils/mountd/mountd.man | 26 ++++++++++++ + 3 files changed, 118 insertions(+), 2 deletions(-) + +diff --git a/nfs.conf b/nfs.conf +index 30f9e109..f5a3bb7c 100644 +--- a/nfs.conf ++++ b/nfs.conf +@@ -35,6 +35,7 @@ use-gss-proxy=1 + # + [mountd] + # debug=0 ++# apply-root-cred=n + # manage-gids=n + # descriptors=0 + # port=0 +diff --git a/utils/mountd/mountd.c b/utils/mountd/mountd.c +index 779f51b4..8e26bcb7 100644 +--- a/utils/mountd/mountd.c ++++ b/utils/mountd/mountd.c +@@ -31,6 +31,7 @@ + #include "pseudoflavors.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; + + struct state_paths etab; +@@ -77,9 +79,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)) +@@ -458,6 +461,71 @@ mount_mnt_3_svc(struct svc_req *rqstp, dirpath *path, mountres3 *res) + return 1; + } + ++/* ++ * NB: In the upstream code, all the nfsd_cred_openat() stuff is located ++ * nfsd_path.c, which doesn't existing in RHEL8, so I'm including it here. ++ * Also, these functions differ slightly from the upstream versions because ++ * RHEL8 lacks the workqueue mechanism, which was added to support chrooted ++ * threads (i.e. the rootdir option). ++ */ ++struct nfsd_openat_t { ++ const struct nfs_ucred *cred; ++ 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; ++} ++ ++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); ++ } ++} ++ ++static 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, ++ }; ++ ++ if (cred) ++ nfsd_cred_openatfunc(&open_buf); ++ else ++ nfsd_openatfunc(&open_buf); ++ if (open_buf.res_fd == -1) ++ errno = open_buf.res_error; ++ return open_buf.res_fd; ++} ++ + static struct nfs_fh_len * + get_rootfh(struct svc_req *rqstp, dirpath *path, nfs_export **expret, + mountstat3 *error, int v3) +@@ -527,11 +595,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 = 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)); +@@ -764,6 +848,8 @@ main(int argc, 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); + + /* Parse the command line options and arguments. */ + opterr = 0; +@@ -772,6 +858,9 @@ main(int argc, char **argv) + case 'g': + manage_gids = 1; + break; ++ case 'c': ++ apply_root_cred = 1; ++ break; + case 'o': + descriptors = atoi(optarg); + if (descriptors <= 0) { +diff --git a/utils/mountd/mountd.man b/utils/mountd/mountd.man +index 2a91e193..ac6d6d30 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.52.0 + diff --git a/SOURCES/nfs-utils-2.3.3-gssd-protect-kerberos-ticket-cache-access.patch b/SOURCES/nfs-utils-2.3.3-gssd-protect-kerberos-ticket-cache-access.patch new file mode 100644 index 0000000..af805fe --- /dev/null +++ b/SOURCES/nfs-utils-2.3.3-gssd-protect-kerberos-ticket-cache-access.patch @@ -0,0 +1,52 @@ +From 8600bbb7727df779ba1104c9f0c574b06be116a3 Mon Sep 17 00:00:00 2001 +From: Olga Kornievskaia +Date: Tue, 18 Nov 2025 10:23:27 -0500 +Subject: [nfs-utils PATCH] gssd: protect kerberos ticket cache access + +gssd_get_single_krb5_cred() is a function that's will (for when needed) +send a TGT request to the KDC and then store it in a credential cache. +If multiple threads (eg., parallel mounts) are making an upcall at the +same time then getting creds and storing creds need to be serialized due +to do kerberos API not being concurrency safe. + +Fixes: https://issues.redhat.com/browse/RHEL-103627 +Signed-off-by: Olga Kornievskaia +Signed-off-by: Steve Dickson +--- + utils/gssd/krb5_util.c | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/utils/gssd/krb5_util.c b/utils/gssd/krb5_util.c +index 09625fb9..137cffda 100644 +--- a/utils/gssd/krb5_util.c ++++ b/utils/gssd/krb5_util.c +@@ -456,12 +456,14 @@ gssd_get_single_krb5_cred(krb5_context context, + krb5_get_init_creds_opt_set_tkt_life(opts, 5*60); + #endif + ++ pthread_mutex_lock(&ple_lock); + if ((code = krb5_get_init_creds_opt_set_out_ccache(context, opts, + ccache))) { + k5err = gssd_k5_err_msg(context, code); + printerr(1, "WARNING: %s while initializing ccache for " + "principal '%s' using keytab '%s'\n", k5err, + pname ? pname : "", kt_name); ++ pthread_mutex_unlock(&ple_lock); + goto out; + } + if ((code = krb5_get_init_creds_keytab(context, &my_creds, ple->princ, +@@ -470,10 +472,10 @@ gssd_get_single_krb5_cred(krb5_context context, + printerr(1, "WARNING: %s while getting initial ticket for " + "principal '%s' using keytab '%s'\n", k5err, + pname ? pname : "", kt_name); ++ pthread_mutex_unlock(&ple_lock); + goto out; + } + +- pthread_mutex_lock(&ple_lock); + ple->endtime = my_creds.times.endtime; + pthread_mutex_unlock(&ple_lock); + +-- +2.52.0 + diff --git a/SOURCES/nfs-utils-2.3.3-mountd-Minor-refactor-of-get_rootfh.patch b/SOURCES/nfs-utils-2.3.3-mountd-Minor-refactor-of-get_rootfh.patch new file mode 100644 index 0000000..828ac36 --- /dev/null +++ b/SOURCES/nfs-utils-2.3.3-mountd-Minor-refactor-of-get_rootfh.patch @@ -0,0 +1,70 @@ +From 71baef7f9d3e00ed8a1ec38c791aea5854c93eaf Mon Sep 17 00:00:00 2001 +From: Scott Mayhew +Date: Tue, 13 Jan 2026 13:22:29 -0500 +Subject: [PATCH 1/4] mountd: Minor refactor of get_rootfh() + +Perform the mountpoint checks before checking the user path. +--- + utils/mountd/mountd.c | 32 ++++++++++++++++---------------- + 1 file changed, 16 insertions(+), 16 deletions(-) + +diff --git a/utils/mountd/mountd.c b/utils/mountd/mountd.c +index 2b342377..60f5e488 100644 +--- a/utils/mountd/mountd.c ++++ b/utils/mountd/mountd.c +@@ -488,6 +488,22 @@ get_rootfh(struct svc_req *rqstp, dirpath *path, nfs_export **expret, + *error = MNT3ERR_ACCES; + return NULL; + } ++ if (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 && ++ !is_mountpoint(exp->m_export.e_mountpoint[0]? ++ exp->m_export.e_mountpoint: ++ exp->m_export.e_path)) { ++ xlog(L_WARNING, "request to export an unmounted filesystem: %s", ++ p); ++ *error = MNT3ERR_NOENT; ++ return NULL; ++ } ++ + if (stat(p, &stb) < 0) { + xlog(L_WARNING, "can't stat exported dir %s: %s", + p, strerror(errno)); +@@ -502,12 +518,6 @@ get_rootfh(struct svc_req *rqstp, dirpath *path, nfs_export **expret, + *error = MNT3ERR_NOTDIR; + return NULL; + } +- if (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", +@@ -515,16 +525,6 @@ get_rootfh(struct svc_req *rqstp, dirpath *path, nfs_export **expret, + *error = MNT3ERR_ACCES; + return NULL; + } +- if (exp->m_export.e_mountpoint && +- !is_mountpoint(exp->m_export.e_mountpoint[0]? +- exp->m_export.e_mountpoint: +- exp->m_export.e_path)) { +- 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.52.0 + diff --git a/SOURCES/nfs-utils-2.3.3-mountd-Separate-lookup-of-the-exported-directory-and.patch b/SOURCES/nfs-utils-2.3.3-mountd-Separate-lookup-of-the-exported-directory-and.patch new file mode 100644 index 0000000..a763710 --- /dev/null +++ b/SOURCES/nfs-utils-2.3.3-mountd-Separate-lookup-of-the-exported-directory-and.patch @@ -0,0 +1,114 @@ +From c7d8779b71314afa5e6c529aae85da12ae250f70 Mon Sep 17 00:00:00 2001 +From: Scott Mayhew +Date: Tue, 13 Jan 2026 14:00:19 -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. +--- + utils/mountd/mountd.c | 61 ++++++++++++++++++++++++++++++++++++------- + 1 file changed, 52 insertions(+), 9 deletions(-) + +diff --git a/utils/mountd/mountd.c b/utils/mountd/mountd.c +index 60f5e488..779f51b4 100644 +--- a/utils/mountd/mountd.c ++++ b/utils/mountd/mountd.c +@@ -468,7 +468,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 = "/"; +@@ -488,12 +491,20 @@ get_rootfh(struct svc_req *rqstp, dirpath *path, nfs_export **expret, + *error = MNT3ERR_ACCES; + return NULL; + } +- if (stat(exp->m_export.e_path, &estb) < 0) { +- xlog(L_WARNING, "can't stat export point %s: %s", ++ dirfd = 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 && + !is_mountpoint(exp->m_export.e_mountpoint[0]? + exp->m_export.e_mountpoint: +@@ -501,18 +512,50 @@ 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 (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 = 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.52.0 + diff --git a/SOURCES/nfs-utils-2.3.3-nfsrahead-Modify-get_device_info-logic.patch b/SOURCES/nfs-utils-2.3.3-nfsrahead-Modify-get_device_info-logic.patch new file mode 100644 index 0000000..4b0e5a2 --- /dev/null +++ b/SOURCES/nfs-utils-2.3.3-nfsrahead-Modify-get_device_info-logic.patch @@ -0,0 +1,60 @@ +commit 162a0093a86411c4ce46d44660c57b98e4879701 +Author: Thiago Becker +Date: Wed Sep 10 08:21:47 2025 -0500 + + nfsrahead: modify get_device_info logic + + There are a few reports of failures by nfsrahead to set the read ahead + when the nfs mount information is not available when the udev event + fires. This was alleviated by retrying to read mountinfo multiple times, + but some cases where still failing to find the device information. To + further alleviate this issue, this patch adds a 50ms delay between + attempts. To not incur into unecessary delays, the logic in + get_device_info is reworked. + + While we are in this, remove a second loop of reptitions of + get_device_info. + + Signed-off-by: Thiago Becker + Signed-off-by: Steve Dickson + +diff --git a/tools/nfsrahead/main.c b/tools/nfsrahead/main.c +index 8a11cf1a..b7b889ff 100644 +--- a/tools/nfsrahead/main.c ++++ b/tools/nfsrahead/main.c +@@ -117,9 +117,11 @@ out_free_device_info: + + static int get_device_info(const char *device_number, struct device_info *device_info) + { +- int ret = ENOENT; +- for (int retry_count = 0; retry_count < 10 && ret != 0; retry_count++) ++ int ret = get_mountinfo(device_number, device_info, MOUNTINFO_PATH); ++ for (int retry_count = 0; retry_count < 5 && ret != 0; retry_count++) { ++ usleep(50000); + ret = get_mountinfo(device_number, device_info, MOUNTINFO_PATH); ++ } + + return ret; + } +@@ -135,7 +137,7 @@ static int conf_get_readahead(const char *kind) { + + int main(int argc, char **argv) + { +- int ret = 0, retry, opt; ++ int ret = 0, opt; + struct device_info device; + unsigned int readahead = 128, log_level, log_stderr = 0; + +@@ -163,11 +165,7 @@ int main(int argc, char **argv) + if ((argc - optind) != 1) + xlog_err("expected the device number of a BDI; is udev ok?"); + +- for (retry = 0; retry <= 10; retry++ ) +- if ((ret = get_device_info(argv[optind], &device)) == 0) +- break; +- +- if (ret != 0 || device.fstype == NULL) { ++ if ((ret = get_device_info(argv[optind], &device)) != 0 || device.fstype == NULL) { + xlog(D_GENERAL, "unable to find device %s\n", argv[optind]); + goto out; + } diff --git a/SOURCES/nfs-utils-2.3.3-support-Add-a-mini-library-to-extract-and-apply-RPC-.patch b/SOURCES/nfs-utils-2.3.3-support-Add-a-mini-library-to-extract-and-apply-RPC-.patch new file mode 100644 index 0000000..6514eb3 --- /dev/null +++ b/SOURCES/nfs-utils-2.3.3-support-Add-a-mini-library-to-extract-and-apply-RPC-.patch @@ -0,0 +1,456 @@ +From db822b7432a3fba65454276779d6c165247f68f0 Mon Sep 17 00:00:00 2001 +From: Scott Mayhew +Date: Tue, 13 Jan 2026 14:05:28 -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. +--- + aclocal/libtirpc.m4 | 11 +++ + support/include/Makefile.am | 1 + + support/include/nfs_ucred.h | 44 ++++++++++ + support/misc/Makefile.am | 3 +- + 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 27368ff2..b4128c95 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 599f500e..4d0e1e87 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 \ + nfslib.h \ + nfsrpc.h \ + nls.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 ++ ++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 8936b0d6..55099cba 100644 +--- a/support/misc/Makefile.am ++++ b/support/misc/Makefile.am +@@ -1,6 +1,7 @@ + ## Process this file with automake to produce Makefile.in + + noinst_LIBRARIES = libmisc.a +-libmisc_a_SOURCES = tcpwrapper.c from_local.c mountpoint.c file.c ++libmisc_a_SOURCES = tcpwrapper.c from_local.c mountpoint.c file.c \ ++ ucred.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 ++#endif ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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 67e3a8e1..d7ba72a7 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 + + libnfsconf_la_SOURCES = conffile.c xlog.c +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 ++#endif ++ ++#include ++#include ++#include ++#include ++ ++#include "exportfs.h" ++#include "nfs_ucred.h" ++ ++#ifdef HAVE_TIRPC_GSS_GETCRED ++#include ++#endif /* HAVE_TIRPC_GSS_GETCRED */ ++#ifdef HAVE_TIRPC_AUTHDES_GETUCRED ++#include ++#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.52.0 + diff --git a/SPECS/nfs-utils.spec b/SPECS/nfs-utils.spec index 62d3691..e35bf40 100644 --- a/SPECS/nfs-utils.spec +++ b/SPECS/nfs-utils.spec @@ -2,7 +2,7 @@ Summary: NFS utilities and supporting clients and daemons for the kernel NFS ser Name: nfs-utils URL: http://linux-nfs.org/ Version: 2.3.3 -Release: 64%{?dist} +Release: 68%{?dist} Epoch: 1 # group all 32bit related archs @@ -118,6 +118,12 @@ Patch060: nfs-utils-2.3.3-gssd-do-not-use-krb5_cc_initialize.patch Patch061: nfs-utils-2.3.3-nfsiostat-fixes.patch Patch062: nfs-utils-2.3.3-mountstats-fixes.patch Patch063: nfs-utils-2.3.3-nfs-man-rdirplus.patch +Patch064: nfs-utils-2.3.3-nfsrahead-Modify-get_device_info-logic.patch +Patch065: nfs-utils-2.3.3-gssd-protect-kerberos-ticket-cache-access.patch +Patch066: nfs-utils-2.3.3-mountd-Minor-refactor-of-get_rootfh.patch +Patch067: nfs-utils-2.3.3-mountd-Separate-lookup-of-the-exported-directory-and.patch +Patch068: nfs-utils-2.3.3-support-Add-a-mini-library-to-extract-and-apply-RPC-.patch +Patch069: nfs-utils-2.3.3-Fix-access-checks-when-mounting-subdirectories-in-NF.patch Patch100: nfs-utils-1.2.1-statdpath-man.patch Patch101: nfs-utils-1.2.1-exp-subtree-warn-off.patch @@ -161,6 +167,7 @@ Requires: libnfsidmap libevent Requires: libtirpc >= 0.2.3-1 libblkid libcap libmount %{?systemd_requires} Requires: gssproxy => 0.7.0-3 +Requires: (selinux-policy >= 3.14.3-139.el8_10.2 if selinux-policy) %package -n libnfsidmap Summary: NFSv4 User and Group ID Mapping Library @@ -396,6 +403,22 @@ fi %{_libdir}/libnfsidmap.so %changelog +* Fri Feb 27 2026 Scott Mayhew 2.3.3-68 +- Add requires for selinux-policy (RHEL-127095) + +* Fri Jan 30 2026 Scott Mayhew 2.3.3-67 +- mountd: Minor refactor of get_rootfh() (RHEL-127095) +- mountd: Separate lookup of the exported directory and the mount path (RHEL-127095) +- support: Add a mini-library to extract and apply RPC credentials (RHEL-127095) +- Fix access checks when mounting subdirectories in NFSv3 (RHEL-127095) + Resolves: CVE-2025-12801 + +* Fri Jan 9 2026 Scott Mayhew 2.3.3-66 +- gssd: protect kerberos ticket cache access (RHEL-103627) + +* Mon Dec 8 2025 Steve Dickson 2.3.3-65 +- nfsrahead: modify get_device_info logic (RHEL-108924) + * Tue May 20 2025 Scott Mayhew 2.3.3-64 - update rdirplus documentation on nfs(5) man page (RHEL-91253)