FAILED: patch "[PATCH] nfsd: reject out-of-range nseconds in NFSv3 SETATTR and" 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 eb0eca7720662ba5847df1510e73801f7f473094
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026090307-suave-unlucky-a08a@gregkh' --subject-prefix 'PATCH 5.15.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From eb0eca7720662ba5847df1510e73801f7f473094 Mon Sep 17 00:00:00 2001
From: Robbie Ko <[email protected]>
Date: Tue, 16 Jun 2026 13:39:59 +0800
Subject: [PATCH] nfsd: reject out-of-range nseconds in NFSv3 SETATTR and
create ops
A client can send an NFSv3 SETATTR, CREATE, MKDIR, SYMLINK or MKNOD carrying an atime or mtime whose nseconds field is out of range. The value is well-formed on the wire and decodes cleanly into a valid uint32, but it is not a valid timespec64: tv_nsec must be less than NSEC_PER_SEC.
Nothing in the setattr path clamps it. notify_change() runs the time through timestamp_truncate(), which does not reduce tv_nsec below NSEC_PER_SEC when the filesystem supports nanosecond granularity (s_time_gran == 1), and the inode atime/mtime setters store it verbatim (only ctime is normalized, via inode_set_ctime_to_ts()). The un-normalized value then corrupts on-disk metadata: ext4's ext4_encode_extra_time() shifts tv_nsec left by EXT4_EPOCH_BITS, which overflows the 32-bit extra field and clobbers the seconds-epoch bits, so the stored seconds (and thus the year) are wrong on read-back. XFS with bigtime mis-stores the timestamp for the same reason.
Validate the client-supplied atime/mtime in the proc handlers and return NFS3ERR_INVAL before anything is changed. RFC 1813 lists NFS3ERR_INVAL for SETATTR and describes it as the error for a value the server 'can not store ... in its own representation'; the client maps it to EINVAL.
Checking in the proc handlers, rather than in nfsd_setattr(), keeps the rejection in front of object creation. The create operations create the object before nfsd_create_setattr() runs, so a late failure would leave the new object behind and turn a non-idempotent request into a namespace change that reports failure. The check is therefore done up front, for the create operations before the object is created.
tv_nsec is a long, so the comparison casts it to unsigned long (the same width) rather than to u32, matching timespec64_valid(). A u32 cast would truncate on 64-bit; the unsigned long cast also rejects a value that became negative when an out-of-range u32 wire nseconds was assigned to a 32-bit long.
Only client-supplied times are checked: SET_TO_SERVER_TIME requests carry no client value. The sattrguard3 ctime is deliberately left alone:
an out-of-range guard simply never matches the object's ctime and yields NFS3ERR_NOT_SYNC via the existing guardtime comparison, which is the protocol-correct outcome rather than rejecting the request.
fs/nfsd/nfs3proc.c +40 -0
@@ -29,6 +29,25 @@ static int nfs3_ftypes[] = { S_IFIFO, /* NF3FIFO */ }; +/* +* Reject a client-supplied atime or mtime whose nanoseconds field is out +* of range. Such a value is well-formed on the wire but is not a valid +* timespec64, and storing it verbatim can corrupt on-disk timestamps. +* tv_nsec is a long, so it is cast to unsigned long (the same width) to +* catch both an over-large value and one that became negative when an +* out-of-range u32 wire nseconds was assigned to a 32-bit long. +*/ +static bool nfsd3_time_in_range (const struct iattr *iap) +{ +if ((iap->ia_valid &ATTR_ATIME_SET ) && + (unsigned long )iap->ia_atime.tv_nsec >=NSEC_PER_SEC ) +return false; +if ((iap->ia_valid &ATTR_MTIME_SET ) && + (unsigned long )iap->ia_mtime.tv_nsec >=NSEC_PER_SEC ) +return false; +return true; +} +static __be32 nfsd3_map_status (__be32 status) {switch (status) {
@@ -101,9 +120,14 @@ nfsd3_proc_setattr(struct svc_rqst *rqstp) SVCFH_fmt(&argp->fh));fh_copy (&resp->fh, &argp->fh); +if (!nfsd3_time_in_range (&argp->attrs)) { + resp->status = nfserr_inval; + goto out; + }if (argp->check_guard) guardtime = &argp->guardtime; resp->status =nfsd_setattr (rqstp, &resp->fh, &attrs, guardtime); +out: resp->status =nfsd3_map_status (resp->status);return rpc_success; }
@@ -265,6 +289,8 @@ nfsd3_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,trace_nfsd_vfs_create (rqstp, fhp,S_IFREG , argp->name, argp->len); +if (!nfsd3_time_in_range (iap)) +return nfserr_inval;if (isdotent (argp->name, argp->len))return nfserr_exist;if (!(iap->ia_valid &ATTR_MODE ))
@@ -400,8 +426,13 @@ nfsd3_proc_mkdir(struct svc_rqst *rqstp) argp->attrs.ia_valid &= ~ATTR_SIZE ;fh_copy (&resp->dirfh, &argp->fh);fh_init (&resp->fh,NFS3_FHSIZE ); +if (!nfsd3_time_in_range (&argp->attrs)) { + resp->status = nfserr_inval; + goto out; + } resp->status =nfsd_create (rqstp, &resp->dirfh, argp->name, argp->len, &attrs,S_IFDIR ,0 , &resp->fh); +out: resp->status =nfsd3_map_status (resp->status);return rpc_success; }
@@ -415,6 +446,10 @@ nfsd3_proc_symlink(struct svc_rqst *rqstp) .na_iattr = &argp->attrs, }; +if (!nfsd3_time_in_range (&argp->attrs)) { + resp->status = nfserr_inval; + goto out; + }if (argp->tlen ==0 ) { resp->status = nfserr_inval; goto out;
@@ -471,6 +506,11 @@ nfsd3_proc_mknod(struct svc_rqst *rqstp) goto out; } +if (!nfsd3_time_in_range (&argp->attrs)) { + resp->status = nfserr_inval; + goto out; + } + type = nfs3_ftypes[argp->ftype]; resp->status =nfsd_create (rqstp, &resp->dirfh, argp->name, argp->len, &attrs, type, rdev, &resp->fh);
Sasha Levin
maintainerPATCH 1/4 nfsd: Move error code mapping to per-version proc code.
From: NeilBrown <[email protected]>
[ Upstream commit 1459ad57673b8019fbfcfddc345c99630d29e716 ]
There is code scattered around nfsd which chooses an error status based on the particular version of nfs being used. It is cleaner to have the version specific choices in version specific code.
With this patch common code returns the most specific error code possible and the version specific code maps that if necessary.
Both v2 (nfsproc.c) and v3 (nfs3proc.c) now have a "map_status()" function which is called to map the resp->status before each non-trivial nfsd_proc_* or nfsd3_proc_* function returns.
NFS4ERR_SYMLINK and NFS4ERR_WRONG_TYPE introduce extra complications and are left for a later patch.
Stable-dep-of: eb0eca772066 ("nfsd: reject out-of-range nseconds in NFSv3 SETATTR and create ops")
--- fs/nfsd/export.c | 2 +- fs/nfsd/nfs3proc.c | 45 +++++++++++++++++++++++++++++++++++++++++++-- fs/nfsd/nfs3xdr.c | 5 +---- fs/nfsd/nfs4proc.c | 3 +-- fs/nfsd/nfs4state.c | 2 +- fs/nfsd/nfsfh.c | 10 +++------- fs/nfsd/nfsproc.c | 39 ++++++++++++++++++++++++++++++++++++--- fs/nfsd/vfs.c | 33 +++++++++++++++++---------------- fs/nfsd/vfs.h | 2 +- fs/nfsd/xdr3.h | 2 +- 10 files changed, 105 insertions(+), 38 deletions(-)
fs/nfsd/export.c +1 -1
@@ -1076,7 +1076,7 @@ __be32 check_nfsd_access(struct svc_export *exp, struct svc_rqst *rqstp)if (nfsd4_spo_must_allow (rqstp))return 0 ; -return rqstp->rq_vers <4 ? nfserr_acces : nfserr_wrongsec; +return nfserr_wrongsec; }/*
fs/nfsd/nfs3proc.c +43 -2
188 lines not on this page · raw message
fs/nfsd/nfs3xdr.c +1 -4
@@ -295,17 +295,14 @@ svcxdr_decode_sattr3(struct svc_rqst *rqstp, struct xdr_stream *xdr,static bool svcxdr_decode_sattrguard3 (struct xdr_stream *xdr,struct nfsd3_sattrargs *args) { -__be32 *p;u32 check;if (xdr_stream_decode_bool (xdr, &check) <0 )return false;if (check) { - p =xdr_inline_decode (xdr,XDR_UNIT *2 ); -if (!p) +if (!svcxdr_decode_nfstime3 (xdr, &args->guardtime))return false; args->check_guard =1 ; - args->guardtime =be32_to_cpup (p); }else args->check_guard =0 ;
fs/nfsd/nfs4proc.c +1 -2
@@ -1127,8 +1127,7 @@ nfsd4_setattr(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, goto out; save_no_wcc = cstate->current_fh.fh_no_wcc; cstate->current_fh.fh_no_wcc = true; - status =nfsd_setattr (rqstp, &cstate->current_fh, &attrs, -0 , (time64_t )0 ); + status =nfsd_setattr (rqstp, &cstate->current_fh, &attrs,NULL ); cstate->current_fh.fh_no_wcc = save_no_wcc;if (!status) status =nfserrno (attrs.na_labelerr);
fs/nfsd/nfs4state.c +1 -1
@@ -5229,7 +5229,7 @@ nfsd4_truncate(struct svc_rqst *rqstp, struct svc_fh *fh,return 0 ;if (!(open->op_share_access &NFS4_SHARE_ACCESS_WRITE ))return nfserr_inval; -return nfsd_setattr (rqstp, fh, &attrs,0 , (time64_t )0 ); +return nfsd_setattr (rqstp, fh, &attrs,NULL ); } static __be32 nfs4_get_vfs_file(struct svc_rqst *rqstp, struct nfs4_file *fp,
fs/nfsd/nfsfh.c +3 -7
@@ -162,10 +162,8 @@ static __be32 nfsd_set_fh_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp)int len;__be32 error; - error = nfserr_stale; -if (rqstp->rq_vers >2 ) - error = nfserr_badhandle; -if (rqstp->rq_vers ==4 && fh->fh_size ==0 ) + error = nfserr_badhandle; +if (fh->fh_size ==0 )return nfserr_nofilehandle;if (fh->fh_version !=1 )
@@ -237,9 +235,7 @@ static __be32 nfsd_set_fh_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp)/* * Look up the dentry using the NFS file handle. */ - error = nfserr_stale; -if (rqstp->rq_vers >2 ) - error = nfserr_badhandle; + error = nfserr_badhandle; fileid_type = fh->fh_fileid_type;
fs/nfsd/nfsproc.c +36 -3
@@ -13,6 +13,24 @@#define NFSDDBG_FACILITY NFSDDBG_PROC +static __be32 nfsd_map_status (__be32 status) +{ +switch (status) { +case nfs_ok: +break ; +case nfserr_nofilehandle: +case nfserr_badhandle: + status = nfserr_stale; +break ; +case nfserr_wrongsec: +case nfserr_xdev: +case nfserr_file_open: + status = nfserr_acces; +break ; + } +return status; +} +static __be32 nfsd_proc_null (struct svc_rqst *rqstp) {
@@ -38,6 +56,7 @@ nfsd_proc_getattr(struct svc_rqst *rqstp) goto out; resp->status =fh_getattr (&resp->fh, &resp->stat); out: + resp->status =nfsd_map_status (resp->status);return rpc_success; }
@@ -103,12 +122,13 @@ nfsd_proc_setattr(struct svc_rqst *rqstp) } } - resp->status =nfsd_setattr (rqstp, fhp, &attrs,0 , (time64_t )0 ); + resp->status =nfsd_setattr (rqstp, fhp, &attrs,NULL );if (resp->status != nfs_ok) goto out; resp->status =fh_getattr (&resp->fh, &resp->stat); out: + resp->status =nfsd_map_status (resp->status);return rpc_success; }
@@ -143,6 +163,7 @@ nfsd_proc_lookup(struct svc_rqst *rqstp) resp->status =fh_getattr (&resp->fh, &resp->stat); out: + resp->status =nfsd_map_status (resp->status);return rpc_success; }
@@ -164,6 +185,7 @@ nfsd_proc_readlink(struct svc_rqst *rqstp) page_address(resp->page), &resp->len);fh_put (&argp->fh); + resp->status =nfsd_map_status (resp->status);return rpc_success; }
@@ -212,6 +234,7 @@ nfsd_proc_read(struct svc_rqst *rqstp) resp->status =fh_getattr (&resp->fh, &resp->stat); else if (resp->status == nfserr_jukebox) set_bit(RQ_DROPME, &rqstp->rq_flags); + resp->status =nfsd_map_status (resp->status);return rpc_success; }
@@ -247,6 +270,7 @@ nfsd_proc_write(struct svc_rqst *rqstp) resp->status =fh_getattr (&resp->fh, &resp->stat); else if (resp->status == nfserr_jukebox) set_bit(RQ_DROPME, &rqstp->rq_flags); + resp->status =nfsd_map_status (resp->status);return rpc_success; }
@@ -402,8 +426,8 @@ nfsd_proc_create(struct svc_rqst *rqstp)*/ attr->ia_valid &=ATTR_SIZE ;if (attr->ia_valid) - resp->status =nfsd_setattr (rqstp, newfhp, &attrs,0 , - (time64_t )0 ); + resp->status =nfsd_setattr (rqstp, newfhp, &attrs, +NULL ); } out_unlock:
@@ -415,6 +439,7 @@ nfsd_proc_create(struct svc_rqst *rqstp) goto out; resp->status =fh_getattr (&resp->fh, &resp->stat); out: + resp->status =nfsd_map_status (resp->status);return rpc_success; }
@@ -431,6 +456,7 @@ nfsd_proc_remove(struct svc_rqst *rqstp) resp->status =nfsd_unlink (rqstp, &argp->fh, -S_IFDIR , argp->name, argp->len);fh_put (&argp->fh); + resp->status =nfsd_map_status (resp->status);return rpc_success; }
@@ -449,6 +475,7 @@ nfsd_proc_rename(struct svc_rqst *rqstp) &argp->tfh, argp->tname, argp->tlen);fh_put (&argp->ffh);fh_put (&argp->tfh); + resp->status =nfsd_map_status (resp->status);return rpc_success; }
@@ -469,6 +496,7 @@ nfsd_proc_link(struct svc_rqst *rqstp) &argp->ffh);fh_put (&argp->ffh);fh_put (&argp->tfh); + resp->status =nfsd_map_status (resp->status);return rpc_success; }
@@ -507,6 +535,7 @@ nfsd_proc_symlink(struct svc_rqst *rqstp)fh_put (&argp->ffh);fh_put (&newfh); out: + resp->status =nfsd_map_status (resp->status);return rpc_success; }
@@ -540,6 +569,7 @@ nfsd_proc_mkdir(struct svc_rqst *rqstp) resp->status =fh_getattr (&resp->fh, &resp->stat); out: + resp->status =nfsd_map_status (resp->status);return rpc_success; }
@@ -557,6 +587,7 @@ nfsd_proc_rmdir(struct svc_rqst *rqstp) resp->status =nfsd_unlink (rqstp, &argp->fh,S_IFDIR , argp->name, argp->len);fh_put (&argp->fh); + resp->status =nfsd_map_status (resp->status);return rpc_success; }
@@ -602,6 +633,7 @@ nfsd_proc_readdir(struct svc_rqst *rqstp)nfssvc_encode_nfscookie (resp, offset);fh_put (&argp->fh); + resp->status =nfsd_map_status (resp->status);return rpc_success; }
@@ -619,6 +651,7 @@ nfsd_proc_statfs(struct svc_rqst *rqstp) resp->status =nfsd_statfs (rqstp, &argp->fh, &resp->stats,NFSD_MAY_BYPASS_GSS_ON_ROOT );fh_put (&argp->fh); + resp->status =nfsd_map_status (resp->status);return rpc_success; }
fs/nfsd/vfs.c +17 -16
@@ -454,7 +454,6 @@ static int __nfsd_setattr(struct dentry *dentry, struct iattr *iap)* @rqstp: controlling RPC transaction * @fhp: filehandle of target * @attr: attributes to set -* @check_guard: set to 1 if guardtime is a valid timestamp * @guardtime: do not act if ctime.tv_sec does not match this timestamp * * This call may adjust the contents of @attr (in particular, this
@@ -466,8 +465,7 @@ static int __nfsd_setattr(struct dentry *dentry, struct iattr *iap)*/ __be32 nfsd_setattr (struct svc_rqst *rqstp,struct svc_fh *fhp, -struct nfsd_attrs *attr, -int check_guard,time64_t guardtime) +struct nfsd_attrs *attr,const struct timespec64 *guardtime) {struct dentry *dentry;struct inode *inode;
@@ -516,9 +514,6 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp,nfsd_sanitize_attrs (inode, iap); -if (check_guard && guardtime != inode->i_ctime.tv_sec) -return nfserr_notsync; -/* * The size case is special, it changes the file in addition to the * attributes, and file systems don't expect it to be mixed with
@@ -534,6 +529,17 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp,inode_lock (inode);fh_fill_pre_attrs (fhp); + +if (guardtime) { +struct timespec64 ctime =inode_get_ctime (inode); + +if ((u32 )guardtime->tv_sec != (u32 )ctime.tv_sec || + guardtime->tv_nsec != ctime.tv_nsec) { + err = nfserr_notsync; + goto out_fill_attrs; + } + } +for (retries =1 ;;) {struct iattr attrs;
@@ -561,6 +567,7 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, attr->na_aclerr =set_posix_acl (&init_user_ns, inode,ACL_TYPE_DEFAULT , attr->na_dpacl); +out_fill_attrs:fh_fill_post_attrs (fhp);inode_unlock (inode);if (size_change)
@@ -1327,7 +1334,7 @@ nfsd_create_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp,* if the attributes have not changed. */ if (iap->ia_valid || attrs->na_pacl || attrs->na_dpacl) - status =nfsd_setattr (rqstp, resfhp, attrs,0 , (time64_t )0 ); + status =nfsd_setattr (rqstp, resfhp, attrs,NULL );else status =nfserrno (commit_metadata (resfhp));
@@ -1663,10 +1670,7 @@ nfsd_link(struct svc_rqst *rqstp, struct svc_fh *ffhp,if (!err) err =nfserrno (commit_metadata (tfhp)); }else { -if (host_err == -EXDEV && rqstp->rq_vers ==2 ) - err = nfserr_acces; -else - err =nfserrno (host_err); + err =nfserrno (host_err); }dput (dnew); out_drop_write:
@@ -1732,7 +1736,7 @@ nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen,if (!flen ||isdotent (fname, flen) || !tlen ||isdotent (tname, tlen)) goto out; - err = (rqstp->rq_vers ==2 ) ? nfserr_acces : nfserr_xdev; + err = nfserr_xdev;if (ffhp->fh_export->ex_path.mnt != tfhp->fh_export->ex_path.mnt) goto out;if (ffhp->fh_export->ex_path.dentry != tfhp->fh_export->ex_path.dentry)
@@ -1904,10 +1908,7 @@ nfsd_unlink(struct svc_rqst *rqstp, struct svc_fh *fhp, int type,/* name is mounted-on. There is no perfect * error status. */ -if (nfsd_v4client (rqstp)) - err = nfserr_file_open; -else - err = nfserr_acces; + err = nfserr_file_open; }else { err =nfserrno (host_err); }
fs/nfsd/vfs.h +1 -1
@@ -69,7 +69,7 @@ __be32 nfsd_lookup_dentry(struct svc_rqst *, struct svc_fh *, const char *, unsigned int, struct svc_export **, struct dentry **);__be32 nfsd_setattr (struct svc_rqst *,struct svc_fh *, -struct nfsd_attrs *,int ,time64_t ); +struct nfsd_attrs *,const struct timespec64 *);int nfsd_mountpoint (struct dentry *,struct svc_export *);#ifdef CONFIG_NFSD_V4 __be32 nfsd4_vfs_fallocate(struct svc_rqst *, struct svc_fh *,
fs/nfsd/xdr3.h +1 -1
@@ -14,7 +14,7 @@ struct nfsd3_sattrargs {struct svc_fh fh;struct iattr attrs;int check_guard; -time64_t guardtime; +struct timespec64 guardtime; };struct nfsd3_diropargs {
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 2/4 NFSD: Use sockaddr instead of a generic array
From: Chuck Lever <[email protected]>
[ Upstream commit 45e3eda46db467b8b934188ff2109ee29b5b0b00 ]
Record and emit presentation addresses using tracing helpers designed for the task.
Stable-dep-of: eb0eca772066 ("nfsd: reject out-of-range nseconds in NFSv3 SETATTR and create ops")
--- fs/nfsd/trace.h | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-)
fs/nfsd/trace.h +15 -14
@@ -16,22 +16,23 @@#include "nfsfh.h" #include "xdr4.h" -#define NFSD_TRACE_PROC_RES_FIELDS \ +#define NFSD_TRACE_PROC_RES_FIELDS (r) \ __field(unsigned int, netns_ino) \ __field(u32, xid) \ __field(unsigned long, status) \ - __array(unsigned char, server, sizeof(struct sockaddr_in6)) \ - __array(unsigned char, client, sizeof(struct sockaddr_in6)) + __sockaddr(server, (r)->rq_xprt->xpt_locallen) \ + __sockaddr(client, (r)->rq_xprt->xpt_remotelen) -#define NFSD_TRACE_PROC_RES_ASSIGNMENTS (error) \ +#define NFSD_TRACE_PROC_RES_ASSIGNMENTS (r, error) \ do { \ - __entry->netns_ino = SVC_NET(rqstp)->ns.inum; \ - __entry->xid = be32_to_cpu(rqstp->rq_xid); \ + struct svc_xprt *xprt = (r)->rq_xprt; \ + __entry->netns_ino = SVC_NET(r)->ns.inum; \ + __entry->xid = be32_to_cpu((r)->rq_xid); \ __entry->status = be32_to_cpu(error); \ - memcpy(__entry->server, &rqstp->rq_xprt->xpt_local, \ - rqstp->rq_xprt->xpt_locallen); \ - memcpy(__entry->client, &rqstp->rq_xprt->xpt_remote, \ - rqstp->rq_xprt->xpt_remotelen); \ + __assign_sockaddr(server, &xprt->xpt_local, \ + xprt->xpt_locallen); \ + __assign_sockaddr(client, &xprt->xpt_remote, \ + xprt->xpt_remotelen); \ } while (0); DECLARE_EVENT_CLASS(nfsd_xdr_err_class,
@@ -142,14 +143,14 @@ TRACE_EVENT(nfsd_compound_decode_err, ),TP_ARGS (rqstp, args_opcnt, resp_opcnt, opnum, status),TP_STRUCT__entry ( - NFSD_TRACE_PROC_RES_FIELDS + NFSD_TRACE_PROC_RES_FIELDS(rqstp) __field(u32, args_opcnt) __field(u32, resp_opcnt)__field (u32, opnum) ),TP_fast_assign ( -NFSD_TRACE_PROC_RES_ASSIGNMENTS (status) +NFSD_TRACE_PROC_RES_ASSIGNMENTS (rqstp, status) __entry->args_opcnt = args_opcnt; __entry->resp_opcnt = resp_opcnt;
@@ -168,12 +169,12 @@ TRACE_EVENT(nfsd_compound_encode_err, ),TP_ARGS (rqstp, opnum, status),TP_STRUCT__entry ( - NFSD_TRACE_PROC_RES_FIELDS + NFSD_TRACE_PROC_RES_FIELDS(rqstp)__field (u32, opnum) ),TP_fast_assign ( -NFSD_TRACE_PROC_RES_ASSIGNMENTS (status) +NFSD_TRACE_PROC_RES_ASSIGNMENTS (rqstp, status) __entry->opnum = opnum; ),
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 3/4 NFSD: Add a Call equivalent to the NFSD_TRACE_PROC_RES macros
In reply to Sasha LevinFrom: Chuck Lever <[email protected]>
[ Upstream commit 218927aa47647cc3bb58520cc630279844d8adde ]
Introduce tracing helpers that can be used before the procedure status code is known. These macros are similar to the SVC_RQST_ENDPOINT helpers, but they can be modified to include NFS-specific fields if that is needed later.
Stable-dep-of: eb0eca772066 ("nfsd: reject out-of-range nseconds in NFSv3 SETATTR and create ops")
--- fs/nfsd/trace.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+)
fs/nfsd/trace.h +17 -0
@@ -16,6 +16,23 @@#include "nfsfh.h" #include "xdr4.h" +#define NFSD_TRACE_PROC_CALL_FIELDS (r) \ + __field(unsigned int, netns_ino) \ + __field(u32, xid) \ + __sockaddr(server, (r)->rq_xprt->xpt_locallen) \ + __sockaddr(client, (r)->rq_xprt->xpt_remotelen) + +#define NFSD_TRACE_PROC_CALL_ASSIGNMENTS (r) \ + do { \ + struct svc_xprt *xprt = (r)->rq_xprt; \ + __entry->netns_ino = SVC_NET(r)->ns.inum; \ + __entry->xid = be32_to_cpu((r)->rq_xid); \ + __assign_sockaddr(server, &xprt->xpt_local, \ + xprt->xpt_locallen); \ + __assign_sockaddr(client, &xprt->xpt_remote, \ + xprt->xpt_remotelen); \ + } while (0) +#define NFSD_TRACE_PROC_RES_FIELDS (r) \ __field(unsigned int, netns_ino) \ __field(u32, xid) \
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 4/4 nfsd: reject out-of-range nseconds in NFSv3 SETATTR and create ops
In reply to Sasha LevinFrom: Robbie Ko <[email protected]>
[ Upstream commit eb0eca7720662ba5847df1510e73801f7f473094 ]
A client can send an NFSv3 SETATTR, CREATE, MKDIR, SYMLINK or MKNOD carrying an atime or mtime whose nseconds field is out of range. The value is well-formed on the wire and decodes cleanly into a valid uint32, but it is not a valid timespec64: tv_nsec must be less than NSEC_PER_SEC.
Nothing in the setattr path clamps it. notify_change() runs the time through timestamp_truncate(), which does not reduce tv_nsec below NSEC_PER_SEC when the filesystem supports nanosecond granularity (s_time_gran == 1), and the inode atime/mtime setters store it verbatim (only ctime is normalized, via inode_set_ctime_to_ts()). The un-normalized value then corrupts on-disk metadata: ext4's ext4_encode_extra_time() shifts tv_nsec left by EXT4_EPOCH_BITS, which overflows the 32-bit extra field and clobbers the seconds-epoch bits, so the stored seconds (and thus the year) are wrong on read-back. XFS with bigtime mis-stores the timestamp for the same reason.
Validate the client-supplied atime/mtime in the proc handlers and return NFS3ERR_INVAL before anything is changed. RFC 1813 lists NFS3ERR_INVAL for SETATTR and describes it as the error for a value the server 'can not store ... in its own representation'; the client maps it to EINVAL.
Checking in the proc handlers, rather than in nfsd_setattr(), keeps the rejection in front of object creation. The create operations create the object before nfsd_create_setattr() runs, so a late failure would leave the new object behind and turn a non-idempotent request into a namespace change that reports failure. The check is therefore done up front, for the create operations before the object is created.
tv_nsec is a long, so the comparison casts it to unsigned long (the same width) rather than to u32, matching timespec64_valid(). A u32 cast would truncate on 64-bit; the unsigned long cast also rejects a value that became negative when an out-of-range u32 wire nseconds was assigned to a 32-bit long.
Only client-supplied times are checked: SET_TO_SERVER_TIME requests carry no client value. The sattrguard3 ctime is deliberately left alone:
an out-of-range guard simply never matches the object's ctime and yields NFS3ERR_NOT_SYNC via the existing guardtime comparison, which is the protocol-correct outcome rather than rejecting the request.
--- fs/nfsd/nfs3proc.c | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+)
fs/nfsd/nfs3proc.c +40 -0
@@ -28,6 +28,25 @@ static int nfs3_ftypes[] = { S_IFIFO, /* NF3FIFO */ }; +/* +* Reject a client-supplied atime or mtime whose nanoseconds field is out +* of range. Such a value is well-formed on the wire but is not a valid +* timespec64, and storing it verbatim can corrupt on-disk timestamps. +* tv_nsec is a long, so it is cast to unsigned long (the same width) to +* catch both an over-large value and one that became negative when an +* out-of-range u32 wire nseconds was assigned to a 32-bit long. +*/ +static bool nfsd3_time_in_range (const struct iattr *iap) +{ +if ((iap->ia_valid &ATTR_ATIME_SET ) && + (unsigned long )iap->ia_atime.tv_nsec >=NSEC_PER_SEC ) +return false; +if ((iap->ia_valid &ATTR_MTIME_SET ) && + (unsigned long )iap->ia_mtime.tv_nsec >=NSEC_PER_SEC ) +return false; +return true; +} +static __be32 nfsd3_map_status (__be32 status) {switch (status) {
@@ -94,9 +113,14 @@ nfsd3_proc_setattr(struct svc_rqst *rqstp) SVCFH_fmt(&argp->fh));fh_copy (&resp->fh, &argp->fh); +if (!nfsd3_time_in_range (&argp->attrs)) { + resp->status = nfserr_inval; + goto out; + }if (argp->check_guard) guardtime = &argp->guardtime; resp->status =nfsd_setattr (rqstp, &resp->fh, &attrs, guardtime); +out: resp->status =nfsd3_map_status (resp->status);return rpc_success; }
@@ -272,6 +296,8 @@ nfsd3_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,/* Validation hooks belong here, before name checks and object creation. */ +if (!nfsd3_time_in_range (iap)) +return nfserr_inval;if (isdotent (argp->name, argp->len))return nfserr_exist;if (!(iap->ia_valid &ATTR_MODE ))
@@ -417,8 +443,13 @@ nfsd3_proc_mkdir(struct svc_rqst *rqstp) argp->attrs.ia_valid &= ~ATTR_SIZE ;fh_copy (&resp->dirfh, &argp->fh);fh_init (&resp->fh,NFS3_FHSIZE ); +if (!nfsd3_time_in_range (&argp->attrs)) { + resp->status = nfserr_inval; + goto out; + } resp->status =nfsd_create (rqstp, &resp->dirfh, argp->name, argp->len, &attrs,S_IFDIR ,0 , &resp->fh); +out: resp->status =nfsd3_map_status (resp->status);return rpc_success; }
@@ -432,6 +463,10 @@ nfsd3_proc_symlink(struct svc_rqst *rqstp) .na_iattr = &argp->attrs, }; +if (!nfsd3_time_in_range (&argp->attrs)) { + resp->status = nfserr_inval; + goto out; + }if (argp->tlen ==0 ) { resp->status = nfserr_inval; goto out;
@@ -498,6 +533,11 @@ nfsd3_proc_mknod(struct svc_rqst *rqstp) goto out; } +if (!nfsd3_time_in_range (&argp->attrs)) { + resp->status = nfserr_inval; + goto out; + } + type = nfs3_ftypes[argp->ftype]; resp->status =nfsd_create (rqstp, &resp->dirfh, argp->name, argp->len, &attrs, type, rdev, &resp->fh);
Signature · 1 line
2.53.0