FAILED: patch "[PATCH] NFSD: check truncate permission under inode lock" failed to apply to 5.10-stable tree
Subsystems (2)
[email protected]
maintainerThe patch below does not apply to the 5.10-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-5.10.y
git checkout FETCH_HEAD
git cherry-pick -x b778e0e0a16759f22a70579c3cf8d254a40d4a7f
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026090340-darkness-disposal-745f@gregkh' --subject-prefix 'PATCH 5.10.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From b778e0e0a16759f22a70579c3cf8d254a40d4a7f Mon Sep 17 00:00:00 2001
From: Chuck Lever <[email protected]>
Date: Sat, 30 May 2026 09:19:22 -0400
Subject: [PATCH] NFSD: check truncate permission under inode lock
nfsd_setattr() checks whether a size update needs NFSD_MAY_TRUNC before it takes inode_lock(). The comparison uses the file size sampled by that unlocked read, but the actual ATTR_SIZE update is applied later under inode_lock() by notify_change().
This leaves a TOCTOU window for append-only files. If a client sends a SETATTR that does not shrink the file at the time of the unlocked sample, a concurrent append can extend the file before nfsd_setattr() takes inode_lock(). notify_change() then applies a real truncation without the NFSD_MAY_TRUNC check that rejects IS_APPEND(inode). The VFS truncate syscall paths perform their own append-only checks before calling notify_change(), so NFSD must make this decision against the locked size it is about to change.
Split the write-count acquisition from the truncation permission check. Keep get_write_access() before the locked setattr work, then recheck whether the requested size is below i_size_read(inode) after inode_lock() has been acquired and before notify_change(ATTR_SIZE). This also avoids the plain unlocked inode->i_size load.
fs/nfsd/vfs.c +18 -12
@@ -419,21 +419,22 @@ nfsd_sanitize_attrs(struct inode *inode, struct iattr *iap) }static __be32 -nfsd_get_write_access (struct svc_rqst *rqstp,struct svc_fh *fhp, -struct iattr *iap) +nfsd_may_truncate (struct svc_rqst *rqstp,struct svc_fh *fhp, +struct iattr *iap) {struct inode *inode =d_inode (fhp->fh_dentry); -if (iap->ia_size < inode->i_size) { -__be32 err; +if (iap->ia_size >=i_size_read (inode)) +return nfs_ok; - err =nfsd_permission (&rqstp->rq_cred, - fhp->fh_export, fhp->fh_dentry, -NFSD_MAY_TRUNC |NFSD_MAY_OWNER_OVERRIDE ); -if (err) -return err; - } -return nfserrno (get_write_access (inode)); +return nfsd_permission (&rqstp->rq_cred, fhp->fh_export, fhp->fh_dentry, +NFSD_MAY_TRUNC |NFSD_MAY_OWNER_OVERRIDE ); +} + +static __be32 +nfsd_get_write_access (struct svc_fh *fhp) +{ +return nfserrno (get_write_access (d_inode (fhp->fh_dentry))); }static int __nfsd_setattr (struct dentry *dentry,struct iattr *iap)
@@ -560,12 +561,17 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp,* setattr call. */ if (size_change) { - err =nfsd_get_write_access (rqstp, fhp, iap); + err =nfsd_get_write_access (fhp);if (err)return err; }inode_lock (inode); +if (size_change) { + err =nfsd_may_truncate (rqstp, fhp, iap); +if (err) + goto out_unlock; + } err =fh_fill_pre_attrs (fhp);if (err) goto out_unlock;
Sasha Levin
maintainerPATCH 1/2 nfsd: handle failure to collect pre/post-op attrs more sanely
From: Jeff Layton <[email protected]>
[ Upstream commit a332018a91c419a9a475c41d827544c771986876 ]
Collecting pre_op_attrs can fail, in which case it's probably best to fail the whole operation.
Change fh_fill_pre_attrs and fh_fill_both_attrs to return __be32, and have the callers check the return code and abort the operation if it's not nfs_ok.
Stable-dep-of: b778e0e0a167 ("NFSD: check truncate permission under inode lock")
--- fs/nfsd/auth.c | 15 ++++---- fs/nfsd/auth.h | 2 +- fs/nfsd/export.h | 3 +- fs/nfsd/nfs3proc.c | 4 +- fs/nfsd/nfs4proc.c | 14 ++++--- fs/nfsd/nfs4state.c | 5 ++- fs/nfsd/nfsfh.c | 45 +++++++++++----------- fs/nfsd/nfsfh.h | 6 +-- fs/nfsd/nfsproc.c | 9 +++-- fs/nfsd/vfs.c | 94 ++++++++++++++++++++++++++------------------- fs/nfsd/vfs.h | 4 +- 11 files changed, 112 insertions(+), 89 deletions(-)
fs/nfsd/auth.c +7 -8
@@ -5,26 +5,26 @@#include "nfsd.h" #include "auth.h" -int nfsexp_flags (struct svc_rqst *rqstp,struct svc_export *exp) +int nfsexp_flags (struct svc_cred *cred,struct svc_export *exp) {struct exp_flavor_info *f;struct exp_flavor_info *end = exp->ex_flavors + exp->ex_nflavors;for (f = exp->ex_flavors; f < end; f++) { -if (f->pseudoflavor == rqstp->rq_cred.cr_flavor) +if (f->pseudoflavor == cred->cr_flavor)return f->flags; }return exp->ex_flags; } -int nfsd_setuser (struct svc_rqst *rqstp,struct svc_export *exp) +int nfsd_setuser (struct svc_cred *cred,struct svc_export *exp) {struct group_info *rqgi;struct group_info *gi;struct cred *new;int i; -int flags =nfsexp_flags (rqstp, exp); +int flags =nfsexp_flags (cred, exp);validate_process_creds ();
@@ -34,10 +34,10 @@ int nfsd_setuser(struct svc_rqst *rqstp, struct svc_export *exp)if (!new)return -ENOMEM ; - new->fsuid = rqstp->rq_cred.cr_uid; - new->fsgid = rqstp->rq_cred.cr_gid; + new->fsuid = cred->cr_uid; + new->fsgid = cred->cr_gid; - rqgi = rqstp->rq_cred.cr_group_info; + rqgi = cred->cr_group_info;if (flags &NFSEXP_ALLSQUASH ) { new->fsuid = exp->ex_anon_uid;
@@ -91,4 +91,3 @@ int nfsd_setuser(struct svc_rqst *rqstp, struct svc_export *exp)abort_creds (new);return -ENOMEM ; } -
fs/nfsd/auth.h +1 -1
@@ -12,6 +12,6 @@* Set the current process's fsuid/fsgid etc to those of the NFS * client user */ -int nfsd_setuser (struct svc_rqst *,struct svc_export *); +int nfsd_setuser (struct svc_cred *cred,struct svc_export *exp);#endif /* LINUX_NFSD_AUTH_H */
fs/nfsd/export.h +2 -1
@@ -98,7 +98,8 @@ struct svc_expkey {#define EX_NOHIDE (exp) ((exp)->ex_flags & NFSEXP_NOHIDE)#define EX_WGATHER (exp) ((exp)->ex_flags & NFSEXP_GATHERED_WRITES) -int nfsexp_flags (struct svc_rqst *rqstp,struct svc_export *exp); +struct svc_cred ; +int nfsexp_flags (struct svc_cred *cred,struct svc_export *exp);__be32 check_nfsd_access (struct svc_export *exp,struct svc_rqst *rqstp);/*
fs/nfsd/nfs3proc.c +3 -1
@@ -319,7 +319,9 @@ nfsd3_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,if (!IS_POSIXACL (inode)) iap->ia_mode &= ~current_umask (); -fh_fill_pre_attrs (fhp); + status =fh_fill_pre_attrs (fhp); +if (status != nfs_ok) + goto out; host_err =vfs_create (inode, child, iap->ia_mode, true);if (host_err <0 ) { status =nfserrno (host_err);
fs/nfsd/nfs4proc.c +8 -6
@@ -298,12 +298,12 @@ nfsd4_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp, }if (d_really_is_positive (child)) { - status = nfs_ok; -/* NFSv4 protocol requires change attributes even though * no change happened. */ -fh_fill_both_attrs (fhp); + status =fh_fill_both_attrs (fhp); +if (status != nfs_ok) + goto out;switch (open->op_createmode) {case NFS4_CREATE_UNCHECKED :
@@ -346,7 +346,9 @@ nfsd4_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,if (!IS_POSIXACL (inode)) iap->ia_mode &= ~current_umask (); -fh_fill_pre_attrs (fhp); + status =fh_fill_pre_attrs (fhp); +if (status != nfs_ok) + goto out; status =nfsd4_vfs_create (fhp, child, open);if (status != nfs_ok) goto out;
@@ -425,11 +427,11 @@ do_open_lookup(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, stru }else { status =nfsd_lookup (rqstp, current_fh, open->op_fname, open->op_fnamelen, *resfh); -if (!status) +if (status == nfs_ok)/* NFSv4 protocol requires change attributes even though * no change happened. */ -fh_fill_both_attrs (current_fh); + status =fh_fill_both_attrs (current_fh); }if (status) goto out;
fs/nfsd/nfs4state.c +3 -2
@@ -6566,8 +6566,9 @@ nfs4_check_file(struct svc_rqst *rqstp, struct svc_fh *fhp, struct nfs4_stid *s, nf =nfs4_find_file (s, flags);if (nf) { - status =nfsd_permission (rqstp, fhp->fh_export, fhp->fh_dentry, - acc |NFSD_MAY_OWNER_OVERRIDE ); + status =nfsd_permission (&rqstp->rq_cred, + fhp->fh_export, fhp->fh_dentry, + acc |NFSD_MAY_OWNER_OVERRIDE );if (status) {nfsd_file_put (nf); goto out;
fs/nfsd/nfsfh.c +23 -22
116 lines not on this page · raw message
fs/nfsd/nfsfh.h +3 -3
@@ -314,7 +314,7 @@ static inline u64 nfsd4_change_attribute(struct kstat *stat, return time_to_chattr(&stat->ctime); } -extern void fh_fill_pre_attrs (struct svc_fh *fhp); -extern void fh_fill_post_attrs (struct svc_fh *fhp); -extern void fh_fill_both_attrs (struct svc_fh *fhp); +__be32 __must_check fh_fill_pre_attrs (struct svc_fh *fhp); +__be32 fh_fill_post_attrs (struct svc_fh *fhp); +__be32 __must_check fh_fill_both_attrs (struct svc_fh *fhp);#endif /* _LINUX_NFSD_NFSFH_H */
fs/nfsd/nfsproc.c +5 -4
@@ -343,10 +343,11 @@ nfsd_proc_create(struct svc_rqst *rqstp)* echo thing > device-special-file-or-pipe * by doing a CREATE with type==0 */ - resp->status =nfsd_permission (rqstp, - newfhp->fh_export, - newfhp->fh_dentry, -NFSD_MAY_WRITE |NFSD_MAY_LOCAL_ACCESS ); + resp->status =nfsd_permission ( + &rqstp->rq_cred, + newfhp->fh_export, + newfhp->fh_dentry, +NFSD_MAY_WRITE |NFSD_MAY_LOCAL_ACCESS );if (resp->status && resp->status != nfserr_rofs) goto out_unlock; }
fs/nfsd/vfs.c +55 -39
225 lines not on this page · raw message
fs/nfsd/vfs.h +2 -2
@@ -145,8 +145,8 @@ __be32 nfsd_readdir(struct svc_rqst *, struct svc_fh *,__be32 nfsd_statfs (struct svc_rqst *,struct svc_fh *,struct kstatfs *,int access); -__be32 nfsd_permission (struct svc_rqst *,struct svc_export *, -struct dentry *,int ); +__be32 nfsd_permission (struct svc_cred *cred,struct svc_export *exp, +struct dentry *dentry,int acc);static inline int fh_want_write (struct svc_fh *fh) {
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 2/2 NFSD: check truncate permission under inode lock
From: Chuck Lever <[email protected]>
[ Upstream commit b778e0e0a16759f22a70579c3cf8d254a40d4a7f ]
nfsd_setattr() checks whether a size update needs NFSD_MAY_TRUNC before it takes inode_lock(). The comparison uses the file size sampled by that unlocked read, but the actual ATTR_SIZE update is applied later under inode_lock() by notify_change().
This leaves a TOCTOU window for append-only files. If a client sends a SETATTR that does not shrink the file at the time of the unlocked sample, a concurrent append can extend the file before nfsd_setattr() takes inode_lock(). notify_change() then applies a real truncation without the NFSD_MAY_TRUNC check that rejects IS_APPEND(inode). The VFS truncate syscall paths perform their own append-only checks before calling notify_change(), so NFSD must make this decision against the locked size it is about to change.
Split the write-count acquisition from the truncation permission check. Keep get_write_access() before the locked setattr work, then recheck whether the requested size is below i_size_read(inode) after inode_lock() has been acquired and before notify_change(ATTR_SIZE). This also avoids the plain unlocked inode->i_size load.
--- fs/nfsd/vfs.c | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-)
fs/nfsd/vfs.c +18 -12
@@ -390,21 +390,22 @@ nfsd_sanitize_attrs(struct inode *inode, struct iattr *iap) }static __be32 -nfsd_get_write_access (struct svc_rqst *rqstp,struct svc_fh *fhp, -struct iattr *iap) +nfsd_may_truncate (struct svc_rqst *rqstp,struct svc_fh *fhp, +struct iattr *iap) {struct inode *inode =d_inode (fhp->fh_dentry); -if (iap->ia_size < inode->i_size) { -__be32 err; +if (iap->ia_size >=i_size_read (inode)) +return nfs_ok; - err =nfsd_permission (&rqstp->rq_cred, - fhp->fh_export, fhp->fh_dentry, -NFSD_MAY_TRUNC |NFSD_MAY_OWNER_OVERRIDE ); -if (err) -return err; - } -return nfserrno (get_write_access (inode)); +return nfsd_permission (&rqstp->rq_cred, fhp->fh_export, fhp->fh_dentry, +NFSD_MAY_TRUNC |NFSD_MAY_OWNER_OVERRIDE ); +} + +static __be32 +nfsd_get_write_access (struct svc_fh *fhp) +{ +return nfserrno (get_write_access (d_inode (fhp->fh_dentry))); }static int __nfsd_setattr (struct dentry *dentry,struct iattr *iap)
@@ -531,12 +532,17 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp,* setattr call. */ if (size_change) { - err =nfsd_get_write_access (rqstp, fhp, iap); + err =nfsd_get_write_access (fhp);if (err)return err; }inode_lock (inode); +if (size_change) { + err =nfsd_may_truncate (rqstp, fhp, iap); +if (err) + goto out_unlock; + } err =fh_fill_pre_attrs (fhp);if (err) goto out_unlock;
Signature · 1 line
2.53.0