FAILED: patch "[PATCH] NFSD: check truncate permission under inode lock" failed to apply to 5.15-stable tree
Subsystems (2)
[email protected]
maintainerThe patch below does not apply to the 5.15-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.15.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-copier-another-5c3a@gregkh' --subject-prefix 'PATCH 5.15.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: don't provide pre/post-op attrs if fh_getattr fails
From: Jeff Layton <[email protected]>
[ Upstream commit 518f375c15af724cd89a4ec888dea942bb27f77f ]
nfsd calls fh_getattr to get the latest inode attrs for pre/post-op info. In the event that fh_getattr fails, it resorts to scraping cached values out of the inode directly.
Since these attributes are optional, we can just skip providing them altogether when this happens.
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/nfs4state.c | 3 ++- fs/nfsd/nfsfh.c | 29 +++++++++++++---------------- fs/nfsd/nfsfh.h | 2 +- fs/nfsd/nfsproc.c | 9 +++++---- fs/nfsd/vfs.c | 27 +++++++++++++++++---------- fs/nfsd/vfs.h | 4 ++-- 9 files changed, 50 insertions(+), 44 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/nfs4state.c +2 -1
@@ -6561,7 +6561,8 @@ 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, + status =nfsd_permission (&rqstp->rq_cred, + fhp->fh_export, fhp->fh_dentry, acc |NFSD_MAY_OWNER_OVERRIDE );if (status) {nfsd_file_put (nf);
fs/nfsd/nfsfh.c +13 -16
@@ -102,7 +102,7 @@ static bool nfsd_originating_port_ok(struct svc_rqst *rqstp, int flags)static __be32 nfsd_setuser_and_check_port (struct svc_rqst *rqstp,struct svc_export *exp) { -int flags =nfsexp_flags (rqstp, exp); +int flags =nfsexp_flags (&rqstp->rq_cred, exp);/* Check if the request originated from a secure port. */ if (!nfsd_originating_port_ok (rqstp, flags)) {
@@ -113,7 +113,7 @@ static __be32 nfsd_setuser_and_check_port(struct svc_rqst *rqstp, }/* Set user creds for this exportpoint */ -return nfserrno (nfsd_setuser (rqstp, exp)); +return nfserrno (nfsd_setuser (&rqstp->rq_cred, exp)); } static inline __be32 check_pseudo_root(struct svc_rqst *rqstp,
@@ -392,7 +392,7 @@ fh_verify(struct svc_rqst *rqstp, struct svc_fh *fhp, umode_t type, int access) skip_pseudoflavor_check:/* Finally, check access permissions. */ - error =nfsd_permission (rqstp, exp, dentry, access); + error =nfsd_permission (&rqstp->rq_cred, exp, dentry, access); out:trace_nfsd_fh_verify_err (rqstp, fhp, type, access, error);if (error == nfserr_stale)
@@ -612,7 +612,7 @@ fh_update(struct svc_fh *fhp)* @fhp: file handle to be updated * */ -void fh_fill_pre_attrs (struct svc_fh *fhp) +__be32 fh_fill_pre_attrs (struct svc_fh *fhp) {bool v4 = (fhp->fh_maxsize ==NFS4_FHSIZE );struct inode *inode;
@@ -620,16 +620,13 @@ void fh_fill_pre_attrs(struct svc_fh *fhp)__be32 err;if (fhp->fh_no_wcc || fhp->fh_pre_saved) -return ; +return nfs_ok; inode =d_inode (fhp->fh_dentry); err =fh_getattr (fhp, &stat); -if (err) { -/* Grab the times from inode anyway */ - stat.mtime = inode->i_mtime; - stat.ctime = inode->i_ctime; - stat.size = inode->i_size; - } +if (err) +return err; +if (v4) fhp->fh_pre_change =nfsd4_change_attribute (&stat, inode);
@@ -637,6 +634,7 @@ void fh_fill_pre_attrs(struct svc_fh *fhp) fhp->fh_pre_ctime = stat.ctime; fhp->fh_pre_size = stat.size; fhp->fh_pre_saved = true; +return nfs_ok; }/**
@@ -657,11 +655,10 @@ void fh_fill_post_attrs(struct svc_fh *fhp)printk ("nfsd: inode locked twice during operation.\n" ); err =fh_getattr (fhp, &fhp->fh_post_attr); -if (err) { - fhp->fh_post_saved = false; - fhp->fh_post_attr.ctime = inode->i_ctime; - }else - fhp->fh_post_saved = true; +if (err) +return ; + + fhp->fh_post_saved = true;if (v4) fhp->fh_post_change =nfsd4_change_attribute (&fhp->fh_post_attr, inode);
fs/nfsd/nfsfh.h +1 -1
@@ -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 __be32 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);#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 +17 -10
@@ -400,8 +400,9 @@ nfsd_get_write_access(struct svc_rqst *rqstp, struct svc_fh *fhp,if (iap->ia_size < inode->i_size) {__be32 err; - err =nfsd_permission (rqstp, fhp->fh_export, fhp->fh_dentry, -NFSD_MAY_TRUNC |NFSD_MAY_OWNER_OVERRIDE ); + err =nfsd_permission (&rqstp->rq_cred, + fhp->fh_export, fhp->fh_dentry, +NFSD_MAY_TRUNC |NFSD_MAY_OWNER_OVERRIDE );if (err)return err; }
@@ -533,7 +534,10 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, }inode_lock (inode); -fh_fill_pre_attrs (fhp); + err =fh_fill_pre_attrs (fhp); +if (err) + goto out_unlock; +for (retries =1 ;;) {struct iattr attrs;
@@ -562,6 +566,7 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, inode,ACL_TYPE_DEFAULT , attr->na_dpacl);fh_fill_post_attrs (fhp); +out_unlock:inode_unlock (inode);if (size_change)put_write_access (inode);
@@ -778,7 +783,8 @@ nfsd_access(struct svc_rqst *rqstp, struct svc_fh *fhp, u32 *access, u32 *suppor sresult |= map->access; - err2 =nfsd_permission (rqstp, export, dentry, map->how); + err2 =nfsd_permission (&rqstp->rq_cred, export, + dentry, map->how);switch (err2) {case nfs_ok: result |= map->access;
@@ -1379,7 +1385,8 @@ nfsd_create_locked(struct svc_rqst *rqstp, struct svc_fh *fhp, dirp =d_inode (dentry); dchild =dget (resfhp->fh_dentry); - err =nfsd_permission (rqstp, fhp->fh_export, dentry,NFSD_MAY_CREATE ); + err =nfsd_permission (&rqstp->rq_cred, fhp->fh_export, dentry, +NFSD_MAY_CREATE );if (err) goto out;
@@ -2093,9 +2100,9 @@ nfsd_statfs(struct svc_rqst *rqstp, struct svc_fh *fhp, struct kstatfs *stat, inreturn err; } -static int exp_rdonly (struct svc_rqst *rqstp,struct svc_export *exp) +static int exp_rdonly (struct svc_cred *cred,struct svc_export *exp) { -return nfsexp_flags (rqstp, exp) &NFSEXP_READONLY ; +return nfsexp_flags (cred, exp) &NFSEXP_READONLY ; }#ifdef CONFIG_NFSD_V4
@@ -2338,8 +2345,8 @@ nfsd_setxattr(struct svc_rqst *rqstp, struct svc_fh *fhp, char *name,* Check for a user's access permissions to this inode. */ __be32 -nfsd_permission (struct svc_rqst *rqstp,struct svc_export *exp, -struct dentry *dentry,int acc) +nfsd_permission (struct svc_cred *cred,struct svc_export *exp, +struct dentry *dentry,int acc) {struct inode *inode =d_inode (dentry);int err;
@@ -2370,7 +2377,7 @@ nfsd_permission(struct svc_rqst *rqstp, struct svc_export *exp,*/ if (!(acc &NFSD_MAY_LOCAL_ACCESS ))if (acc & (NFSD_MAY_WRITE |NFSD_MAY_SATTR |NFSD_MAY_TRUNC )) { -if (exp_rdonly (rqstp, exp) || +if (exp_rdonly (cred, exp) ||__mnt_is_readonly (exp->ex_path.mnt))return nfserr_rofs;if (/* (acc & NFSD_MAY_WRITE) && */ IS_IMMUTABLE (inode))
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
@@ -392,21 +392,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)
@@ -528,12 +529,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