All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v5 0/3] NFSD: Enforce export security after a deferred PUTFH check
@ 2026-10-30 21:27 Chuck Lever
  2026-10-30 21:27 ` [PATCH v5 1/3] NFSD: Fix PUTFH wrongsec check when SAVEFH follows Chuck Lever
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: Chuck Lever @ 2026-10-30 21:27 UTC (permalink / raw)
  To: VeroFess, Chuck Lever, Jeff Layton, NeilBrown, Olga Kornievskaia,
	Dai Ngo, Tom Talpey
  Cc: linux-nfs

VeroFess reported that an AUTH_SYS NFSv4.0 client can read or write
a file on a sec=krb5p export with another client's OPEN stateid by
placing SAVEFH between PUTFH and the I/O operation. The v4 posting
carried that fix together with changes to COPY, LOOKUP, READDIR, and
SECINFO in one patch, which would not apply to the stable kernels
that need the fix. This series splits that patch. The
need_wrongsec_check() change stands alone (patch 1) and applies to
every LTS kernel, and the remaining patches carry only the Fixes:
tags that match their code. VeroFess developed the original fix with
the assistance of an LLM, and an LLM assisted with the split and the
commit messages in this series.

VeroFess confirmed on a kernel with only the need_wrongsec_check()
change that PUTFH; SAVEFH; READ and PUTFH; SAVEFH; WRITE return
NFS4ERR_WRONGSEC, and on a kernel with that change plus the COPY
source check that PUTFH; SAVEFH; PUTFH; COPY returns NFS4ERR_ACCESS.
The series as posted has not been run against the reproducer yet.
VeroFess has offered to do that.

After this series, NFSD still relies on per-operation
check_nfsd_access() calls to enforce a PUTFH check that was deferred.
A filehandle could instead carry a check-pending state that the
COMPOUND loop enforces before any operation consumes the filehandle.
Carrying such a state is later work.

Based on nfsd-testing.

---
Changes in v5:
- Split the single patch into three, each with its own Fixes: tags.
- Drop the nfs4_check_file() and COPY_NOTIFY export checks.
- Check the entered export in nfsd_cross_mnt() and nfsd_lookup_parent().
- Replace the for_secinfo flag with NFSD_MAY_SECINFO.
- Report a SECINFO transport policy mismatch as NFS4ERR_ACCESS.
- Keep the PUTFH export check for NFSv4.0 PUTFH; SECINFO.
- Link to v4: https://lore.kernel.org/r/20261029063917.138184-1-coderzeng@gmail.com

Changes in v4:
- Skip only the flavor check for SECINFO; xprtsec= stays enforced.
- Re-check xprtsec= when a SECINFO lookup selects another export.
- Link to v3: https://lore.kernel.org/r/20261029051430.96326-1-coderzeng@gmail.com

Changes in v3:
- Use nfsd_lookup_dentry() for SECINFO_NO_NAME(PARENT).
- Let SECINFO and SECINFO_NO_NAME(PARENT) query a strict starting export.
- Skip the source export's policy check on a SECINFO mount crossing.
- Link to v2: https://lore.kernel.org/r/20261029013648.12015-1-coderzeng@gmail.com

Changes in v2:
- Check cached stateid I/O and COPY_NOTIFY against the current export.
- Check the selected export after LOOKUP and READDIR mount crossing.
- Map READDIR's NFS4ERR_WRONGSEC to NFS4ERR_ACCESS without rdattr_error.
- Add the missing Fixes: tags and regenerate the whitespace-damaged diff.
- v1 was sent off-list.

---
VeroFess (3):
      NFSD: Fix PUTFH wrongsec check when SAVEFH follows
      NFSD: Check export security on the COPY and CLONE source
      NFSD: Enforce export security on LOOKUP, READDIR, and SECINFO

 fs/nfsd/export.c    |  6 ++--
 fs/nfsd/nfs3proc.c  |  2 +-
 fs/nfsd/nfs4proc.c  | 80 +++++++++++++++++++++++++++++++++++++----------------
 fs/nfsd/nfs4state.c |  2 +-
 fs/nfsd/nfs4xdr.c   |  8 ++++--
 fs/nfsd/nfsfh.c     | 12 ++++++--
 fs/nfsd/nfsproc.c   |  2 +-
 fs/nfsd/trace.h     |  3 +-
 fs/nfsd/vfs.c       | 60 ++++++++++++++++++++++++++--------------
 fs/nfsd/vfs.h       | 17 +++++++++---
 10 files changed, 132 insertions(+), 60 deletions(-)
---
base-commit: d9130c9f6535392d423351ffb5521e7689cae6b6
change-id: 20261030-verofess-f0281be339bc

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


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

* [PATCH v5 1/3] NFSD: Fix PUTFH wrongsec check when SAVEFH follows
  2026-10-30 21:27 [PATCH v5 0/3] NFSD: Enforce export security after a deferred PUTFH check Chuck Lever
@ 2026-10-30 21:27 ` Chuck Lever
  2026-10-30 21:27 ` [PATCH v5 2/3] NFSD: Check export security on the COPY and CLONE source Chuck Lever
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: Chuck Lever @ 2026-10-30 21:27 UTC (permalink / raw)
  To: VeroFess, Chuck Lever, Jeff Layton, NeilBrown, Olga Kornievskaia,
	Dai Ngo, Tom Talpey
  Cc: linux-nfs

From: VeroFess <coderzeng@gmail.com>

An AUTH_SYS NFSv4.0 client can read or write a file on a sec=krb5p
export using another client's valid OPEN stateid by inserting
SAVEFH between PUTFH and READ or WRITE.

PUTFH resolves the handle with NFSD_MAY_BYPASS_GSS and defers the
security flavor check to need_wrongsec_check(), which looks only at
the next operation. SAVEFH carries OP_HANDLES_WRONGSEC even though
it only saves the handle, so PUTFH skips NFS4ERR_WRONGSEC. The READ
or WRITE that follows then reuses a cached nfsd_file.

With these export options:

  rw,sync,no_subtree_check,fsid=0,sec=krb5p,secure,root_squash

a Kerberos privacy NFSv4.0 client held an OPEN stateid. With the
same stateid, PUTFH; READ over AUTH_SYS was rejected, but PUTFH;
SAVEFH; READ and PUTFH; SAVEFH; WRITE succeeded, and the Kerberos
client subsequently read the changed contents.

SAVEFH leaves CURRENT_FH in place. When deciding whether PUTFH must
report NFS4ERR_WRONGSEC, look past SAVEFH to the first operation
that uses or replaces CURRENT_FH. If another PUTFH replaces it,
leave the earlier PUTFH unchecked, as RFC 8881 Section 2.6.3.1.1
requires. Drop OP_HANDLES_WRONGSEC from SAVEFH, which performs no
check of its own and is never consulted once it is skipped.

Fixes: 68d93184352f ("nfsd4: fix wrongsec handling for PUTFH + op cases")
Signed-off-by: VeroFess <coderzeng@gmail.com>
Link: https://patch.msgid.link/20261029063917.138184-1-coderzeng@gmail.com
Signed-off-by: Chuck Lever <cel@kernel.org>
---
 fs/nfsd/nfs4proc.c | 35 +++++++++++++++++++++--------------
 1 file changed, 21 insertions(+), 14 deletions(-)

diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
index d0290fce9187..50f38d6e147b 100644
--- a/fs/nfsd/nfs4proc.c
+++ b/fs/nfsd/nfs4proc.c
@@ -3237,32 +3237,39 @@ static bool need_wrongsec_check(struct svc_rqst *rqstp)
 	struct nfsd4_compoundres *resp = rqstp->rq_resp;
 	struct nfsd4_compoundargs *argp = rqstp->rq_argp;
 	struct nfsd4_op *this = &argp->ops[resp->opcnt - 1];
-	struct nfsd4_op *next = &argp->ops[resp->opcnt];
+	struct nfsd4_op *next = NULL;
 	const struct nfsd4_operation *thisd = OPDESC(this);
 	const struct nfsd4_operation *nextd;
+	unsigned int i;
 
-	/*
-	 * Most ops check wronsec on our own; only the putfh-like ops
-	 * have special rules.
-	 */
+	/* Only put-filehandle operations need a deferred WRONGSEC check. */
 	if (!(thisd->op_flags & OP_IS_PUTFH_LIKE))
 		return false;
+
 	/*
-	 * rfc 5661 2.6.3.1.1.6: don't bother erroring out a
-	 * put-filehandle operation if we're not going to use the
-	 * result:
+	 * RFC 8881 Section 2.6.3.1.1.1: SAVEFH does not count as the
+	 * next operation.
 	 */
-	if (argp->opcnt == resp->opcnt)
+	for (i = resp->opcnt; i < argp->opcnt; i++) {
+		next = &argp->ops[i];
+		if (next->opnum != OP_SAVEFH)
+			break;
+	}
+
+	/* RFC 8881 Section 2.6.3.1.1.6: nothing uses the filehandle. */
+	if (i == argp->opcnt)
 		return false;
+
 	if (next->opnum == OP_ILLEGAL)
 		return false;
 	nextd = OPDESC(next);
 	/*
-	 * Rest of 2.6.3.1.1: certain operations will return WRONGSEC
-	 * errors themselves as necessary; others should check for them
-	 * now:
+	 * RFC 8881 Section 2.6.3.1.1.2: a later put-filehandle operation
+	 * replaces this one. Other marked operations check WRONGSEC
+	 * themselves.
 	 */
-	return !(nextd->op_flags & OP_HANDLES_WRONGSEC);
+	return !(nextd->op_flags &
+		  (OP_HANDLES_WRONGSEC | OP_IS_PUTFH_LIKE));
 }
 
 #ifdef CONFIG_NFSD_V4_2_INTER_SSC
@@ -4047,7 +4054,7 @@ static const struct nfsd4_operation nfsd4_ops[] = {
 	},
 	[OP_SAVEFH] = {
 		.op_func = nfsd4_savefh,
-		.op_flags = OP_HANDLES_WRONGSEC | OP_MODIFIES_SOMETHING,
+		.op_flags = OP_MODIFIES_SOMETHING,
 		.op_name = "OP_SAVEFH",
 		.op_rsize_bop = nfsd4_only_status_rsize,
 	},

-- 
2.56.0


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

* [PATCH v5 2/3] NFSD: Check export security on the COPY and CLONE source
  2026-10-30 21:27 [PATCH v5 0/3] NFSD: Enforce export security after a deferred PUTFH check Chuck Lever
  2026-10-30 21:27 ` [PATCH v5 1/3] NFSD: Fix PUTFH wrongsec check when SAVEFH follows Chuck Lever
@ 2026-10-30 21:27 ` Chuck Lever
  2026-10-30 21:27 ` [PATCH v5 3/3] NFSD: Enforce export security on LOOKUP, READDIR, and SECINFO Chuck Lever
  2026-10-31 18:57 ` [PATCH v5 0/3] NFSD: Enforce export security after a deferred PUTFH check VeroFess
  3 siblings, 0 replies; 5+ messages in thread
From: Chuck Lever @ 2026-10-30 21:27 UTC (permalink / raw)
  To: VeroFess, Chuck Lever, Jeff Layton, NeilBrown, Olga Kornievskaia,
	Dai Ngo, Tom Talpey
  Cc: linux-nfs

From: VeroFess <coderzeng@gmail.com>

RFC 8881 Section 2.6.3.1.1 forbids PUTFH from returning
NFS4ERR_WRONGSEC when another PUTFH follows, so in PUTFH; SAVEFH;
PUTFH; COPY the source handle reaches nfsd4_verify_copy() with its
export's security flavor unchecked. The source stateid then finds a
cached nfsd_file and the copy proceeds.

On a kernel with only the need_wrongsec_check() fix, an AUTH_SYS
READ of a sec=krb5p source returned NFS4ERR_WRONGSEC, but PUTFH;
SAVEFH; PUTFH; COPY of the same file completed and wrote the source
bytes to the destination.

Check the saved export with check_nfsd_access() before using the
source stateid. Map NFS4ERR_WRONGSEC to NFS4ERR_ACCESS, since
neither COPY nor CLONE permits NFS4ERR_WRONGSEC.

Fixes: 29ae7f9dc21a ("NFSD: Implement the COPY call")
Signed-off-by: VeroFess <coderzeng@gmail.com>
Link: https://patch.msgid.link/20261029063917.138184-1-coderzeng@gmail.com
Signed-off-by: Chuck Lever <cel@kernel.org>
---
 fs/nfsd/nfs4proc.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
index 50f38d6e147b..b6dcb889b5eb 100644
--- a/fs/nfsd/nfs4proc.c
+++ b/fs/nfsd/nfs4proc.c
@@ -1491,6 +1491,11 @@ nfsd4_verify_copy(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
 	if (!cstate->save_fh.fh_dentry)
 		return nfserr_nofilehandle;
 
+	/* A cached open stateid does not authorize the saved source export. */
+	status = check_nfsd_access(cstate->save_fh.fh_export, rqstp);
+	if (status)
+		return status == nfserr_wrongsec ? nfserr_acces : status;
+
 	status = nfs4_preprocess_stateid_op(rqstp, cstate, &cstate->save_fh,
 					    src_stateid, RD_STATE, src, NULL);
 	if (status)

-- 
2.56.0


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

* [PATCH v5 3/3] NFSD: Enforce export security on LOOKUP, READDIR, and SECINFO
  2026-10-30 21:27 [PATCH v5 0/3] NFSD: Enforce export security after a deferred PUTFH check Chuck Lever
  2026-10-30 21:27 ` [PATCH v5 1/3] NFSD: Fix PUTFH wrongsec check when SAVEFH follows Chuck Lever
  2026-10-30 21:27 ` [PATCH v5 2/3] NFSD: Check export security on the COPY and CLONE source Chuck Lever
@ 2026-10-30 21:27 ` Chuck Lever
  2026-10-31 18:57 ` [PATCH v5 0/3] NFSD: Enforce export security after a deferred PUTFH check VeroFess
  3 siblings, 0 replies; 5+ messages in thread
From: Chuck Lever @ 2026-10-30 21:27 UTC (permalink / raw)
  To: VeroFess, Chuck Lever, Jeff Layton, NeilBrown, Olga Kornievskaia,
	Dai Ngo, Tom Talpey
  Cc: linux-nfs

From: VeroFess <coderzeng@gmail.com>

A bind-mounted regular file can have a stricter export than its
parent. nfsd_cross_mnt() checks the request's flavor against the
export being left, not the one being entered, so LOOKUP, READDIR,
and the NFSv2 and NFSv3 CREATE paths all hand out filehandles on
the child export without consulting its sec= list. Stepping up with
".." has the same gap: nfsd_lookup_parent() switches to the parent
export without any check, so LOOKUPP from the root of a sec=sys
export returns a filehandle on a sec=krb5p parent. Every caller has
already passed fh_verify() on the export being left, so check only
the export being entered, in nfsd_cross_mnt() and in
nfsd_lookup_parent().

READDIR crosses into child exports to fetch entry attributes, so
the new check also fires there. When the client requests
rdattr_error, report NFS4ERR_WRONGSEC in that attribute, as RFC
8881 Section 2.6.3.1 directs. Otherwise the whole READDIR fails, as
Section 18.23.3 requires; NFS4ERR_WRONGSEC is not a READDIR status,
so report NFS4ERR_ACCESS. The Linux, FreeBSD, illumos, and Windows
clients all request rdattr_error on every READDIR.

SECINFO and SECINFO_NO_NAME report export security policies. RFC 8881
Section 2.6.3.1.1.5 forbids NFS4ERR_WRONGSEC from either operation,
which allows a client using any flavor to learn whether a name exists
in a directory on a sec=krb5p export. The
starting directory's fh_verify(..., NFSD_MAY_EXEC) call rejects
SECINFO and SECINFO_NO_NAME(PARENT) when that export does not allow
the request's flavor, and a mount crossing rejects it again on the
crossed-into export.

Add NFSD_MAY_SECINFO, which fh_verify() and the lookup path both
read: it skips the starting export's security flavor check and the
flavor check on a mount crossing or parent step-up, bringing NFSD
into conformance with the spec. NFSv4.0 is different: RFC 7530
permits NFS4ERR_WRONGSEC from PUTFH and from neither SECINFO nor
the operation after it, and a v4.0 SECINFO leaves the current
filehandle in place. A v4.0 put-filehandle operation followed by
SECINFO therefore keeps its own export check, so the error is
reported where RFC 7530 allows it. The Linux and illumos clients
send SECINFO only with a parent directory they already hold under
the same flavor, and FreeBSD does not send SECINFO at all, so the
check does not fire for any of them. The export's
transport policy (xprtsec=) is a transport-layer requirement, not
part of the flavor negotiation SECINFO performs, so SECINFO still
enforces it on the starting export and on any export a crossing
enters. Report a transport policy mismatch as NFS4ERR_ACCESS, the
error RFC 8881 Section 18.29.3 specifies for a SECINFO access
failure. The directory type, secure-port, and execute-permission
checks still apply.

Fixes: f0d6ad5a2f67 ("nfsd: move check_nfsd_access() call into nfsd_cross_mnt()")
Fixes: 29a78a3ed7fc ("nfsd4: make fh_verify responsibility of nfsd_lookup_dentry caller")
Fixes: 04f4ad16b231 ("nfsd4: implement secinfo_no_name")
Signed-off-by: VeroFess <coderzeng@gmail.com>
Link: https://patch.msgid.link/20261029063917.138184-1-coderzeng@gmail.com
Signed-off-by: Chuck Lever <cel@kernel.org>
---
 fs/nfsd/export.c    |  6 ++++--
 fs/nfsd/nfs3proc.c  |  2 +-
 fs/nfsd/nfs4proc.c  | 40 ++++++++++++++++++++++++++---------
 fs/nfsd/nfs4state.c |  2 +-
 fs/nfsd/nfs4xdr.c   |  8 +++++--
 fs/nfsd/nfsfh.c     | 12 ++++++++---
 fs/nfsd/nfsproc.c   |  2 +-
 fs/nfsd/trace.h     |  3 ++-
 fs/nfsd/vfs.c       | 60 ++++++++++++++++++++++++++++++++++-------------------
 fs/nfsd/vfs.h       | 17 +++++++++++----
 10 files changed, 106 insertions(+), 46 deletions(-)

diff --git a/fs/nfsd/export.c b/fs/nfsd/export.c
index 397767d756c6..0b4c50d82364 100644
--- a/fs/nfsd/export.c
+++ b/fs/nfsd/export.c
@@ -1976,8 +1976,10 @@ 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
- * one exception is __fh_verify() since it has logic that may result in one
- * or both of the helpers being skipped.
+ * 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 for SECINFO.
  *
  * Return values:
  *   %nfs_ok if access is granted, or
diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c
index 11e05b2e5e84..1025cec447a9 100644
--- a/fs/nfsd/nfs3proc.c
+++ b/fs/nfsd/nfs3proc.c
@@ -961,7 +961,7 @@ nfsd3_create_file(struct svc_rqst *rqstp, struct svc_fh *dirfhp,
 		 */
 		if (d_is_reg(child) &&
 		    unlikely(nfsd_mountpoint(child, exp))) {
-			status = nfsd_cross_mnt(rqstp, &child, &exp);
+			status = nfsd_cross_mnt(rqstp, &child, &exp, 0);
 			if (status != nfs_ok) {
 				exp_put(exp);
 				goto out;
diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
index b6dcb889b5eb..2324e0cce171 100644
--- a/fs/nfsd/nfs4proc.c
+++ b/fs/nfsd/nfs4proc.c
@@ -342,7 +342,7 @@ nfsd4_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
 		    unlikely(nfsd_mountpoint(child, fhp->fh_export))) {
 			struct svc_export *exp = exp_get(fhp->fh_export);
 
-			status = nfsd_cross_mnt(rqstp, &child, &exp);
+			status = nfsd_cross_mnt(rqstp, &child, &exp, 0);
 			if (status == nfs_ok)
 				status = fh_compose(resfhp, exp,
 						    child, fhp);
@@ -1079,7 +1079,17 @@ nfsd4_link(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
 	return status;
 }
 
-static __be32 nfsd4_do_lookupp(struct svc_rqst *rqstp, struct svc_fh *fh)
+/*
+ * RFC 8881 Section 2.6.3.1.1.5 forbids NFS4ERR_WRONGSEC from SECINFO.
+ * Section 18.29.3 has SECINFO report a denied lookup as NFS4ERR_ACCESS.
+ */
+static __be32 nfsd4_secinfo_status(__be32 status)
+{
+	return status == nfserr_wrongsec ? nfserr_acces : status;
+}
+
+static __be32 nfsd4_do_lookupp(struct svc_rqst *rqstp, struct svc_fh *fh,
+			       int access)
 {
 	struct svc_fh tmp_fh;
 	__be32 ret;
@@ -1093,14 +1103,14 @@ static __be32 nfsd4_do_lookupp(struct svc_rqst *rqstp, struct svc_fh *fh)
 		return nfserr_noent;
 	}
 	fh_put(&tmp_fh);
-	return nfsd_lookup(rqstp, fh, "..", 2, fh);
+	return __nfsd_lookup(rqstp, fh, "..", 2, fh, access);
 }
 
 static __be32
 nfsd4_lookupp(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
 	      union nfsd4_op_u *u)
 {
-	return nfsd4_do_lookupp(rqstp, &cstate->current_fh);
+	return nfsd4_do_lookupp(rqstp, &cstate->current_fh, NFSD_MAY_EXEC);
 }
 
 static __be32
@@ -1252,14 +1262,16 @@ nfsd4_secinfo(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
 	struct dentry *dentry;
 	__be32 err;
 
-	err = fh_verify(rqstp, &cstate->current_fh, S_IFDIR, NFSD_MAY_EXEC);
+	err = fh_verify(rqstp, &cstate->current_fh, S_IFDIR,
+			NFSD_MAY_EXEC | NFSD_MAY_SECINFO);
 	if (err)
-		return err;
+		return nfsd4_secinfo_status(err);
 	err = nfsd_lookup_dentry(rqstp, &cstate->current_fh,
 				    secinfo->si_name, secinfo->si_namelen,
-				    &exp, &dentry);
+				    &exp, &dentry,
+				    NFSD_MAY_EXEC | NFSD_MAY_SECINFO);
 	if (err)
-		return err;
+		return nfsd4_secinfo_status(err);
 	if (d_really_is_negative(dentry)) {
 		exp_put(exp);
 		err = nfserr_noent;
@@ -1282,9 +1294,10 @@ nfsd4_secinfo_no_name(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstat
 	case NFS4_SECINFO_STYLE4_CURRENT_FH:
 		break;
 	case NFS4_SECINFO_STYLE4_PARENT:
-		err = nfsd4_do_lookupp(rqstp, &cstate->current_fh);
+		err = nfsd4_do_lookupp(rqstp, &cstate->current_fh,
+				       NFSD_MAY_EXEC | NFSD_MAY_SECINFO);
 		if (err)
-			return err;
+			return nfsd4_secinfo_status(err);
 		break;
 	default:
 		return nfserr_inval;
@@ -3267,6 +3280,13 @@ static bool need_wrongsec_check(struct svc_rqst *rqstp)
 
 	if (next->opnum == OP_ILLEGAL)
 		return false;
+	/*
+	 * RFC 7530 permits NFS4ERR_WRONGSEC from PUTFH but not from
+	 * SECINFO, and NFSv4.0 SECINFO leaves the current filehandle in
+	 * place for the next operation.
+	 */
+	if (resp->cstate.minorversion == 0 && next->opnum == OP_SECINFO)
+		return true;
 	nextd = OPDESC(next);
 	/*
 	 * RFC 8881 Section 2.6.3.1.1.2: a later put-filehandle operation
diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
index cafdf80f32de..97b4fa30362a 100644
--- a/fs/nfsd/nfs4state.c
+++ b/fs/nfsd/nfs4state.c
@@ -7126,7 +7126,7 @@ nfsd4_verify_deleg_dentry(struct nfsd4_open *open, struct nfs4_file *fp,
 
 	err = nfsd_lookup_dentry(open->op_rqstp, parent,
 				 open->op_fname, open->op_fnamelen,
-				 &exp, &child);
+				 &exp, &child, 0);
 
 	if (err)
 		return -ESTALE;
diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c
index d4a88bef8444..111bf2b8ebd1 100644
--- a/fs/nfsd/nfs4xdr.c
+++ b/fs/nfsd/nfs4xdr.c
@@ -4676,7 +4676,7 @@ nfsd4_encode_entry4_fattr(struct nfsd4_readdir *cd, const char *name,
 		 * Different "."/".." handling?  Something else?
 		 * At least, add a comment here to explain....
 		 */
-		nfserr = nfsd_cross_mnt(cd->rd_rqstp, &dentry, &exp);
+		nfserr = nfsd_cross_mnt(cd->rd_rqstp, &dentry, &exp, 0);
 		if (nfserr)
 			goto out_put;
 		crossed = true;
@@ -4775,8 +4775,12 @@ nfsd4_encode_entry4(void *ccdv, const char *name, int namlen,
 		 * then in accordance with the spec, we fail the
 		 * entire READDIR operation(!)
 		 */
-		if (!(cd->rd_bmval[0] & FATTR4_WORD0_RDATTR_ERROR))
+		if (!(cd->rd_bmval[0] & FATTR4_WORD0_RDATTR_ERROR)) {
+			/* READDIR cannot return NFS4ERR_WRONGSEC itself. */
+			if (nfserr == nfserr_wrongsec)
+				nfserr = nfserr_acces;
 			goto fail;
+		}
 		if (nfsd4_encode_entry4_rdattr_error(xdr, nfserr)) {
 			nfserr = nfserr_toosmall;
 			goto fail;
diff --git a/fs/nfsd/nfsfh.c b/fs/nfsd/nfsfh.c
index f1ba5a14bb85..f9fe985c7433 100644
--- a/fs/nfsd/nfsfh.c
+++ b/fs/nfsd/nfsfh.c
@@ -471,9 +471,15 @@ __fh_verify(struct svc_rqst *rqstp,
 			&& exp->ex_path.dentry == dentry)
 		may_bypass_gss = true;
 
-	error = check_security_flavor(exp, rqstp, may_bypass_gss);
-	if (error)
-		goto out;
+	/*
+	 * SECINFO reports the export's security flavors instead of
+	 * failing on a flavor mismatch. The xprtsec= policy still applies.
+	 */
+	if (!(access & NFSD_MAY_SECINFO)) {
+		error = check_security_flavor(exp, rqstp, may_bypass_gss);
+		if (error)
+			goto out;
+	}
 
 	svc_xprt_set_valid(rqstp->rq_xprt);
 
diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c
index 2f8da62ad6b0..43e6a4b6a259 100644
--- a/fs/nfsd/nfsproc.c
+++ b/fs/nfsd/nfsproc.c
@@ -712,7 +712,7 @@ nfsd_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
 	exp = exp_get(fhp->fh_export);
 	if (d_is_reg(dchild) &&
 	    unlikely(nfsd_mountpoint(dchild, exp))) {
-		status = nfsd_cross_mnt(rqstp, &dchild, &exp);
+		status = nfsd_cross_mnt(rqstp, &dchild, &exp, 0);
 		if (status != nfs_ok) {
 			exp_put(exp);
 			goto out;
diff --git a/fs/nfsd/trace.h b/fs/nfsd/trace.h
index e7b186307c21..b937217b81d2 100644
--- a/fs/nfsd/trace.h
+++ b/fs/nfsd/trace.h
@@ -142,7 +142,8 @@ DEFINE_NFSD_DYNTHREAD_EVENT(trylock_fail);
 		{ NFSD_MAY_BYPASS_GSS,		"BYPASS_GSS" },		\
 		{ NFSD_MAY_READ_IF_EXEC,	"READ_IF_EXEC" },	\
 		{ NFSD_MAY_64BIT_COOKIE,	"64BIT_COOKIE" },	\
-		{ NFSD_MAY_LOCALIO,		"LOCALIO" })
+		{ NFSD_MAY_LOCALIO,		"LOCALIO" },	\
+		{ NFSD_MAY_SECINFO,		"SECINFO" })
 
 TRACE_EVENT(nfsd_compound,
 	TP_PROTO(
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index a8cf41e4dfea..d5bb6c7a35af 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -124,14 +124,13 @@ nfserrno(int errno)
 }
 
 /*
- * Called from nfsd_lookup and encode_dirent. Check if we have crossed
- * a mount point.
+ * Check if we have crossed a mount point.
  * Returns an nfs error leaving *dpp and *expp unchanged,
  *  or nfs_ok having possibly changed *dpp and *expp
  */
 __be32
 nfsd_cross_mnt(struct svc_rqst *rqstp, struct dentry **dpp,
-	       struct svc_export **expp)
+	       struct svc_export **expp, int access)
 {
 	struct svc_export *exp = *expp, *exp2 = NULL;
 	struct dentry *dentry = *dpp;
@@ -169,7 +168,9 @@ 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 = check_nfsd_access(exp, rqstp);
+		nfserr = (access & NFSD_MAY_SECINFO) ?
+			 check_xprtsec_policy(exp2, rqstp) :
+			 check_nfsd_access(exp2, rqstp);
 		if (nfserr == nfs_ok) {
 			/* successfully crossed mount point */
 			/*
@@ -204,11 +205,14 @@ static void follow_to_parent(struct path *path)
 	path->dentry = dp;
 }
 
-static int nfsd_lookup_parent(struct svc_rqst *rqstp, struct dentry *dparent, struct svc_export **exp, struct dentry **dentryp)
+static __be32 nfsd_lookup_parent(struct svc_rqst *rqstp, struct dentry *dparent,
+				 struct svc_export **exp, struct dentry **dentryp,
+				 int access)
 {
 	struct svc_export *exp2;
 	struct path path = {.mnt = mntget((*exp)->ex_path.mnt),
 			    .dentry = dget(dparent)};
+	__be32 nfserr = nfs_ok;
 
 	follow_to_parent(&path);
 
@@ -216,15 +220,21 @@ static int nfsd_lookup_parent(struct svc_rqst *rqstp, struct dentry *dparent, st
 	if (PTR_ERR(exp2) == -ENOENT) {
 		*dentryp = dget(dparent);
 	} else if (IS_ERR(exp2)) {
-		path_put(&path);
-		return PTR_ERR(exp2);
+		nfserr = nfserrno(PTR_ERR(exp2));
 	} else {
-		*dentryp = dget(path.dentry);
-		exp_put(*exp);
-		*exp = exp2;
+		nfserr = (access & NFSD_MAY_SECINFO) ?
+			 check_xprtsec_policy(exp2, rqstp) :
+			 check_nfsd_access(exp2, rqstp);
+		if (nfserr != nfs_ok) {
+			exp_put(exp2);
+		} else {
+			*dentryp = dget(path.dentry);
+			exp_put(*exp);
+			*exp = exp2;
+		}
 	}
 	path_put(&path);
-	return 0;
+	return nfserr;
 }
 
 /*
@@ -255,7 +265,8 @@ int nfsd_mountpoint(struct dentry *dentry, struct svc_export *exp)
 __be32
 nfsd_lookup_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp,
 		   const char *name, unsigned int len,
-		   struct svc_export **exp_ret, struct dentry **dentry_ret)
+		   struct svc_export **exp_ret, struct dentry **dentry_ret,
+		   int access)
 {
 	struct svc_export	*exp;
 	struct dentry		*dparent;
@@ -277,9 +288,13 @@ nfsd_lookup_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp,
 			dentry = dget(dparent); /* .. == . just like at / */
 		else {
 			/* checking mountpoint crossing is very different when stepping up */
-			host_err = nfsd_lookup_parent(rqstp, dparent, &exp, &dentry);
-			if (host_err)
-				goto out_nfserr;
+			__be32 nfserr = nfsd_lookup_parent(rqstp, dparent,
+							   &exp, &dentry, access);
+
+			if (nfserr) {
+				exp_put(exp);
+				return nfserr;
+			}
 		}
 	} else {
 		dentry = lookup_one_unlocked(&nop_mnt_idmap,
@@ -288,7 +303,8 @@ nfsd_lookup_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp,
 		if (IS_ERR(dentry))
 			goto out_nfserr;
 		if (nfsd_mountpoint(dentry, exp)) {
-			__be32 nfserr = nfsd_cross_mnt(rqstp, &dentry, &exp);
+			__be32 nfserr = nfsd_cross_mnt(rqstp, &dentry, &exp,
+						       access);
 
 			if (nfserr) {
 				dput(dentry);
@@ -307,13 +323,15 @@ nfsd_lookup_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp,
 }
 
 /**
- * nfsd_lookup - look up a single path component for nfsd
+ * __nfsd_lookup - look up a single path component for nfsd
  *
  * @rqstp:   the request context
  * @fhp:     the file handle of the directory
  * @name:    the component name, or %NULL to look up parent
  * @len:     length of name to examine
  * @resfh:   pointer to pre-initialised filehandle to hold result.
+ * @access:  NFSD_MAY_* flags for the fh_verify() of @fhp; adding
+ *	     NFSD_MAY_SECINFO skips the export security flavor checks
  *
  * Look up one component of a pathname.
  * N.B. After this call _both_ fhp and resfh need an fh_put
@@ -327,17 +345,17 @@ nfsd_lookup_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp,
  *
  */
 __be32
-nfsd_lookup(struct svc_rqst *rqstp, struct svc_fh *fhp, const char *name,
-	    unsigned int len, struct svc_fh *resfh)
+__nfsd_lookup(struct svc_rqst *rqstp, struct svc_fh *fhp, const char *name,
+	      unsigned int len, struct svc_fh *resfh, int access)
 {
 	struct svc_export	*exp;
 	struct dentry		*dentry;
 	__be32 err;
 
-	err = fh_verify(rqstp, fhp, S_IFDIR, NFSD_MAY_EXEC);
+	err = fh_verify(rqstp, fhp, S_IFDIR, access);
 	if (err)
 		return err;
-	err = nfsd_lookup_dentry(rqstp, fhp, name, len, &exp, &dentry);
+	err = nfsd_lookup_dentry(rqstp, fhp, name, len, &exp, &dentry, access);
 	if (err)
 		return err;
 	/*
diff --git a/fs/nfsd/vfs.h b/fs/nfsd/vfs.h
index 3cac25adf16b..12cf14a8777a 100644
--- a/fs/nfsd/vfs.h
+++ b/fs/nfsd/vfs.h
@@ -33,6 +33,7 @@
 #define NFSD_MAY_64BIT_COOKIE		0x1000 /* 64 bit readdir cookies for >= NFSv3 */
 
 #define NFSD_MAY_LOCALIO		0x2000 /* for tracing, reflects when localio used */
+#define NFSD_MAY_SECINFO		0x4000 /* query export security policy */
 
 #define NFSD_MAY_CREATE		(NFSD_MAY_EXEC|NFSD_MAY_WRITE)
 #define NFSD_MAY_REMOVE		(NFSD_MAY_EXEC|NFSD_MAY_WRITE|NFSD_MAY_TRUNC)
@@ -128,12 +129,20 @@ void		nfsd_maybe_reset_write_verifier(struct nfsd_net *nn,
 						struct svc_rqst *rqstp,
 						int err);
 __be32		nfsd_cross_mnt(struct svc_rqst *rqstp, struct dentry **dpp,
-			       struct svc_export **expp);
-__be32		nfsd_lookup(struct svc_rqst *, struct svc_fh *,
-				const char *, unsigned int, struct svc_fh *);
+			       struct svc_export **expp, int access);
+__be32		__nfsd_lookup(struct svc_rqst *rqstp, struct svc_fh *fhp,
+			      const char *name, unsigned int len,
+			      struct svc_fh *resfh, int access);
+static inline __be32 nfsd_lookup(struct svc_rqst *rqstp, struct svc_fh *fhp,
+				 const char *name, unsigned int len,
+				 struct svc_fh *resfh)
+{
+	return __nfsd_lookup(rqstp, fhp, name, len, resfh, NFSD_MAY_EXEC);
+}
 __be32		 nfsd_lookup_dentry(struct svc_rqst *, struct svc_fh *,
 				const char *, unsigned int,
-				struct svc_export **, struct dentry **);
+				struct svc_export **, struct dentry **,
+				int access);
 __be32		nfsd_setattr(struct svc_rqst *, struct svc_fh *,
 			     struct nfsd_attrs *, const struct timespec64 *);
 int nfsd_mountpoint(struct dentry *, struct svc_export *);

-- 
2.56.0


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

* Re: [PATCH v5 0/3] NFSD: Enforce export security after a deferred PUTFH check
  2026-10-30 21:27 [PATCH v5 0/3] NFSD: Enforce export security after a deferred PUTFH check Chuck Lever
                   ` (2 preceding siblings ...)
  2026-10-30 21:27 ` [PATCH v5 3/3] NFSD: Enforce export security on LOOKUP, READDIR, and SECINFO Chuck Lever
@ 2026-10-31 18:57 ` VeroFess
  3 siblings, 0 replies; 5+ messages in thread
From: VeroFess @ 2026-10-31 18:57 UTC (permalink / raw)
  To: cel; +Cc: jlayton, neil, okorniev, Dai.Ngo, tom, linux-nfs

On Sat, Oct 31, 2026 at 05:27:34AM +0800, Chuck Lever wrote:
> The series as posted has not been run against the reproducer yet.

Thanks for splitting the series. I tested it incrementally in an
x86_64 KASAN guest on d9130c9f6535: patch 1 alone, then patches 1 and
2, then the full series. I'm happy to test further revisions.

With only patch 1 applied, the NFSv4.0 reproducer returned
NFS4ERR_WRONGSEC at PUTFH for AUTH_SYS PUTFH; SAVEFH; READ and
PUTFH; SAVEFH; WRITE. The file was unchanged and the Kerberos client
could still read it, so patch 1 by itself stops the reported sequence.

Adding patch 2: with patch 1 only, an AUTH_SYS local COPY of a krb5p
source still completed and changed the destination; with patch 2 the
saved-source check made COPY and CLONE return NFS4ERR_ACCESS and leave
the destination unchanged. A permitted AUTH_SYS local COPY still
succeeded.

Adding patch 3: LOOKUPP into a krb5p parent, which succeeded before
patch 3, returned WRONGSEC, while SECINFO_NO_NAME(PARENT) still
succeeded. READDIR with rdattr_error reported WRONGSEC for the strict
child, and the NFSv2/v3 CREATE cases returned ACCESS. Over plaintext
TCP, SECINFO into an xprtsec=tls child returned NFS4ERR_ACCESS while
LOOKUP returned WRONGSEC.

On the full series I reran the earlier regression suite: Btrfs local
COPY and reflink CLONE succeeded with matching destination bytes, and
asynchronous inter-server COPY completed 4096 bytes between two guests.

I also rebuilt the full series with CONFIG_TLS=y for an RPC-over-TLS
run. On NFSv4.0, v4.1, and v4.2, a plaintext client could read the
parent export but could not enter its xprtsec=tls child; a TLS client
could read the child. For an xprtsec=mtls child, TLS without a client
certificate was denied, and mTLS with a trusted certificate succeeded
on all three versions. A raw NFSv4.0 PUTROOTFH; SECINFO request
returned NFS4_OK over both TLS and mTLS.

The W=1 fs/nfsd/nfs4proc.o build and the 259-case ASan/UBSan check
passed, with no KASAN reports.

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

Thanks,
VeroFess

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

end of thread, other threads:[~2026-10-31 18:57 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-30 21:27 [PATCH v5 0/3] NFSD: Enforce export security after a deferred PUTFH check Chuck Lever
2026-10-30 21:27 ` [PATCH v5 1/3] NFSD: Fix PUTFH wrongsec check when SAVEFH follows Chuck Lever
2026-10-30 21:27 ` [PATCH v5 2/3] NFSD: Check export security on the COPY and CLONE source Chuck Lever
2026-10-30 21:27 ` [PATCH v5 3/3] NFSD: Enforce export security on LOOKUP, READDIR, and SECINFO Chuck Lever
2026-10-31 18:57 ` [PATCH v5 0/3] NFSD: Enforce export security after a deferred PUTFH check VeroFess

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.