From: Steve Dickson <steved@redhat.com>
To: Linux NFS Mailing list <linux-nfs@vger.kernel.org>
Subject: [PATCH 3/4] support: Add a mini-library to extract and apply RPC credentials
Date: Thu, 5 Mar 2026 10:59:47 -0500 [thread overview]
Message-ID: <20260305155948.11261-4-steved@redhat.com> (raw)
In-Reply-To: <20260305155948.11261-1-steved@redhat.com>
From: Trond Myklebust <trond.myklebust@hammerspace.com>
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.
Reviewed-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
Signed-off-by: Steve Dickson <steved@redhat.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.53.0
next prev parent reply other threads:[~2026-03-05 15:59 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-03-05 15:59 [PATCH 0/4] rpc.mountd CVE-2025-12801 announcement Steve Dickson
2026-03-05 15:59 ` [PATCH 1/4] mountd: Minor refactor of get_rootfh() Steve Dickson
2026-03-05 15:59 ` [PATCH 2/4] mountd: Separate lookup of the exported directory and the mount path Steve Dickson
2026-03-05 15:59 ` Steve Dickson [this message]
2026-03-05 15:59 ` [PATCH 4/4] Fix access checks when mounting subdirectories in NFSv3 Steve Dickson
2026-03-07 18:07 ` [PATCH 0/4] rpc.mountd CVE-2025-12801 announcement Steve Dickson
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260305155948.11261-4-steved@redhat.com \
--to=steved@redhat.com \
--cc=linux-nfs@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox