* [PATCH 0/3] NFSD: Apply all export checks when a compound changes exports
@ 2026-11-03 1:32 VeroFess
2026-11-03 1:32 ` [PATCH 1/3] NFSD: Check the secure-port policy when entering an export VeroFess
` (3 more replies)
0 siblings, 4 replies; 5+ messages in thread
From: VeroFess @ 2026-11-03 1:32 UTC (permalink / raw)
To: linux-nfs; +Cc: cel, jlayton
When a compound moves the current filehandle into another export,
NFSD does not always apply all of that export's checks. These patches
fix three such paths: LOOKUP entering an export, RESTOREFH, and COPY
and CLONE with a source and destination in different exports. They
apply to nfsd-testing at 08f97dfc517b.
In each case, the requester must already hold an OPEN stateid that
NFSD granted after a full export check.
Patch 1 checks the secure-port setting of an export entered by LOOKUP.
An AUTH_SYS request from an unreserved port can no longer enter a
secure export that PUTFH of the same filehandle would reject.
Patch 2 applies the entered export's credentials after LOOKUP crosses
into another export and after RESTOREFH, so root_squash and all_squash
follow the export that owns the current filehandle.
Patch 3 verifies the COPY and CLONE source and destination
filehandles, so each stateid is checked with the credentials of its
own export.
The combined tree was built for x86-64 with KASAN, kernel TLS and
NFSD_V4_2_INTER_SSC, and tested in a guest on NFSv4.0, v4.1 and v4.2.
The test results are below the --- line of each patch. The guest log
showed no KASAN report or oops. Each patch also applies and builds
with W=1 on its own.
VeroFess (3):
NFSD: Check the secure-port policy when entering an export
NFSD: Apply the entered export's credentials after LOOKUP and
RESTOREFH
NFSD: Verify COPY and CLONE source and destination filehandles
fs/nfsd/export.c | 8 ++++++--
fs/nfsd/nfs4proc.c | 18 +++++++++++++++---
fs/nfsd/nfsfh.c | 26 ++++++++++++++++++++++----
fs/nfsd/nfsfh.h | 2 ++
fs/nfsd/vfs.c | 26 ++++++++++++++++++++------
5 files changed, 65 insertions(+), 15 deletions(-)
base-commit: 08f97dfc517b50a00a41ce99f5af7b6ce2814592
--
2.43.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 1/3] NFSD: Check the secure-port policy when entering an export
2026-11-03 1:32 [PATCH 0/3] NFSD: Apply all export checks when a compound changes exports VeroFess
@ 2026-11-03 1:32 ` VeroFess
2026-11-03 1:32 ` [PATCH 2/3] NFSD: Apply the entered export's credentials after LOOKUP and RESTOREFH VeroFess
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: VeroFess @ 2026-11-03 1:32 UTC (permalink / raw)
To: linux-nfs; +Cc: cel, jlayton
LOOKUP checks the parent filehandle before crossing a mountpoint.
The crossing checks the entered export's flavor and transport policy,
but not its secure-port setting. An AUTH_SYS request from an
unreserved port can therefore enter a secure export by LOOKUP,
although PUTFH of the same filehandle returns NFS4ERR_PERM. The
requester must already hold an OPEN stateid for the file for the
demonstrated READ and CLONE paths; the reproducer obtains it from a
reserved port before sending the requests from an unreserved port.
Check the port whenever check_nfsd_access() revalidates an export.
Use the same port helper as fh_verify(), including SECINFO's flavor
bypass path. Map a port denial to NFS4ERR_ACCESS in SECINFO and
SECINFO_NO_NAME, as required for a denied name lookup.
The omission is old; it dates back at least to Linux 2.6.12.
Maintained LTS branches with this crossing path also need the check.
Signed-off-by: VeroFess <coderzeng@gmail.com>
---
The combined fix tree was tested in an x86-64 KASAN guest. High-port
AUTH_SYS LOOKUP returned NFS4ERR_PERM, named SECINFO returned
NFS4ERR_ACCESS, and the CLONE destination was unchanged. Low-port COPY
and CLONE and high-port GSS privacy access succeeded. This patch also
built alone with W=1.
fs/nfsd/export.c | 8 ++++++--
fs/nfsd/nfs4proc.c | 4 +++-
fs/nfsd/nfsfh.c | 26 ++++++++++++++++++++++----
fs/nfsd/nfsfh.h | 2 ++
fs/nfsd/vfs.c | 21 +++++++++++++++------
5 files changed, 48 insertions(+), 13 deletions(-)
diff --git a/fs/nfsd/export.c b/fs/nfsd/export.c
index f0dd9fb..3bdc6b8 100644
--- a/fs/nfsd/export.c
+++ b/fs/nfsd/export.c
@@ -2077,13 +2077,17 @@ __be32 check_security_flavor(struct svc_export *exp, struct svc_rqst *rqstp,
* @rqstp: svc_rqst attempting to access @exp.
*
* Return values:
- * %nfs_ok if access is granted, or
- * %nfserr_wrongsec if access is denied
+ * %nfs_ok if access is granted, %nfserr_perm if the
+ * originating port is not allowed, or %nfserr_wrongsec
+ * if the transport or flavor is not allowed.
*/
__be32 check_nfsd_access(struct svc_export *exp, struct svc_rqst *rqstp)
{
__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;
diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
index 2c65302..43c2aa9 100644
--- a/fs/nfsd/nfs4proc.c
+++ b/fs/nfsd/nfs4proc.c
@@ -1086,7 +1086,9 @@ nfsd4_link(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
*/
static __be32 nfsd4_secinfo_status(__be32 status)
{
- return status == nfserr_wrongsec ? nfserr_acces : status;
+ if (status == nfserr_wrongsec || status == nfserr_perm)
+ return nfserr_acces;
+ return status;
}
static __be32 nfsd4_do_lookupp(struct svc_rqst *rqstp, struct svc_fh *fh,
diff --git a/fs/nfsd/nfsfh.c b/fs/nfsd/nfsfh.c
index 0939146..4b5280e 100644
--- a/fs/nfsd/nfsfh.c
+++ b/fs/nfsd/nfsfh.c
@@ -102,11 +102,17 @@ 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: credentials used for export option checks
+ * @exp: export being checked
+ *
+ * Return: %nfs_ok if allowed, %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];
@@ -116,6 +122,18 @@ static __be32 nfsd_setuser_and_check_port(struct svc_rqst *rqstp,
}
return 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 aa3ff30..90b6429 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 269dc49..2775911 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -123,6 +123,19 @@ nfserrno(int errno)
return nfserr_io;
}
+static __be32 nfsd_check_crossing_access(struct svc_rqst *rqstp,
+ struct svc_export *exp, int access)
+{
+ __be32 status;
+
+ if (!(access & NFSD_MAY_BYPASS_FLAVOR))
+ return check_nfsd_access(exp, rqstp);
+ status = nfsd_check_port(rqstp, &rqstp->rq_cred, exp);
+ if (status)
+ return status;
+ return check_xprtsec_policy(exp, rqstp);
+}
+
/*
* Check if we have crossed a mount point.
* Returns an nfs error leaving *dpp and *expp unchanged,
@@ -168,9 +181,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 = nfsd_check_crossing_access(rqstp, exp2, access);
if (nfserr == nfs_ok) {
/* successfully crossed mount point */
/*
@@ -222,9 +233,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 = nfsd_check_crossing_access(rqstp, exp2, access);
if (nfserr != nfs_ok) {
exp_put(exp2);
} else {
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH 2/3] NFSD: Apply the entered export's credentials after LOOKUP and RESTOREFH
2026-11-03 1:32 [PATCH 0/3] NFSD: Apply all export checks when a compound changes exports VeroFess
2026-11-03 1:32 ` [PATCH 1/3] NFSD: Check the secure-port policy when entering an export VeroFess
@ 2026-11-03 1:32 ` VeroFess
2026-11-03 1:32 ` [PATCH 3/3] NFSD: Verify COPY and CLONE source and destination filehandles VeroFess
2026-11-03 14:06 ` [PATCH 0/3] NFSD: Apply all export checks when a compound changes exports Chuck Lever
3 siblings, 0 replies; 5+ messages in thread
From: VeroFess @ 2026-11-03 1:32 UTC (permalink / raw)
To: linux-nfs; +Cc: cel, jlayton
LOOKUP verifies its parent filehandle before selecting a child export.
After the crossing, the task can still use credentials mapped by the
parent export. RESTOREFH likewise restores a saved filehandle without
restoring its export's credentials. The requester must already hold an
OPEN stateid for the file: with a cached stateid, READ can retain root
access after the export enables root_squash, or fail even though
all_squash would allow access through its anonymous UID or GID.
Verify the composed filehandle when LOOKUP selects another export.
After RESTOREFH, install the saved export's credentials and propagate
an error from nfsd_setuser(). Keep the existing deferred flavor
decision for RESTOREFH.
The credential mismatch is old; it dates back at least to Linux
2.6.12. Maintained LTS branches with these filehandle paths also need
the correction.
Signed-off-by: VeroFess <coderzeng@gmail.com>
---
On nfsd-testing with all three fixes, crossed and restored READ used the
child export's root_squash or all_squash mapping, including anonymous
UID 2000 and GID 2001. In a separate fault-injection build of the same
NFSD code, RESTOREFH returned NFS4ERR_DELAY before GETATTR. This patch
also built alone with W=1.
fs/nfsd/nfs4proc.c | 4 +++-
fs/nfsd/vfs.c | 5 +++++
2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
index 43c2aa9..218cedb 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"
@@ -859,7 +860,8 @@ nfsd4_restorefh(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
memcpy(&cstate->current_stateid, &cstate->save_stateid, sizeof(stateid_t));
SET_CSTATE_FLAG(cstate, CURRENT_STATE_ID_FLAG);
}
- return nfs_ok;
+ return nfserrno(nfsd_setuser(&rqstp->rq_cred,
+ cstate->current_fh.fh_export));
}
static __be32
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index 2775911..0547029 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -351,6 +351,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);
@@ -363,9 +364,13 @@ __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;
+ if (!err && crossed)
+ err = fh_verify(rqstp, resfh, 0,
+ access & NFSD_MAY_BYPASS_FLAVOR);
dput(dentry);
exp_put(exp);
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH 3/3] NFSD: Verify COPY and CLONE source and destination filehandles
2026-11-03 1:32 [PATCH 0/3] NFSD: Apply all export checks when a compound changes exports VeroFess
2026-11-03 1:32 ` [PATCH 1/3] NFSD: Check the secure-port policy when entering an export VeroFess
2026-11-03 1:32 ` [PATCH 2/3] NFSD: Apply the entered export's credentials after LOOKUP and RESTOREFH VeroFess
@ 2026-11-03 1:32 ` VeroFess
2026-11-03 14:06 ` [PATCH 0/3] NFSD: Apply all export checks when a compound changes exports Chuck Lever
3 siblings, 0 replies; 5+ messages in thread
From: VeroFess @ 2026-11-03 1:32 UTC (permalink / raw)
To: linux-nfs; +Cc: cel, jlayton
nfsd4_verify_copy() checks the saved source export's flavor and
transport policy, then resolves its stateid. With a cached OPEN
stateid, the file permission check can use credentials left by PUTFH
on the destination export. This can retain root access after the
source export enables root_squash, or reject a source that all_squash
maps to its anonymous UID or GID. The requester must already hold an
OPEN stateid for each file in the reproducer, both source and
destination.
Verify the saved filehandle before resolving the source stateid.
This checks its port requirement and installs the source export's
mapped credentials. Verify the destination filehandle before
resolving its stateid to restore the credentials for the write.
Continue mapping NFS4ERR_WRONGSEC to NFS4ERR_ACCESS for COPY and CLONE.
Fixes: 29ae7f9dc21a ("NFSD: Implement the COPY call")
Signed-off-by: VeroFess <coderzeng@gmail.com>
---
On the combined fix tree, saved-source COPY with root_squash was denied
without changing the destination; permitted COPY and CLONE matched the
source bytes. This patch also built alone with W=1.
fs/nfsd/nfs4proc.c | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
index 218cedb..774ba9e 100644
--- a/fs/nfsd/nfs4proc.c
+++ b/fs/nfsd/nfs4proc.c
@@ -1514,7 +1514,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 = fh_verify(rqstp, &cstate->save_fh, 0, NFSD_MAY_NOP);
if (status)
return status == nfserr_wrongsec ? nfserr_acces : status;
@@ -1523,6 +1523,14 @@ nfsd4_verify_copy(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
if (status)
goto out;
+ /* Use the destination export's credentials for the write. */
+ status = fh_verify(rqstp, &cstate->current_fh, 0, NFSD_MAY_NOP);
+ if (status) {
+ if (status == nfserr_wrongsec)
+ status = nfserr_acces;
+ goto out_put_src;
+ }
+
status = nfs4_preprocess_stateid_op(rqstp, cstate, &cstate->current_fh,
dst_stateid, WR_STATE, dst, NULL);
if (status)
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH 0/3] NFSD: Apply all export checks when a compound changes exports
2026-11-03 1:32 [PATCH 0/3] NFSD: Apply all export checks when a compound changes exports VeroFess
` (2 preceding siblings ...)
2026-11-03 1:32 ` [PATCH 3/3] NFSD: Verify COPY and CLONE source and destination filehandles VeroFess
@ 2026-11-03 14:06 ` Chuck Lever
3 siblings, 0 replies; 5+ messages in thread
From: Chuck Lever @ 2026-11-03 14:06 UTC (permalink / raw)
To: VeroFess; +Cc: linux-nfs, jlayton
On Tue, 03 Nov 2026 09:32:34 +0800, VeroFess wrote:
> When a compound moves the current filehandle into another export,
> NFSD does not always apply all of that export's checks.
Thanks for the report and the patches.
Two comments about the series: first, LOOKUP is a frequent operation,
so I want to be careful about adding more logic there; second, there
are better ways to layer the helpers, IMO. I'll run with this, and
add a Reported-by: VeroFess to each patch before posting here for
review.
--
Chuck Lever (Come to NFS bake-a-thon! https://nfsv4bat.org)
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-11-03 14:06 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-03 1:32 [PATCH 0/3] NFSD: Apply all export checks when a compound changes exports VeroFess
2026-11-03 1:32 ` [PATCH 1/3] NFSD: Check the secure-port policy when entering an export VeroFess
2026-11-03 1:32 ` [PATCH 2/3] NFSD: Apply the entered export's credentials after LOOKUP and RESTOREFH VeroFess
2026-11-03 1:32 ` [PATCH 3/3] NFSD: Verify COPY and CLONE source and destination filehandles VeroFess
2026-11-03 14:06 ` [PATCH 0/3] NFSD: Apply all export checks when a compound changes exports Chuck Lever
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.