Compare commits

..

No commits in common. "c8" and "c8-beta" have entirely different histories.
c8 ... c8-beta

13 changed files with 1 additions and 1803 deletions

View File

@ -1,218 +0,0 @@
From a0dc9ad489b016ba2d84d21d4f1a1b40df5b548e Mon Sep 17 00:00:00 2001
From: Scott Mayhew <smayhew@redhat.com>
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

View File

@ -1,208 +0,0 @@
From ad4eafccd244e87af315c432d076b4f988dde52a Mon Sep 17 00:00:00 2001
From: Olga Kornievskaia <okorniev@redhat.com>
Date: Mon, 24 Mar 2025 08:43:43 -0400
Subject: [PATCH 2/2] gssd: do not use krb5_cc_initialize
Note: This patch differs from the upstream version in several places
because RHEL 8 does not have c8659457 ("gssd: We never use the nocache
param of gssd_check_if_cc_exists()") or f066f87b ("gssd: enable forcing
cred renewal using the keytab").
Original commit message:
When gssd refreshes machine credentials, it uses the
krb5_get_init_creds_keytab() and then to save the received credentials
in a ticket cache, it proceeds to initialize the credential cache via
a krb5_cc_initialize() before storing the received credentials into it.
krb5_cc_initialize() is not concurrency safe. two gssd upcalls by
uid=0, one for krb5i auth flavor and another for krb5p, would enter
into krb5_cc_initialize() and one of them would fail, leading to
an upcall failure and NFS operation error.
Instead it was proposed that gssd changes its design to do what
kinit does and forgo the use of krb5_cc_initialize and instead setup
the output cache via krb5_get_init_creds_opt_set_out_cache() prior
to calling krb5_get_init_creds_keytab() which would then store
credentials automatically.
https://mailman.mit.edu/pipermail/krbdev/2025-February/013708.html
Signed-off-by: Olga Kornievskaia <okorniev@redhat.com>
Signed-off-by: Steve Dickson <steved@redhat.com>
(cherry picked from commit 1cd9e3c0d290646e80750249914396566dd6b800)
Signed-off-by: Scott Mayhew <smayhew@redhat.com>
---
utils/gssd/krb5_util.c | 103 ++++++++++++++++++++---------------------
1 file changed, 50 insertions(+), 53 deletions(-)
diff --git a/utils/gssd/krb5_util.c b/utils/gssd/krb5_util.c
index 871add74..43bc8744 100644
--- a/utils/gssd/krb5_util.c
+++ b/utils/gssd/krb5_util.c
@@ -161,7 +161,8 @@ static int select_krb5_ccache(const struct dirent *d);
static int gssd_find_existing_krb5_ccache(uid_t uid, char *dirname,
const char **cctype, struct dirent **d);
static int gssd_get_single_krb5_cred(krb5_context context,
- krb5_keytab kt, struct gssd_k5_kt_princ *ple, int nocache);
+ krb5_keytab kt, struct gssd_k5_kt_princ *ple, int nocache,
+ krb5_ccache ccache);
static int query_krb5_ccache(const char* cred_cache, char **ret_princname,
char **ret_realm);
@@ -368,16 +369,14 @@ static int
gssd_get_single_krb5_cred(krb5_context context,
krb5_keytab kt,
struct gssd_k5_kt_princ *ple,
- int nocache)
+ int nocache,
+ krb5_ccache ccache)
{
krb5_get_init_creds_opt *opts = NULL;
krb5_creds my_creds;
- krb5_ccache ccache = NULL;
char kt_name[BUFSIZ];
- char cc_name[BUFSIZ];
int code;
time_t now = time(0);
- char *cache_type;
char *pname = NULL;
char *k5err = NULL;
pthread_t tid = pthread_self();
@@ -427,6 +426,14 @@ gssd_get_single_krb5_cred(krb5_context context,
krb5_get_init_creds_opt_set_tkt_life(opts, 5*60);
#endif
+ 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 : "<unparsable>", kt_name);
+ goto out;
+ }
if ((code = krb5_get_init_creds_keytab(context, &my_creds, ple->princ,
kt, 0, NULL, opts))) {
k5err = gssd_k5_err_msg(context, code);
@@ -436,61 +443,18 @@ gssd_get_single_krb5_cred(krb5_context context,
goto out;
}
- /*
- * Initialize cache file which we're going to be using
- */
-
pthread_mutex_lock(&ple_lock);
- if (use_memcache)
- cache_type = "MEMORY";
- else
- cache_type = "FILE";
- snprintf(cc_name, sizeof(cc_name), "%s:%s/%s%s_%s",
- cache_type,
- ccachesearch[0], GSSD_DEFAULT_CRED_PREFIX,
- GSSD_DEFAULT_MACHINE_CRED_SUFFIX, ple->realm);
ple->endtime = my_creds.times.endtime;
- if (ple->ccname == NULL || strcmp(ple->ccname, cc_name) != 0) {
- free(ple->ccname);
- ple->ccname = strdup(cc_name);
- if (ple->ccname == NULL) {
- printerr(0, "ERROR: no storage to duplicate credentials "
- "cache name '%s'\n", cc_name);
- code = ENOMEM;
- pthread_mutex_unlock(&ple_lock);
- goto out;
- }
- }
pthread_mutex_unlock(&ple_lock);
- if ((code = krb5_cc_resolve(context, cc_name, &ccache))) {
- k5err = gssd_k5_err_msg(context, code);
- printerr(0, "ERROR: %s while opening credential cache '%s'\n",
- k5err, cc_name);
- goto out;
- }
- if ((code = krb5_cc_initialize(context, ccache, ple->princ))) {
- k5err = gssd_k5_err_msg(context, code);
- printerr(0, "ERROR: %s while initializing credential "
- "cache '%s'\n", k5err, cc_name);
- goto out;
- }
- if ((code = krb5_cc_store_cred(context, ccache, &my_creds))) {
- k5err = gssd_k5_err_msg(context, code);
- printerr(0, "ERROR: %s while storing credentials in '%s'\n",
- k5err, cc_name);
- goto out;
- }
code = 0;
- printerr(2, "%s(0x%lx): principal '%s' ccache:'%s'\n",
- __func__, tid, pname, cc_name);
+ printerr(2, "%s(0x%lx): principal '%s' ccache:'%s'\n",
+ __func__, tid, pname, ple->ccname);
out:
if (opts)
krb5_get_init_creds_opt_free(context, opts);
if (pname)
k5_free_unparsed_name(context, pname);
- if (ccache)
- krb5_cc_close(context, ccache);
krb5_free_cred_contents(context, &my_creds);
krb5_free_string(context, k5err);
return (code);
@@ -1108,10 +1072,12 @@ gssd_refresh_krb5_machine_credential_internal(char *hostname,
{
krb5_error_code code = 0;
krb5_context context;
- krb5_keytab kt = NULL;;
+ krb5_keytab kt = NULL;
+ krb5_ccache ccache = NULL;
int retval = 0;
- char *k5err = NULL;
+ char *k5err = NULL, *cache_type;
const char *svcnames[] = { "$", "root", "nfs", "host", NULL };
+ char cc_name[BUFSIZ];
/*
* If a specific service name was specified, use it.
@@ -1170,7 +1136,38 @@ gssd_refresh_krb5_machine_credential_internal(char *hostname,
goto out_free_kt;
}
}
- retval = gssd_get_single_krb5_cred(context, kt, ple, 0);
+
+ if (use_memcache)
+ cache_type = "MEMORY";
+ else
+ cache_type = "FILE";
+ snprintf(cc_name, sizeof(cc_name), "%s:%s/%s%s_%s",
+ cache_type,
+ ccachesearch[0], GSSD_DEFAULT_CRED_PREFIX,
+ GSSD_DEFAULT_MACHINE_CRED_SUFFIX, ple->realm);
+
+ pthread_mutex_lock(&ple_lock);
+ if (ple->ccname == NULL || strcmp(ple->ccname, cc_name) != 0) {
+ free(ple->ccname);
+ ple->ccname = strdup(cc_name);
+ if (ple->ccname == NULL) {
+ printerr(0, "ERROR: no storage to duplicate credentials "
+ "cache name '%s'\n", cc_name);
+ code = ENOMEM;
+ pthread_mutex_unlock(&ple_lock);
+ goto out_free_kt;
+ }
+ }
+ pthread_mutex_unlock(&ple_lock);
+ if ((code = krb5_cc_resolve(context, cc_name, &ccache))) {
+ k5err = gssd_k5_err_msg(context, code);
+ printerr(0, "ERROR: %s while opening credential cache '%s'\n",
+ k5err, cc_name);
+ goto out_free_kt;
+ }
+
+ retval = gssd_get_single_krb5_cred(context, kt, ple, 0, ccache);
+ krb5_cc_close(context, ccache);
out_free_kt:
krb5_kt_close(context, kt);
out_free_context:
--
2.43.0

View File

@ -1,47 +0,0 @@
From 7511a77fc7eb7bd3ae38fcf54d49a47c25c3ed50 Mon Sep 17 00:00:00 2001
From: Scott Mayhew <smayhew@redhat.com>
Date: Mon, 24 Mar 2025 08:59:24 -0400
Subject: [nfs-utils PATCH] gssd.man: add documentation for use-gss-proxy
nfs.conf option
Signed-off-by: Scott Mayhew <smayhew@redhat.com>
Signed-off-by: Steve Dickson <steved@redhat.com>
---
utils/gssd/gssd.man | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/utils/gssd/gssd.man b/utils/gssd/gssd.man
index c735eff6..4a75b056 100644
--- a/utils/gssd/gssd.man
+++ b/utils/gssd/gssd.man
@@ -392,6 +392,17 @@ Setting to
is equivalent to providing the
.B -H
flag.
+.TP
+.B use-gss-proxy
+Setting this to 1 allows
+.BR gssproxy (8)
+to intercept GSSAPI calls and service them on behalf of
+.BR rpc.gssd ,
+enabling certain features such as keytab-based client initiation.
+Note that this is unrelated to the functionality that
+.BR gssproxy (8)
+provides on behalf of the NFS server. For more information, see
+.BR https://github.com/gssapi/gssproxy/blob/main/docs/NFS.md#nfs-client .
.P
In addtion, the following value is recognized from the
.B [general]
@@ -405,7 +416,8 @@ Equivalent to
.BR rpc.svcgssd (8),
.BR kerberos (1),
.BR kinit (1),
-.BR krb5.conf (5)
+.BR krb5.conf (5),
+.BR gssproxy (8)
.SH AUTHORS
.br
Dug Song <dugsong@umich.edu>
--
2.48.1

View File

@ -1,52 +0,0 @@
From 8600bbb7727df779ba1104c9f0c574b06be116a3 Mon Sep 17 00:00:00 2001
From: Olga Kornievskaia <okorniev@redhat.com>
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 <okorniev@redhat.com>
Signed-off-by: Steve Dickson <steved@redhat.com>
---
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 : "<unparsable>", 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 : "<unparsable>", 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

View File

@ -1,98 +0,0 @@
From 55d9bf151b100db9bf52e8f968e33f3ae1d234f5 Mon Sep 17 00:00:00 2001
From: Olga Kornievskaia <okorniev@redhat.com>
Date: Mon, 24 Mar 2025 08:40:32 -0400
Subject: [PATCH 1/2] gssd: unconditionally use krb5_get_init_creds_opt_alloc
Note: This patch has a context difference from the upstream version
because RHEL 8 does not have c8659457 ("gssd: We never use the nocache
param of gssd_check_if_cc_exists()") or f066f87b ("gssd: enable forcing
cred renewal using the keytab").
Original commit message:
Modern kerberos API uses krb5_get_init_creds_opt_alloc() for managing
its options for credential data structure.
Signed-off-by: Olga Kornievskaia <okorniev@redhat.com>
Signed-off-by: Steve Dickson <steved@redhat.com>
(cherry picked from commit 9b3f949331c6541a358fc28bac323533f94d7e0b)
Signed-off-by: Scott Mayhew <smayhew@redhat.com>
---
utils/gssd/krb5_util.c | 37 ++++++++++---------------------------
1 file changed, 10 insertions(+), 27 deletions(-)
diff --git a/utils/gssd/krb5_util.c b/utils/gssd/krb5_util.c
index a1a77a2f..871add74 100644
--- a/utils/gssd/krb5_util.c
+++ b/utils/gssd/krb5_util.c
@@ -370,12 +370,7 @@ gssd_get_single_krb5_cred(krb5_context context,
struct gssd_k5_kt_princ *ple,
int nocache)
{
-#ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ADDRESSLESS
- krb5_get_init_creds_opt *init_opts = NULL;
-#else
- krb5_get_init_creds_opt options;
-#endif
- krb5_get_init_creds_opt *opts;
+ krb5_get_init_creds_opt *opts = NULL;
krb5_creds my_creds;
krb5_ccache ccache = NULL;
char kt_name[BUFSIZ];
@@ -413,33 +408,23 @@ gssd_get_single_krb5_cred(krb5_context context,
if ((krb5_unparse_name(context, ple->princ, &pname)))
pname = NULL;
-#ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ADDRESSLESS
- code = krb5_get_init_creds_opt_alloc(context, &init_opts);
+ code = krb5_get_init_creds_opt_alloc(context, &opts);
if (code) {
k5err = gssd_k5_err_msg(context, code);
printerr(0, "ERROR: %s allocating gic options\n", k5err);
goto out;
}
- if (krb5_get_init_creds_opt_set_addressless(context, init_opts, 1))
+#ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ADDRESSLESS
+ if (krb5_get_init_creds_opt_set_addressless(context, opts, 1))
printerr(1, "WARNING: Unable to set option for addressless "
"tickets. May have problems behind a NAT.\n");
-#ifdef TEST_SHORT_LIFETIME
- /* set a short lifetime (for debugging only!) */
- printerr(1, "WARNING: Using (debug) short machine cred lifetime!\n");
- krb5_get_init_creds_opt_set_tkt_life(init_opts, 5*60);
+#else
+ krb5_get_init_creds_opt_set_address_list(opts, NULL);
#endif
- opts = init_opts;
-
-#else /* HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ADDRESSLESS */
-
- krb5_get_init_creds_opt_init(&options);
- krb5_get_init_creds_opt_set_address_list(&options, NULL);
#ifdef TEST_SHORT_LIFETIME
/* set a short lifetime (for debugging only!) */
- printerr(0, "WARNING: Using (debug) short machine cred lifetime!\n");
- krb5_get_init_creds_opt_set_tkt_life(&options, 5*60);
-#endif
- opts = &options;
+ printerr(1, "WARNING: Using (debug) short machine cred lifetime!\n");
+ krb5_get_init_creds_opt_set_tkt_life(opts, 5*60);
#endif
if ((code = krb5_get_init_creds_keytab(context, &my_creds, ple->princ,
@@ -500,10 +485,8 @@ gssd_get_single_krb5_cred(krb5_context context,
printerr(2, "%s(0x%lx): principal '%s' ccache:'%s'\n",
__func__, tid, pname, cc_name);
out:
-#ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ADDRESSLESS
- if (init_opts)
- krb5_get_init_creds_opt_free(context, init_opts);
-#endif
+ if (opts)
+ krb5_get_init_creds_opt_free(context, opts);
if (pname)
k5_free_unparsed_name(context, pname);
if (ccache)
--
2.43.0

View File

@ -1,70 +0,0 @@
From 71baef7f9d3e00ed8a1ec38c791aea5854c93eaf Mon Sep 17 00:00:00 2001
From: Scott Mayhew <smayhew@redhat.com>
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

View File

@ -1,114 +0,0 @@
From c7d8779b71314afa5e6c529aae85da12ae250f70 Mon Sep 17 00:00:00 2001
From: Scott Mayhew <smayhew@redhat.com>
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

View File

@ -1,141 +0,0 @@
diff --git a/tools/mountstats/mountstats.py b/tools/mountstats/mountstats.py
index 8e129c83..d488f9e1 100755
--- a/tools/mountstats/mountstats.py
+++ b/tools/mountstats/mountstats.py
@@ -333,6 +333,11 @@ class DeviceData:
found = True
self.__parse_rpc_line(words)
+ def fstype(self):
+ """Return the fstype for the mountpoint
+ """
+ return self.__nfs_data['fstype']
+
def is_nfs_mountpoint(self):
"""Return True if this is an NFS or NFSv4 mountpoint,
otherwise return False
@@ -953,73 +958,78 @@ def nfsstat_command(args):
return 0
def print_iostat_summary(old, new, devices, time):
+ if len(devices) == 0:
+ print('No NFS mount points were found')
+ return
+
for device in devices:
stats = DeviceData()
stats.parse_stats(new[device])
- if not old or device not in old:
+ if old and device in old:
+ old_stats = DeviceData()
+ old_stats.parse_stats(old[device])
+ if stats.fstype() == old_stats.fstype():
+ stats.compare_iostats(old_stats).display_iostats(time)
+ else: # device is in old, but fstypes are different
+ stats.display_iostats(time)
+ else: # device is only in new
stats.display_iostats(time)
- else:
- if ("fstype autofs" not in str(old[device])) and ("fstype autofs" not in str(new[device])):
- old_stats = DeviceData()
- old_stats.parse_stats(old[device])
- diff_stats = stats.compare_iostats(old_stats)
- diff_stats.display_iostats(time)
+
+def list_nfs_mounts(givenlist, mountstats):
+ """return a list of NFS mounts given a list to validate or
+ return a full list if the given list is empty -
+ may return an empty list if none found
+ """
+ devicelist = []
+ if len(givenlist) > 0:
+ for device in givenlist:
+ if device in mountstats:
+ stats = DeviceData()
+ stats.parse_stats(mountstats[device])
+ if stats.is_nfs_mountpoint():
+ devicelist += [device]
+ else:
+ for device, descr in mountstats.items():
+ stats = DeviceData()
+ stats.parse_stats(descr)
+ if stats.is_nfs_mountpoint():
+ devicelist += [device]
+ return devicelist
def iostat_command(args):
"""iostat-like command for NFS mount points
"""
mountstats = parse_stats_file(args.infile)
- devices = [os.path.normpath(mp) for mp in args.mountpoints]
+ origdevices = [os.path.normpath(mp) for mp in args.mountpoints]
if args.since:
old_mountstats = parse_stats_file(args.since)
else:
old_mountstats = None
- # make certain devices contains only NFS mount points
- if len(devices) > 0:
- check = []
- for device in devices:
- stats = DeviceData()
- try:
- stats.parse_stats(mountstats[device])
- if stats.is_nfs_mountpoint():
- check += [device]
- except KeyError:
- continue
- devices = check
- else:
- for device, descr in mountstats.items():
- stats = DeviceData()
- stats.parse_stats(descr)
- if stats.is_nfs_mountpoint():
- devices += [device]
- if len(devices) == 0:
- print('No NFS mount points were found')
- return 1
-
sample_time = 0
+ # make certain devices contains only NFS mount points
+ devices = list_nfs_mounts(origdevices, mountstats)
+ print_iostat_summary(old_mountstats, mountstats, devices, sample_time)
+
if args.interval is None:
- print_iostat_summary(old_mountstats, mountstats, devices, sample_time)
return
- if args.count is not None:
- count = args.count
- while count != 0:
- print_iostat_summary(old_mountstats, mountstats, devices, sample_time)
- old_mountstats = mountstats
- time.sleep(args.interval)
- sample_time = args.interval
- mountstats = parse_stats_file(args.infile)
+ count = args.count
+ while True:
+ if count is not None:
count -= 1
- else:
- while True:
- print_iostat_summary(old_mountstats, mountstats, devices, sample_time)
- old_mountstats = mountstats
- time.sleep(args.interval)
- sample_time = args.interval
- mountstats = parse_stats_file(args.infile)
+ if count == 0:
+ break
+ time.sleep(args.interval)
+ old_mountstats = mountstats
+ sample_time = args.interval
+ mountstats = parse_stats_file(args.infile)
+ # nfs mountpoints may appear or disappear, so we need to
+ # recheck the devices list each time we parse mountstats
+ devices = list_nfs_mounts(origdevices, mountstats)
+ print_iostat_summary(old_mountstats, mountstats, devices, sample_time)
args.infile.close()
if args.since:

View File

@ -1,47 +0,0 @@
From 3cca9dbeab3b159c27c1dc48e791139744baf6d0 Mon Sep 17 00:00:00 2001
From: Benjamin Coddington <bcodding@redhat.com>
Date: Mon, 24 Mar 2025 15:47:43 -0400
Subject: [nfs-utils PATCH] nfs(5): Add new rdirplus functionality, clarify
The proposed kernel [patch][1] will modify the rdirplus mount option to
accept optional string values of "none" and "force". Update the man page
to reflect these changes and clarify the current client's behavior for the
default.
[1]: https://lore.kernel.org/linux-nfs/8c33cd92be52255b0dd0a7489c9e5cc35434ec95.1741876784.git.bcodding@redhat.com/T/#u
Signed-off-by: Benjamin Coddington <bcodding@redhat.com>
Signed-off-by: Steve Dickson <steved@redhat.com>
---
utils/mount/nfs.man | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)
diff --git a/utils/mount/nfs.man b/utils/mount/nfs.man
index b5c5913b..94dc29d4 100644
--- a/utils/mount/nfs.man
+++ b/utils/mount/nfs.man
@@ -434,11 +434,16 @@ option may also be used by some pNFS drivers to decide how many
connections to set up to the data servers.
.TP 1.5i
.BR rdirplus " / " nordirplus
-Selects whether to use NFS v3 or v4 READDIRPLUS requests.
-If this option is not specified, the NFS client uses READDIRPLUS requests
-on NFS v3 or v4 mounts to read small directories.
-Some applications perform better if the client uses only READDIR requests
-for all directories.
+Selects whether to use NFS v3 or v4 READDIRPLUS requests. If this option is
+not specified, the NFS client uses a heuristic to optimize performance by
+choosing READDIR vs READDIRPLUS based on how often the calling process uses
+the additional attributes returned from READDIRPLUS. Some applications
+perform better if the client uses only READDIR requests for all directories.
+.TP 1.5i
+.BR rdirplus={none|force}
+If set to "force", the NFS client always attempts to use READDIRPLUS
+requests. If set to "none", the behavior is the same as
+.B nordirplus.
.TP 1.5i
.BI retry= n
The number of minutes that the
--
2.48.1

View File

@ -1,242 +0,0 @@
26f8410d mountstats/nfsiostat: merge and rework the infinite and counted loops
21238e5c mountstats/nfsiostat: Move the checks for empty mountpoint list into the print function
1bd30a9d nfsiostat: make comment explain mount/unmount more broadly
2b9251ed nfsiostat: fix crash when filtering mountstats after unmount
9836adbc nfsiostat: mirror how mountstats iostat prints the stats
936a19cc mountstats/nfsiostat: add a function to return the fstype
155c5343 nfsiostat: skip argv[0] when parsing command-line
39f57998 nfsiostat/mountstats: handle KeyError in compare_iostats()
c4c14011 nfsiostat: replace 'list' reserved word
diff --git a/tools/nfs-iostat/nfs-iostat.py b/tools/nfs-iostat/nfs-iostat.py
index 7cbe543c..dec0e861 100644
--- a/tools/nfs-iostat/nfs-iostat.py
+++ b/tools/nfs-iostat/nfs-iostat.py
@@ -493,20 +493,20 @@ def list_nfs_mounts(givenlist, mountstats):
return a full list if the given list is empty -
may return an empty list if none found
"""
- list = []
+ devicelist = []
if len(givenlist) > 0:
for device in givenlist:
stats = DeviceData()
stats.parse_stats(mountstats[device])
if stats.is_nfs_mountpoint():
- list += [device]
+ devicelist += [device]
else:
for device, descr in mountstats.items():
stats = DeviceData()
stats.parse_stats(descr)
if stats.is_nfs_mountpoint():
- list += [device]
- return list
+ devicelist += [device]
+ return devicelist
def iostat_command(name):
"""iostat-like command for NFS mount points
diff --git a/tools/mountstats/mountstats.py b/tools/mountstats/mountstats.py
index 014f38a3..1054f698 100755
--- a/tools/mountstats/mountstats.py
+++ b/tools/mountstats/mountstats.py
@@ -560,7 +560,10 @@ class DeviceData:
# the reference to them. so we build new lists here
# for the result object.
for op in result.__rpc_data['ops']:
- result.__rpc_data[op] = list(map(difference, self.__rpc_data[op], old_stats.__rpc_data[op]))
+ try:
+ result.__rpc_data[op] = list(map(difference, self.__rpc_data[op], old_stats.__rpc_data[op]))
+ except KeyError:
+ continue
# update the remaining keys
if protocol == 'udp':
diff --git a/tools/nfs-iostat/nfs-iostat.py b/tools/nfs-iostat/nfs-iostat.py
index b7e98a2a..5556f692 100755
--- a/tools/nfs-iostat/nfs-iostat.py
+++ b/tools/nfs-iostat/nfs-iostat.py
@@ -213,8 +213,11 @@ class DeviceData:
# the reference to them. so we build new lists here
# for the result object.
for op in result.__rpc_data['ops']:
- result.__rpc_data[op] = list(map(
- difference, self.__rpc_data[op], old_stats.__rpc_data[op]))
+ try:
+ result.__rpc_data[op] = list(map(
+ difference, self.__rpc_data[op], old_stats.__rpc_data[op]))
+ except KeyError:
+ continue
# update the remaining keys we care about
result.__rpc_data['rpcsends'] -= old_stats.__rpc_data['rpcsends']
diff --git a/tools/nfs-iostat/nfs-iostat.py b/tools/nfs-iostat/nfs-iostat.py
index 85294fb9..e46b1a83 100755
--- a/tools/nfs-iostat/nfs-iostat.py
+++ b/tools/nfs-iostat/nfs-iostat.py
@@ -187,6 +187,11 @@ class DeviceData:
found = True
self.__parse_rpc_line(words)
+ def fstype(self):
+ """Return the fstype for the mountpoint
+ """
+ return self.__nfs_data['fstype']
+
def is_nfs_mountpoint(self):
"""Return True if this is an NFS or NFSv4 mountpoint,
otherwise return False
@@ -471,41 +476,31 @@ def parse_stats_file(filename):
return ms_dict
def print_iostat_summary(old, new, devices, time, options):
- stats = {}
- diff_stats = {}
- devicelist = []
- if old:
- # Trim device list to only include intersection of old and new data,
- # this addresses umounts due to autofs mountpoints
- for device in devices:
- if "fstype autofs" not in str(old[device]):
- devicelist.append(device)
- else:
- devicelist = devices
+ display_stats = {}
+
+ if len(devices) == 0:
+ print('No NFS mount points were found')
+ return
- for device in devicelist:
- stats[device] = DeviceData()
- stats[device].parse_stats(new[device])
- if old:
+ for device in devices:
+ stats = DeviceData()
+ stats.parse_stats(new[device])
+ if old and device in old:
old_stats = DeviceData()
old_stats.parse_stats(old[device])
- diff_stats[device] = stats[device].compare_iostats(old_stats)
+ if stats.fstype() == old_stats.fstype():
+ display_stats[device] = stats.compare_iostats(old_stats)
+ else: # device is in old, but fstypes are different
+ display_stats[device] = stats
+ else: # device is only in new
+ display_stats[device] = stats
if options.sort:
- if old:
- # We now have compared data and can print a comparison
- # ordered by mountpoint ops per second
- devicelist.sort(key=lambda x: diff_stats[x].ops(time), reverse=True)
- else:
- # First iteration, just sort by newly parsed ops/s
- devicelist.sort(key=lambda x: stats[x].ops(time), reverse=True)
+ devices.sort(key=lambda x: display_stats[x].ops(time), reverse=True)
count = 1
- for device in devicelist:
- if old:
- diff_stats[device].display_iostats(time, options.which)
- else:
- stats[device].display_iostats(time, options.which)
+ for device in devices:
+ display_stats[device].display_iostats(time, options.which)
count += 1
if (count > options.list):
@@ -520,10 +515,11 @@ def list_nfs_mounts(givenlist, mountstats):
devicelist = []
if len(givenlist) > 0:
for device in givenlist:
- stats = DeviceData()
- stats.parse_stats(mountstats[device])
- if stats.is_nfs_mountpoint():
- devicelist += [device]
+ if device in mountstats:
+ stats = DeviceData()
+ stats.parse_stats(mountstats[device])
+ if stats.is_nfs_mountpoint():
+ devicelist += [device]
else:
for device, descr in mountstats.items():
stats = DeviceData()
@@ -592,11 +588,7 @@ client are listed.
parser.add_option_group(displaygroup)
(options, args) = parser.parse_args(sys.argv)
- for arg in args:
-
- if arg == sys.argv[0]:
- continue
-
+ for arg in args[1:]:
if arg in mountstats:
origdevices += [arg]
elif not interval_seen:
@@ -622,47 +614,29 @@ client are listed.
print('Illegal <count> value %s' % arg)
return
- # make certain devices contains only NFS mount points
- devices = list_nfs_mounts(origdevices, mountstats)
- if len(devices) == 0:
- print('No NFS mount points were found')
- return
-
-
old_mountstats = None
sample_time = 0.0
+ # make certain devices contains only NFS mount points
+ devices = list_nfs_mounts(origdevices, mountstats)
+ print_iostat_summary(old_mountstats, mountstats, devices, sample_time, options)
+
if not interval_seen:
- print_iostat_summary(old_mountstats, mountstats, devices, sample_time, options)
return
- if count_seen:
- while count != 0:
- print_iostat_summary(old_mountstats, mountstats, devices, sample_time, options)
- old_mountstats = mountstats
- time.sleep(interval)
- sample_time = interval
- mountstats = parse_stats_file('/proc/self/mountstats')
- # automount mountpoints add and drop, if automount is involved
- # we need to recheck the devices list when reparsing
- devices = list_nfs_mounts(origdevices,mountstats)
- if len(devices) == 0:
- print('No NFS mount points were found')
- return
+ while True:
+ if count_seen:
count -= 1
- else:
- while True:
- print_iostat_summary(old_mountstats, mountstats, devices, sample_time, options)
- old_mountstats = mountstats
- time.sleep(interval)
- sample_time = interval
- mountstats = parse_stats_file('/proc/self/mountstats')
- # automount mountpoints add and drop, if automount is involved
- # we need to recheck the devices list when reparsing
- devices = list_nfs_mounts(origdevices,mountstats)
- if len(devices) == 0:
- print('No NFS mount points were found')
- return
+ if count == 0:
+ break
+ time.sleep(interval)
+ old_mountstats = mountstats
+ sample_time = interval
+ mountstats = parse_stats_file('/proc/self/mountstats')
+ # nfs mountpoints may appear or disappear, so we need to
+ # recheck the devices list each time we parse mountstats
+ devices = list_nfs_mounts(origdevices, mountstats)
+ print_iostat_summary(old_mountstats, mountstats, devices, sample_time, options)
#
# Main

View File

@ -1,60 +0,0 @@
commit 162a0093a86411c4ce46d44660c57b98e4879701
Author: Thiago Becker <tbecker@redhat.com>
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 <tbecker@redhat.com>
Signed-off-by: Steve Dickson <steved@redhat.com>
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;
}

View File

@ -1,456 +0,0 @@
From db822b7432a3fba65454276779d6c165247f68f0 Mon Sep 17 00:00:00 2001
From: Scott Mayhew <smayhew@redhat.com>
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 <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 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 <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 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 <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.52.0

View File

@ -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: 68%{?dist}
Release: 59%{?dist}
Epoch: 1
# group all 32bit related archs
@ -109,22 +109,6 @@ Patch055: nfs-utils-2.3.3-systemd-rpcstatd.patch
Patch056: nfs-utils-2.3.3-mountd-v4clnts.patch
Patch057: nfs-utils-2.3.3-covscan-return-value.patch
#
# RHEL 8.10.z
#
Patch058: nfs-utils-2.3.3-gssd-man-document-use-gss-proxy.patch
Patch059: nfs-utils-2.3.3-gssd-unconditionally-use-krb5_get_init_creds_opt_all.patch
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
Patch102: nfs-utils-2.3.3-idmap-errmsg.patch
@ -167,7 +151,6 @@ 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
@ -403,38 +386,6 @@ fi
%{_libdir}/libnfsidmap.so
%changelog
* Fri Feb 27 2026 Scott Mayhew <smayhew@redhat.com> 2.3.3-68
- Add requires for selinux-policy (RHEL-127095)
* Fri Jan 30 2026 Scott Mayhew <smayhew@redhat.com> 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 <smayhew@redhat.com> 2.3.3-66
- gssd: protect kerberos ticket cache access (RHEL-103627)
* Mon Dec 8 2025 Steve Dickson <steved@redhat.com> 2.3.3-65
- nfsrahead: modify get_device_info logic (RHEL-108924)
* Tue May 20 2025 Scott Mayhew <smayhew@redhat.com> 2.3.3-64
- update rdirplus documentation on nfs(5) man page (RHEL-91253)
* Fri May 9 2025 Scott Mayhew <smayhew@redhat.com> 2.3.3-63
- mountstats fixes (RHEL-90242)
* Thu May 8 2025 Scott Mayhew <smayhew@redhat.com> 2.3.3-62
- nfsiostat fixes (RHEL-90242)
* Mon Apr 28 2025 Scott Mayhew <smayhew@redhat.com> 2.3.3-61
- gssd: unconditionally use krb5_get_init_creds_opt_alloc (RHEL-62422)
- gssd: do not use krb5_cc_initialize (RHEL-62422)
* Tue Apr 15 2025 Scott Mayhew <smayhew@redhat.com> 2.3.3-60
- gssd.man: add documentation for use-gss-proxy (RHEL-13085)
* Thu Jan 12 2023 Steve Dickson <steved@redhat.com> 2.3.3-59
- Covscan Scan: Wrong Check of Return Value (bz 2151966)
- Covscan Scan: Clang (experimental) (bz 2151971)