Linux NFS development
 help / color / mirror / Atom feed
* [PATCH 0/5] NFSD: Apply export security when a compound crosses exports
@ 2026-11-03 19:52 Chuck Lever
  2026-11-03 19:52 ` [PATCH 1/5] NFSD: Check the secure-port policy when entering an export Chuck Lever
                   ` (5 more replies)
  0 siblings, 6 replies; 8+ messages in thread
From: Chuck Lever @ 2026-11-03 19:52 UTC (permalink / raw)
  To: VeroFess, Jeff Layton, NeilBrown, Olga Kornievskaia, Dai Ngo,
	Tom Talpey, Al Viro
  Cc: linux-nfs, Chuck Lever

VeroFess reported that a compound can move the current filehandle
into another export without NFSD applying all of that export's
policy:

  https://patch.msgid.link/20261103013237.1174538-1-coderzeng@gmail.com

fh_verify() is where NFSD checks an export's secure-port setting and
installs the credentials its squash options map a request to. A
LOOKUP that crosses a mountpoint, RESTOREFH, and the saved filehandle
that COPY and CLONE consume do not go through fh_verify(). An
operation that then uses an OPEN stateid, such as READ, CLONE, or
COPY, resolves the stateid without calling fh_verify() either. The
request runs under the previous export's port policy and credential
mapping. The requester must already hold an OPEN stateid that NFSD
granted after a full check, so the exposure is a client that keeps a
stateid across a change of port or of export options.

Rather than repeat fh_verify() at every crossing, this series makes
the crossing path itself complete. check_nfsd_access() gains the port
check, so a check added there covers LOOKUP, LOOKUPP, and the COPY
source alongside PUTFH. nfsd_setuser() runs after a crossing and
after RESTOREFH, and once for each side of a cross-export COPY or
CLONE.

---
Chuck Lever (5):
      NFSD: Check the secure-port policy when entering an export
      NFSD: Defer the PUTFH secure-port check
      NFSD: Report WRONGSEC from PUTFH when OPEN uses the current filehandle
      NFSD: Apply the entered export's credentials after LOOKUP and RESTOREFH
      NFSD: Use each export's credentials for the COPY and CLONE stateids

 fs/nfsd/export.c   | 24 ++++++++++++++------
 fs/nfsd/export.h   |  3 ++-
 fs/nfsd/nfs4proc.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++-------
 fs/nfsd/nfsfh.c    | 45 +++++++++++++++++++++++++++++--------
 fs/nfsd/nfsfh.h    |  2 ++
 fs/nfsd/trace.h    |  3 ++-
 fs/nfsd/vfs.c      | 17 +++++++++-----
 fs/nfsd/vfs.h      |  1 +
 8 files changed, 128 insertions(+), 32 deletions(-)
---
base-commit: 693afd3ce9ae5cddb7225ec87975e6ef1443e502
change-id: 20261103-verofess-2-3101d53cfc18

Best regards,
--  
Chuck Lever <cel@kernel.org>


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH 1/5] NFSD: Check the secure-port policy when entering an export
  2026-11-03 19:52 [PATCH 0/5] NFSD: Apply export security when a compound crosses exports Chuck Lever
@ 2026-11-03 19:52 ` Chuck Lever
  2026-11-03 19:52 ` [PATCH 2/5] NFSD: Defer the PUTFH secure-port check Chuck Lever
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 8+ messages in thread
From: Chuck Lever @ 2026-11-03 19:52 UTC (permalink / raw)
  To: VeroFess, Jeff Layton, NeilBrown, Olga Kornievskaia, Dai Ngo,
	Tom Talpey, Al Viro
  Cc: linux-nfs, Chuck Lever

When LOOKUP or LOOKUPP crosses into another export, check_nfsd_access()
checks the xprtsec policy and security flavor of the entered export but
skips the secure-port setting. An AUTH_SYS request from an insecure port
can therefore enter a secure export by LOOKUP, though PUTFH of the same
filehandle returns NFS4ERR_PERM. A client that obtained an OPEN stateid
for a file in that export from a privileged port can then READ or CLONE
the file from an insecure port. Commit 6fa02839bf94 ("nfsd4: recheck
for secure ports in fh_verify") rechecks the port in fh_verify(), but
READ and CLONE with an OPEN stateid go through
nfs4_preprocess_stateid_op() and never call fh_verify().

Check the secure-port setting in check_nfsd_access() with
nfsd_check_port(), factored out of fh_verify(). The SECINFO lookups
must still skip the flavor check, so pass the NFSD_MAY_* access flags
to check_nfsd_access() rather than have the mount-crossing paths
assemble their own subset of its checks. After the change, a check
added to check_nfsd_access() also covers the mount-crossing paths.

Report a port denial to an NFSv4 client as NFS4ERR_WRONGSEC. RFC 8881
does not permit NFS4ERR_PERM, the current result, from LOOKUP, LOOKUPP,
or the put-filehandle operations. NFS4ERR_WRONGSEC is permitted there
and is accurate, since a GSS flavor is exempt from the port check.
READDIR, COPY, CLONE, SECINFO, and the NFSv2 and NFSv3 status mapping
already convert NFS4ERR_WRONGSEC to an access error.

Reported-by: VeroFess <coderzeng@gmail.com>
Closes: https://patch.msgid.link/20261103013237.1174538-2-coderzeng@gmail.com
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Chuck Lever <cel@kernel.org>
---
 fs/nfsd/export.c   | 24 +++++++++++++++++-------
 fs/nfsd/export.h   |  3 ++-
 fs/nfsd/nfs4proc.c |  5 +++--
 fs/nfsd/nfsfh.c    | 34 +++++++++++++++++++++++++++++-----
 fs/nfsd/nfsfh.h    |  2 ++
 fs/nfsd/vfs.c      |  8 ++------
 6 files changed, 55 insertions(+), 21 deletions(-)

diff --git a/fs/nfsd/export.c b/fs/nfsd/export.c
index f0dd9fb22ec2..b5147c9797aa 100644
--- a/fs/nfsd/export.c
+++ b/fs/nfsd/export.c
@@ -1976,10 +1976,8 @@ static struct svc_export *exp_find(struct cache_detail *cd,
  *
  * Helper function for check_nfsd_access().  Note that callers should be
  * using check_nfsd_access() instead of calling this function directly.  The
- * exceptions are __fh_verify(), which has logic that may result in one
- * or both of the helpers being skipped, and the mount-crossing paths in
- * nfsd_cross_mnt() and nfsd_lookup_parent(), which enforce only the
- * transport policy when NFSD_MAY_BYPASS_FLAVOR is set.
+ * one exception is __fh_verify() since it has logic that may result in one
+ * or both of the helpers being skipped.
  *
  * Return values:
  *   %nfs_ok if access is granted, or
@@ -2075,18 +2073,30 @@ __be32 check_security_flavor(struct svc_export *exp, struct svc_rqst *rqstp,
  * check_nfsd_access - check if access to export is allowed.
  * @exp: svc_export that is being accessed.
  * @rqstp: svc_rqst attempting to access @exp.
+ * @access: NFSD_MAY_* flags; %NFSD_MAY_BYPASS_FLAVOR skips the security
+ *	flavor check
+ *
+ * Context: Process context.
  *
  * Return values:
- *   %nfs_ok if access is granted, or
- *   %nfserr_wrongsec if access is denied
+ *   %nfs_ok if access is granted,
+ *   %nfserr_wrongsec if access is denied, or
+ *   %nfserr_perm if the secure-port check denies an NFSv2 or NFSv3
+ *   request
  */
-__be32 check_nfsd_access(struct svc_export *exp, struct svc_rqst *rqstp)
+__be32 check_nfsd_access(struct svc_export *exp, struct svc_rqst *rqstp,
+			 int access)
 {
 	__be32 status;
 
+	status = nfsd_check_port(rqstp, &rqstp->rq_cred, exp);
+	if (status != nfs_ok)
+		return status;
 	status = check_xprtsec_policy(exp, rqstp);
 	if (status != nfs_ok)
 		return status;
+	if (access & NFSD_MAY_BYPASS_FLAVOR)
+		return nfs_ok;
 	return check_security_flavor(exp, rqstp, false);
 }
 
diff --git a/fs/nfsd/export.h b/fs/nfsd/export.h
index dcff16618edf..640109da3dda 100644
--- a/fs/nfsd/export.h
+++ b/fs/nfsd/export.h
@@ -123,7 +123,8 @@ int nfsexp_flags(struct svc_cred *cred, struct svc_export *exp);
 __be32 check_xprtsec_policy(struct svc_export *exp, struct svc_rqst *rqstp);
 __be32 check_security_flavor(struct svc_export *exp, struct svc_rqst *rqstp,
 			     bool may_bypass_gss);
-__be32 check_nfsd_access(struct svc_export *exp, struct svc_rqst *rqstp);
+__be32 check_nfsd_access(struct svc_export *exp, struct svc_rqst *rqstp,
+			 int access);
 
 /*
  * Function declarations
diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
index 2c653027d578..af4cc3a66682 100644
--- a/fs/nfsd/nfs4proc.c
+++ b/fs/nfsd/nfs4proc.c
@@ -1510,7 +1510,7 @@ nfsd4_verify_copy(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
 		return nfserr_nofilehandle;
 
 	/* A cached open stateid does not authorize the saved source export. */
-	status = check_nfsd_access(cstate->save_fh.fh_export, rqstp);
+	status = check_nfsd_access(cstate->save_fh.fh_export, rqstp, 0);
 	if (status)
 		return status == nfserr_wrongsec ? nfserr_acces : status;
 
@@ -3486,7 +3486,8 @@ nfsd4_proc_compound(struct svc_rqst *rqstp)
 
 			if (current_fh->fh_export &&
 					need_wrongsec_check(rqstp))
-				op->status = check_nfsd_access(current_fh->fh_export, rqstp);
+				op->status = check_nfsd_access(current_fh->fh_export,
+							       rqstp, 0);
 		}
 encode_op:
 		if (op->status == nfserr_replay_me) {
diff --git a/fs/nfsd/nfsfh.c b/fs/nfsd/nfsfh.c
index 09391464803f..f76ce9cc405c 100644
--- a/fs/nfsd/nfsfh.c
+++ b/fs/nfsd/nfsfh.c
@@ -102,11 +102,23 @@ static bool nfsd_originating_port_ok(struct svc_rqst *rqstp,
 	return test_bit(RQ_SECURE, &rqstp->rq_flags);
 }
 
-static __be32 nfsd_setuser_and_check_port(struct svc_rqst *rqstp,
-					  struct svc_cred *cred,
-					  struct svc_export *exp)
+/**
+ * nfsd_check_port - check an export's secure-port requirement
+ * @rqstp: request being checked, or %NULL for a local caller
+ * @cred: credential whose flavor selects the export options to check
+ * @exp: export being checked
+ *
+ * Context: Process context.
+ *
+ * A GSS flavor is exempt from the port check, so a denial reports
+ * NFS4ERR_WRONGSEC to an NFSv4 client.
+ *
+ * Return: %nfs_ok if allowed, %nfserr_wrongsec for an NFSv4 client,
+ * %nfserr_perm otherwise.
+ */
+__be32 nfsd_check_port(struct svc_rqst *rqstp,
+		       struct svc_cred *cred, struct svc_export *exp)
 {
-	/* Check if the request originated from a secure port. */
 	if (rqstp && !nfsd_originating_port_ok(rqstp, cred, exp)) {
 		if (IS_ENABLED(CONFIG_SUNRPC_DEBUG)) {
 			char buf[RPC_MAX_ADDRBUFLEN];
@@ -114,8 +126,20 @@ static __be32 nfsd_setuser_and_check_port(struct svc_rqst *rqstp,
 			dprintk("nfsd: request from insecure port %s!\n",
 			        svc_print_addr(rqstp, buf, sizeof(buf)));
 		}
-		return nfserr_perm;
+		return nfsd_v4client(rqstp) ? nfserr_wrongsec : nfserr_perm;
 	}
+	return nfs_ok;
+}
+
+static __be32 nfsd_setuser_and_check_port(struct svc_rqst *rqstp,
+					  struct svc_cred *cred,
+					  struct svc_export *exp)
+{
+	__be32 status;
+
+	status = nfsd_check_port(rqstp, cred, exp);
+	if (status)
+		return status;
 
 	/* Set user creds for this exportpoint */
 	return nfserrno(nfsd_setuser(cred, exp));
diff --git a/fs/nfsd/nfsfh.h b/fs/nfsd/nfsfh.h
index aa3ff3086406..90b6429cbcef 100644
--- a/fs/nfsd/nfsfh.h
+++ b/fs/nfsd/nfsfh.h
@@ -226,6 +226,8 @@ extern char * SVCFH_fmt(struct svc_fh *fhp);
 /*
  * Function prototypes
  */
+__be32	nfsd_check_port(struct svc_rqst *rqstp,
+			struct svc_cred *cred, struct svc_export *exp);
 __be32	fh_verify(struct svc_rqst *, struct svc_fh *, umode_t, int);
 __be32	fh_verify_local(struct net *, struct svc_cred *, struct auth_domain *,
 			struct svc_fh *, umode_t, int);
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index 269dc4968f19..6332341a8fba 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -168,9 +168,7 @@ nfsd_cross_mnt(struct svc_rqst *rqstp, struct dentry **dpp,
 			err = 0;
 	} else if (nfsd_v4client(rqstp) ||
 		(exp->ex_flags & NFSEXP_CROSSMOUNT) || EX_NOHIDE(exp2)) {
-		nfserr = (access & NFSD_MAY_BYPASS_FLAVOR) ?
-			 check_xprtsec_policy(exp2, rqstp) :
-			 check_nfsd_access(exp2, rqstp);
+		nfserr = check_nfsd_access(exp2, rqstp, access);
 		if (nfserr == nfs_ok) {
 			/* successfully crossed mount point */
 			/*
@@ -222,9 +220,7 @@ static __be32 nfsd_lookup_parent(struct svc_rqst *rqstp, struct dentry *dparent,
 	} else if (IS_ERR(exp2)) {
 		nfserr = nfserrno(PTR_ERR(exp2));
 	} else {
-		nfserr = (access & NFSD_MAY_BYPASS_FLAVOR) ?
-			 check_xprtsec_policy(exp2, rqstp) :
-			 check_nfsd_access(exp2, rqstp);
+		nfserr = check_nfsd_access(exp2, rqstp, access);
 		if (nfserr != nfs_ok) {
 			exp_put(exp2);
 		} else {

-- 
2.56.0


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 2/5] NFSD: Defer the PUTFH secure-port check
  2026-11-03 19:52 [PATCH 0/5] NFSD: Apply export security when a compound crosses exports Chuck Lever
  2026-11-03 19:52 ` [PATCH 1/5] NFSD: Check the secure-port policy when entering an export Chuck Lever
@ 2026-11-03 19:52 ` Chuck Lever
  2026-11-03 19:52 ` [PATCH 3/5] NFSD: Report WRONGSEC from PUTFH when OPEN uses the current filehandle Chuck Lever
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 8+ messages in thread
From: Chuck Lever @ 2026-11-03 19:52 UTC (permalink / raw)
  To: VeroFess, Jeff Layton, NeilBrown, Olga Kornievskaia, Dai Ngo,
	Tom Talpey, Al Viro
  Cc: linux-nfs, Chuck Lever

When an AUTH_SYS request from an insecure port presents a filehandle
in an export that requires a secure port, fh_verify() fails PUTFH with
NFS4ERR_WRONGSEC. RFC 8881 Sections 2.6.3.1.1.2 through 2.6.3.1.1.5
forbid NFS4ERR_WRONGSEC from a put-filehandle operation that another
put-filehandle operation, LOOKUP, LOOKUPP, OPEN, SECINFO, or
SECINFO_NO_NAME follows. PUTFH therefore cannot report the denial
before it knows which operation follows. SECINFO and SECINFO_NO_NAME
also fail a port denial themselves, so a client cannot learn which
flavors the export accepts.

Defer the port check in PUTFH, as "NFSD: Defer the PUTFH export
security flavor check" deferred the flavor check.
need_wrongsec_check() needs no change, because check_nfsd_access()
already checks the port. Skip the port check in SECINFO and
SECINFO_NO_NAME, as those operations skip the flavor check. After the
change, LOOKUP, LOOKUPP, OPEN, LINK, and RENAME return
NFS4ERR_WRONGSEC for a port denial, and SECINFO and SECINFO_NO_NAME
report the flavors the export accepts. NFSv2 and NFSv3 still return
NFSERR_PERM.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Chuck Lever <cel@kernel.org>
---
 fs/nfsd/export.c   |  4 ++--
 fs/nfsd/nfs4proc.c | 16 ++++++++++------
 fs/nfsd/nfsfh.c    | 19 +++++++++++--------
 fs/nfsd/nfsfh.h    |  4 ++--
 fs/nfsd/trace.h    |  3 ++-
 fs/nfsd/vfs.h      |  1 +
 6 files changed, 28 insertions(+), 19 deletions(-)

diff --git a/fs/nfsd/export.c b/fs/nfsd/export.c
index b5147c9797aa..31e691554837 100644
--- a/fs/nfsd/export.c
+++ b/fs/nfsd/export.c
@@ -2074,7 +2074,7 @@ __be32 check_security_flavor(struct svc_export *exp, struct svc_rqst *rqstp,
  * @exp: svc_export that is being accessed.
  * @rqstp: svc_rqst attempting to access @exp.
  * @access: NFSD_MAY_* flags; %NFSD_MAY_BYPASS_FLAVOR skips the security
- *	flavor check
+ *	flavor check, %NFSD_MAY_BYPASS_PORT skips the secure-port check
  *
  * Context: Process context.
  *
@@ -2089,7 +2089,7 @@ __be32 check_nfsd_access(struct svc_export *exp, struct svc_rqst *rqstp,
 {
 	__be32 status;
 
-	status = nfsd_check_port(rqstp, &rqstp->rq_cred, exp);
+	status = nfsd_check_port(rqstp, &rqstp->rq_cred, exp, access);
 	if (status != nfs_ok)
 		return status;
 	status = check_xprtsec_policy(exp, rqstp);
diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
index af4cc3a66682..faed08eb1b98 100644
--- a/fs/nfsd/nfs4proc.c
+++ b/fs/nfsd/nfs4proc.c
@@ -827,8 +827,9 @@ nfsd4_putfh(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
 	cstate->current_fh.fh_handle.fh_size = putfh->pf_fhlen;
 	memcpy(&cstate->current_fh.fh_handle.fh_raw, putfh->pf_fhval,
 	       putfh->pf_fhlen);
-	/* need_wrongsec_check() decides whether the flavor check applies. */
-	ret = fh_verify(rqstp, &cstate->current_fh, 0, NFSD_MAY_BYPASS_FLAVOR);
+	/* need_wrongsec_check() decides whether the bypassed checks apply. */
+	ret = fh_verify(rqstp, &cstate->current_fh, 0,
+			NFSD_MAY_BYPASS_FLAVOR | NFSD_MAY_BYPASS_PORT);
 #ifdef CONFIG_NFSD_V4_2_INTER_SSC
 	if (ret == nfserr_stale && putfh->no_verify) {
 		SET_FH_FLAG(&cstate->current_fh, NFSD4_FH_FOREIGN);
@@ -1264,13 +1265,15 @@ nfsd4_secinfo(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
 	__be32 err;
 
 	err = fh_verify(rqstp, &cstate->current_fh, S_IFDIR,
-			NFSD_MAY_EXEC | NFSD_MAY_BYPASS_FLAVOR);
+			NFSD_MAY_EXEC | NFSD_MAY_BYPASS_FLAVOR |
+			NFSD_MAY_BYPASS_PORT);
 	if (err)
 		return nfsd4_secinfo_status(err);
 	err = nfsd_lookup_dentry(rqstp, &cstate->current_fh,
 				    secinfo->si_name, secinfo->si_namelen,
 				    &exp, &dentry,
-				    NFSD_MAY_EXEC | NFSD_MAY_BYPASS_FLAVOR);
+				    NFSD_MAY_EXEC | NFSD_MAY_BYPASS_FLAVOR |
+				    NFSD_MAY_BYPASS_PORT);
 	if (err)
 		return nfsd4_secinfo_status(err);
 	if (d_really_is_negative(dentry)) {
@@ -1294,13 +1297,14 @@ nfsd4_secinfo_no_name(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstat
 	switch (u->secinfo_no_name.sin_style) {
 	case NFS4_SECINFO_STYLE4_CURRENT_FH:
 		err = fh_verify(rqstp, &cstate->current_fh, 0,
-				NFSD_MAY_BYPASS_FLAVOR);
+				NFSD_MAY_BYPASS_FLAVOR | NFSD_MAY_BYPASS_PORT);
 		if (err)
 			return nfsd4_secinfo_status(err);
 		break;
 	case NFS4_SECINFO_STYLE4_PARENT:
 		err = nfsd4_do_lookupp(rqstp, &cstate->current_fh,
-				       NFSD_MAY_EXEC | NFSD_MAY_BYPASS_FLAVOR);
+				       NFSD_MAY_EXEC | NFSD_MAY_BYPASS_FLAVOR |
+				       NFSD_MAY_BYPASS_PORT);
 		if (err)
 			return nfsd4_secinfo_status(err);
 		break;
diff --git a/fs/nfsd/nfsfh.c b/fs/nfsd/nfsfh.c
index f76ce9cc405c..0a5590f1f1bb 100644
--- a/fs/nfsd/nfsfh.c
+++ b/fs/nfsd/nfsfh.c
@@ -107,6 +107,7 @@ static bool nfsd_originating_port_ok(struct svc_rqst *rqstp,
  * @rqstp: request being checked, or %NULL for a local caller
  * @cred: credential whose flavor selects the export options to check
  * @exp: export being checked
+ * @access: NFSD_MAY_* flags; %NFSD_MAY_BYPASS_PORT skips the check
  *
  * Context: Process context.
  *
@@ -116,9 +117,11 @@ static bool nfsd_originating_port_ok(struct svc_rqst *rqstp,
  * Return: %nfs_ok if allowed, %nfserr_wrongsec for an NFSv4 client,
  * %nfserr_perm otherwise.
  */
-__be32 nfsd_check_port(struct svc_rqst *rqstp,
-		       struct svc_cred *cred, struct svc_export *exp)
+__be32 nfsd_check_port(struct svc_rqst *rqstp, struct svc_cred *cred,
+		       struct svc_export *exp, int access)
 {
+	if (access & NFSD_MAY_BYPASS_PORT)
+		return nfs_ok;
 	if (rqstp && !nfsd_originating_port_ok(rqstp, cred, exp)) {
 		if (IS_ENABLED(CONFIG_SUNRPC_DEBUG)) {
 			char buf[RPC_MAX_ADDRBUFLEN];
@@ -133,11 +136,11 @@ __be32 nfsd_check_port(struct svc_rqst *rqstp,
 
 static __be32 nfsd_setuser_and_check_port(struct svc_rqst *rqstp,
 					  struct svc_cred *cred,
-					  struct svc_export *exp)
+					  struct svc_export *exp, int access)
 {
 	__be32 status;
 
-	status = nfsd_check_port(rqstp, cred, exp);
+	status = nfsd_check_port(rqstp, cred, exp, access);
 	if (status)
 		return status;
 
@@ -226,7 +229,7 @@ static __be32 nfsd_set_fh_dentry(struct svc_rqst *rqstp, struct net *net,
 				 struct svc_cred *cred,
 				 struct auth_domain *client,
 				 struct auth_domain *gssclient,
-				 struct svc_fh *fhp)
+				 struct svc_fh *fhp, int access)
 {
 	struct knfsd_fh	*fh = &fhp->fh_handle;
 	struct fid *fid = NULL;
@@ -305,7 +308,7 @@ static __be32 nfsd_set_fh_dentry(struct svc_rqst *rqstp, struct net *net,
 					   new->cap_permitted);
 		put_cred(override_creds(new));
 	} else {
-		error = nfsd_setuser_and_check_port(rqstp, cred, exp);
+		error = nfsd_setuser_and_check_port(rqstp, cred, exp, access);
 		if (error)
 			goto out;
 	}
@@ -423,7 +426,7 @@ __fh_verify(struct svc_rqst *rqstp,
 
 	if (!fhp->fh_dentry) {
 		error = nfsd_set_fh_dentry(rqstp, net, cred, client,
-					   gssclient, fhp);
+					   gssclient, fhp, access);
 		if (error)
 			goto out;
 	}
@@ -452,7 +455,7 @@ __fh_verify(struct svc_rqst *rqstp,
 	if (error)
 		goto out;
 
-	error = nfsd_setuser_and_check_port(rqstp, cred, exp);
+	error = nfsd_setuser_and_check_port(rqstp, cred, exp, access);
 	if (error)
 		goto out;
 
diff --git a/fs/nfsd/nfsfh.h b/fs/nfsd/nfsfh.h
index 90b6429cbcef..51788712d630 100644
--- a/fs/nfsd/nfsfh.h
+++ b/fs/nfsd/nfsfh.h
@@ -226,8 +226,8 @@ extern char * SVCFH_fmt(struct svc_fh *fhp);
 /*
  * Function prototypes
  */
-__be32	nfsd_check_port(struct svc_rqst *rqstp,
-			struct svc_cred *cred, struct svc_export *exp);
+__be32	nfsd_check_port(struct svc_rqst *rqstp, struct svc_cred *cred,
+			struct svc_export *exp, int access);
 __be32	fh_verify(struct svc_rqst *, struct svc_fh *, umode_t, int);
 __be32	fh_verify_local(struct net *, struct svc_cred *, struct auth_domain *,
 			struct svc_fh *, umode_t, int);
diff --git a/fs/nfsd/trace.h b/fs/nfsd/trace.h
index e58b753c1ebc..89b1c575f6af 100644
--- a/fs/nfsd/trace.h
+++ b/fs/nfsd/trace.h
@@ -143,7 +143,8 @@ DEFINE_NFSD_DYNTHREAD_EVENT(trylock_fail);
 		{ NFSD_MAY_READ_IF_EXEC,	"READ_IF_EXEC" },	\
 		{ NFSD_MAY_64BIT_COOKIE,	"64BIT_COOKIE" },	\
 		{ NFSD_MAY_LOCALIO,		"LOCALIO" },	\
-		{ NFSD_MAY_BYPASS_FLAVOR,	"BYPASS_FLAVOR" })
+		{ NFSD_MAY_BYPASS_FLAVOR,	"BYPASS_FLAVOR" },	\
+		{ NFSD_MAY_BYPASS_PORT,		"BYPASS_PORT" })
 
 TRACE_EVENT(nfsd_compound,
 	TP_PROTO(
diff --git a/fs/nfsd/vfs.h b/fs/nfsd/vfs.h
index 939124de6bd7..73ab9d34a275 100644
--- a/fs/nfsd/vfs.h
+++ b/fs/nfsd/vfs.h
@@ -34,6 +34,7 @@
 
 #define NFSD_MAY_LOCALIO		0x2000 /* for tracing, reflects when localio used */
 #define NFSD_MAY_BYPASS_FLAVOR		0x4000 /* skip the export's sec= check */
+#define NFSD_MAY_BYPASS_PORT		0x8000 /* skip the export's secure-port check */
 
 #define NFSD_MAY_CREATE		(NFSD_MAY_EXEC|NFSD_MAY_WRITE)
 #define NFSD_MAY_REMOVE		(NFSD_MAY_EXEC|NFSD_MAY_WRITE|NFSD_MAY_TRUNC)

-- 
2.56.0


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 3/5] NFSD: Report WRONGSEC from PUTFH when OPEN uses the current filehandle
  2026-11-03 19:52 [PATCH 0/5] NFSD: Apply export security when a compound crosses exports Chuck Lever
  2026-11-03 19:52 ` [PATCH 1/5] NFSD: Check the secure-port policy when entering an export Chuck Lever
  2026-11-03 19:52 ` [PATCH 2/5] NFSD: Defer the PUTFH secure-port check Chuck Lever
@ 2026-11-03 19:52 ` Chuck Lever
  2026-11-03 19:52 ` [PATCH 4/5] NFSD: Apply the entered export's credentials after LOOKUP and RESTOREFH Chuck Lever
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 8+ messages in thread
From: Chuck Lever @ 2026-11-03 19:52 UTC (permalink / raw)
  To: VeroFess, Jeff Layton, NeilBrown, Olga Kornievskaia, Dai Ngo,
	Tom Talpey, Al Viro
  Cc: linux-nfs, Chuck Lever

need_wrongsec_check() leaves the deferred security check to any
following OPEN, because OPEN carries OP_HANDLES_WRONGSEC. That is
right for an OPEN by component name, which verifies the parent
directory during its lookup. An OPEN that uses the current filehandle
(CLAIM_PREVIOUS, CLAIM_FH, CLAIM_DELEG_CUR_FH, or CLAIM_DELEG_PREV_FH)
instead reports the denial from its own fh_verify() call. RFC 8881
Section 2.6.3.1.1.7 requires the put-filehandle operation to return
NFS4ERR_WRONGSEC in that case and forbids NFS4ERR_WRONGSEC from an
OPEN by filehandle.

Treat an OPEN that uses the current filehandle like any other
consumer of the filehandle, so PUTFH reports the denial.

Fixes: 68d93184352f ("nfsd4: fix wrongsec handling for PUTFH + op cases")
Signed-off-by: Chuck Lever <cel@kernel.org>
---
 fs/nfsd/nfs4proc.c | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
index faed08eb1b98..9762a56d30ad 100644
--- a/fs/nfsd/nfs4proc.c
+++ b/fs/nfsd/nfs4proc.c
@@ -3259,6 +3259,25 @@ bool nfsd4_cache_this_op(struct nfsd4_op *op)
 	return OPDESC(op)->op_flags & OP_CACHEME;
 }
 
+/*
+ * RFC 8881 Section 2.6.3.1.1.7: only an OPEN by component name may
+ * report NFS4ERR_WRONGSEC itself.
+ */
+static bool open_handles_wrongsec(const struct nfsd4_op *op)
+{
+	/* A failed decode leaves op_claim_type unset. */
+	if (op->status != nfs_ok)
+		return false;
+	switch (op->u.open.op_claim_type) {
+	case NFS4_OPEN_CLAIM_NULL:
+	case NFS4_OPEN_CLAIM_DELEGATE_CUR:
+	case NFS4_OPEN_CLAIM_DELEGATE_PREV:
+		return true;
+	default:
+		return false;
+	}
+}
+
 static bool need_wrongsec_check(struct svc_rqst *rqstp)
 {
 	struct nfsd4_compoundres *resp = rqstp->rq_resp;
@@ -3296,6 +3315,8 @@ static bool need_wrongsec_check(struct svc_rqst *rqstp)
 	 */
 	if (resp->cstate.minorversion == 0 && next->opnum == OP_SECINFO)
 		return true;
+	if (next->opnum == OP_OPEN)
+		return !open_handles_wrongsec(next);
 	nextd = OPDESC(next);
 	/*
 	 * RFC 8881 Section 2.6.3.1.1.2: a later put-filehandle operation

-- 
2.56.0


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 4/5] NFSD: Apply the entered export's credentials after LOOKUP and RESTOREFH
  2026-11-03 19:52 [PATCH 0/5] NFSD: Apply export security when a compound crosses exports Chuck Lever
                   ` (2 preceding siblings ...)
  2026-11-03 19:52 ` [PATCH 3/5] NFSD: Report WRONGSEC from PUTFH when OPEN uses the current filehandle Chuck Lever
@ 2026-11-03 19:52 ` Chuck Lever
  2026-11-03 19:52 ` [PATCH 5/5] NFSD: Use each export's credentials for the COPY and CLONE stateids Chuck Lever
  2026-11-04 20:32 ` [PATCH 0/5] NFSD: Apply export security when a compound crosses exports VeroFess
  5 siblings, 0 replies; 8+ messages in thread
From: Chuck Lever @ 2026-11-03 19:52 UTC (permalink / raw)
  To: VeroFess, Jeff Layton, NeilBrown, Olga Kornievskaia, Dai Ngo,
	Tom Talpey, Al Viro
  Cc: linux-nfs, Chuck Lever

fh_verify() installs the credentials that an export's squash options
map a request to. When LOOKUP crosses into another export, or
RESTOREFH restores a filehandle from another export, nothing installs
the new export's credentials. Operations that use the current
filehandle without calling fh_verify(), such as READ with an OPEN
stateid, then run with the previous export's mapping. If the previous
export does not squash root and the new export does, READ runs as
root. If the new export uses all_squash, READ can fail even though the
anonymous UID or GID would be granted access.

Call nfsd_setuser() for the entered export after a crossing in
__nfsd_lookup() and after RESTOREFH. The crossing has already run
check_nfsd_access() on that export, so a full fh_verify() would repeat
those checks on every LOOKUP that crosses an export. RESTOREFH
likewise calls only nfsd_setuser(), so its flavor check stays
deferred. NFSv2 and NFSv3 verify the filehandle on every request, so
only an NFSv4 LOOKUP installs the credentials, and RESTOREFH skips
the call when the saved filehandle is in the current export.

Reported-by: VeroFess <coderzeng@gmail.com>
Closes: https://patch.msgid.link/20261103013237.1174538-3-coderzeng@gmail.com
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Chuck Lever <cel@kernel.org>
---
 fs/nfsd/nfs4proc.c | 7 +++++++
 fs/nfsd/vfs.c      | 9 +++++++++
 2 files changed, 16 insertions(+)

diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
index 9762a56d30ad..8c7b81f62125 100644
--- a/fs/nfsd/nfs4proc.c
+++ b/fs/nfsd/nfs4proc.c
@@ -49,6 +49,7 @@
 #include "xdr4.h"
 #include "nfs4ctl.h"
 #include "vfs.h"
+#include "auth.h"
 #include "current_stateid.h"
 #include "netns.h"
 #include "acl.h"
@@ -852,14 +853,20 @@ static __be32
 nfsd4_restorefh(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
 		union nfsd4_op_u *u)
 {
+	bool crossed;
+
 	if (!cstate->save_fh.fh_dentry)
 		return nfserr_restorefh;
 
+	crossed = cstate->current_fh.fh_export != cstate->save_fh.fh_export;
 	fh_dup2(&cstate->current_fh, &cstate->save_fh);
 	if (HAS_CSTATE_FLAG(cstate, SAVED_STATE_ID_FLAG)) {
 		memcpy(&cstate->current_stateid, &cstate->save_stateid, sizeof(stateid_t));
 		SET_CSTATE_FLAG(cstate, CURRENT_STATE_ID_FLAG);
 	}
+	/* RFC 8881 Section 18.27.3 does not permit NFS4ERR_DELAY here. */
+	if (crossed && nfsd_setuser(&rqstp->rq_cred, cstate->current_fh.fh_export))
+		return nfserr_serverfault;
 	return nfs_ok;
 }
 
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index 6332341a8fba..02df2ff02d60 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -41,6 +41,7 @@
 #endif /* CONFIG_NFSD_V4 */
 
 #include "nfsd.h"
+#include "auth.h"
 #include "nfserr.h"
 #include "nfs4ctl.h"
 #include "netns.h"
@@ -338,6 +339,7 @@ __nfsd_lookup(struct svc_rqst *rqstp, struct svc_fh *fhp, const char *name,
 {
 	struct svc_export	*exp;
 	struct dentry		*dentry;
+	bool crossed;
 	__be32 err;
 
 	err = fh_verify(rqstp, fhp, S_IFDIR, access);
@@ -350,9 +352,16 @@ __nfsd_lookup(struct svc_rqst *rqstp, struct svc_fh *fhp, const char *name,
 	 * Note: we compose the file handle now, but as the
 	 * dentry may be negative, it may need to be updated.
 	 */
+	crossed = exp != fhp->fh_export;
 	err = fh_compose(resfh, exp, dentry, fhp);
 	if (!err && d_really_is_negative(dentry))
 		err = nfserr_noent;
+	/*
+	 * The crossing checked exp but did not install exp's creds, and
+	 * an NFSv4 stateid op uses the new filehandle without fh_verify().
+	 */
+	if (!err && crossed && nfsd_v4client(rqstp))
+		err = nfserrno(nfsd_setuser(&rqstp->rq_cred, exp));
 
 	dput(dentry);
 	exp_put(exp);

-- 
2.56.0


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 5/5] NFSD: Use each export's credentials for the COPY and CLONE stateids
  2026-11-03 19:52 [PATCH 0/5] NFSD: Apply export security when a compound crosses exports Chuck Lever
                   ` (3 preceding siblings ...)
  2026-11-03 19:52 ` [PATCH 4/5] NFSD: Apply the entered export's credentials after LOOKUP and RESTOREFH Chuck Lever
@ 2026-11-03 19:52 ` Chuck Lever
  2026-11-04 20:32 ` [PATCH 0/5] NFSD: Apply export security when a compound crosses exports VeroFess
  5 siblings, 0 replies; 8+ messages in thread
From: Chuck Lever @ 2026-11-03 19:52 UTC (permalink / raw)
  To: VeroFess, Jeff Layton, NeilBrown, Olga Kornievskaia, Dai Ngo,
	Tom Talpey, Al Viro
  Cc: linux-nfs, Chuck Lever

nfsd4_verify_copy() checks the source export with check_nfsd_access()
but resolves the source stateid under the credentials that PUTFH of
the destination installed. For an OPEN stateid, nfsd_permission()
then checks the source file with the destination export's squash
mapping. When the source export squashes root and the destination
export does not, the source file is checked as root. When the source
export uses all_squash, nfsd_permission() runs as the caller instead of
the anonymous UID and GID, and can reject a request the source export
permits.

Install the source export's credentials before resolving the source
stateid, and restore the destination export's credentials before
resolving the destination stateid. Both exports have already passed
their security checks, so neither filehandle needs a full fh_verify().
When the source and destination share an export, the credentials
PUTFH installed already apply, so skip both calls.

Reported-by: VeroFess <coderzeng@gmail.com>
Closes: https://patch.msgid.link/20261103013237.1174538-4-coderzeng@gmail.com
Fixes: ffa0160a1039 ("nfsd: implement the NFSv4.2 CLONE operation")
Signed-off-by: Chuck Lever <cel@kernel.org>
---
 fs/nfsd/nfs4proc.c | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
index 8c7b81f62125..03ea955f1fe9 100644
--- a/fs/nfsd/nfs4proc.c
+++ b/fs/nfsd/nfs4proc.c
@@ -1515,6 +1515,7 @@ nfsd4_verify_copy(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
 		  stateid_t *src_stateid, struct nfsd_file **src,
 		  stateid_t *dst_stateid, struct nfsd_file **dst)
 {
+	bool crossed;
 	__be32 status;
 
 	if (!cstate->save_fh.fh_dentry)
@@ -1524,12 +1525,27 @@ nfsd4_verify_copy(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
 	status = check_nfsd_access(cstate->save_fh.fh_export, rqstp, 0);
 	if (status)
 		return status == nfserr_wrongsec ? nfserr_acces : status;
+	crossed = cstate->save_fh.fh_export != cstate->current_fh.fh_export;
+	if (crossed) {
+		status = nfserrno(nfsd_setuser(&rqstp->rq_cred,
+					       cstate->save_fh.fh_export));
+		if (status)
+			return status;
+	}
 
 	status = nfs4_preprocess_stateid_op(rqstp, cstate, &cstate->save_fh,
 					    src_stateid, RD_STATE, src, NULL);
 	if (status)
 		goto out;
 
+	/* The destination export was checked when current_fh was set. */
+	if (crossed) {
+		status = nfserrno(nfsd_setuser(&rqstp->rq_cred,
+					       cstate->current_fh.fh_export));
+		if (status)
+			goto out_put_src;
+	}
+
 	status = nfs4_preprocess_stateid_op(rqstp, cstate, &cstate->current_fh,
 					    dst_stateid, WR_STATE, dst, NULL);
 	if (status)

-- 
2.56.0


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [PATCH 0/5] NFSD: Apply export security when a compound crosses exports
  2026-11-03 19:52 [PATCH 0/5] NFSD: Apply export security when a compound crosses exports Chuck Lever
                   ` (4 preceding siblings ...)
  2026-11-03 19:52 ` [PATCH 5/5] NFSD: Use each export's credentials for the COPY and CLONE stateids Chuck Lever
@ 2026-11-04 20:32 ` VeroFess
  2026-11-04 21:06   ` Chuck Lever
  5 siblings, 1 reply; 8+ messages in thread
From: VeroFess @ 2026-11-04 20:32 UTC (permalink / raw)
  To: cel
  Cc: Jeff Layton, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
	Al Viro, linux-nfs

Chuck,

I tested the five patches on an x86-64 KASAN guest. They applied in
order, the changed NFSD objects built with W=1, and the full bzImage
built with Btrfs, TLS, and NFSv4.2 inter-server COPY enabled.
The wire tests covered NFSv4.0, 4.1, and 4.2 where applicable.

After a reserved-port OPEN, high-port AUTH_SYS compounds that used
LOOKUP before READ, COPY, or CLONE stopped at LOOKUP with WRONGSEC.
The destination was unchanged. Direct PUTFH returned WRONGSEC, while
low-port COPY and CLONE succeeded. SECINFO and SECINFO_NO_NAME still
succeeded on the high port. READDIR with rdattr_error omitted the
protected filehandle.

After switching the source export to root_squash, LOOKUP; READ,
RESTOREFH; READ, and saved-source COPY/CLONE returned ACCESS with the
old OPEN stateid. The all_squash UID/GID cases and same-export
COPY/CLONE passed with the expected data.

With krb5 or krb5i on a krb5p export, OPEN by filehandle returned
WRONGSEC at PUTFH; OPEN by name returned it at OPEN. krb5p worked from
a high port and over TLS. NFSv3 LOOKUP returned PERM from a high port
and succeeded from a reserved port. I saw no KASAN report or oops in
the guest log.

Tested-by: VeroFess <coderzeng@gmail.com>

VeroFess

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 0/5] NFSD: Apply export security when a compound crosses exports
  2026-11-04 20:32 ` [PATCH 0/5] NFSD: Apply export security when a compound crosses exports VeroFess
@ 2026-11-04 21:06   ` Chuck Lever
  0 siblings, 0 replies; 8+ messages in thread
From: Chuck Lever @ 2026-11-04 21:06 UTC (permalink / raw)
  To: Adolph Zeng
  Cc: Jeff Layton, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
	Alexander Viro, linux-nfs



On Wed, Nov 4, 2026, at 3:32 PM, VeroFess wrote:
> Chuck,
>
> I tested the five patches on an x86-64 KASAN guest. They applied in
> order, the changed NFSD objects built with W=1, and the full bzImage
> built with Btrfs, TLS, and NFSv4.2 inter-server COPY enabled.
> The wire tests covered NFSv4.0, 4.1, and 4.2 where applicable.
>
> After a reserved-port OPEN, high-port AUTH_SYS compounds that used
> LOOKUP before READ, COPY, or CLONE stopped at LOOKUP with WRONGSEC.
> The destination was unchanged. Direct PUTFH returned WRONGSEC, while
> low-port COPY and CLONE succeeded. SECINFO and SECINFO_NO_NAME still
> succeeded on the high port. READDIR with rdattr_error omitted the
> protected filehandle.
>
> After switching the source export to root_squash, LOOKUP; READ,
> RESTOREFH; READ, and saved-source COPY/CLONE returned ACCESS with the
> old OPEN stateid. The all_squash UID/GID cases and same-export
> COPY/CLONE passed with the expected data.
>
> With krb5 or krb5i on a krb5p export, OPEN by filehandle returned
> WRONGSEC at PUTFH; OPEN by name returned it at OPEN. krb5p worked from
> a high port and over TLS. NFSv3 LOOKUP returned PERM from a high port
> and succeeded from a reserved port. I saw no KASAN report or oops in
> the guest log.
>
> Tested-by: VeroFess <coderzeng@gmail.com>

Thank you! I've tagged all 5 with your Tested-by.


-- 
Chuck Lever (Come to NFS bake-a-thon! https://nfsv4bat.org)

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2026-11-04 21:07 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-03 19:52 [PATCH 0/5] NFSD: Apply export security when a compound crosses exports Chuck Lever
2026-11-03 19:52 ` [PATCH 1/5] NFSD: Check the secure-port policy when entering an export Chuck Lever
2026-11-03 19:52 ` [PATCH 2/5] NFSD: Defer the PUTFH secure-port check Chuck Lever
2026-11-03 19:52 ` [PATCH 3/5] NFSD: Report WRONGSEC from PUTFH when OPEN uses the current filehandle Chuck Lever
2026-11-03 19:52 ` [PATCH 4/5] NFSD: Apply the entered export's credentials after LOOKUP and RESTOREFH Chuck Lever
2026-11-03 19:52 ` [PATCH 5/5] NFSD: Use each export's credentials for the COPY and CLONE stateids Chuck Lever
2026-11-04 20:32 ` [PATCH 0/5] NFSD: Apply export security when a compound crosses exports VeroFess
2026-11-04 21:06   ` Chuck Lever

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox