FAILED: patch "[PATCH] nfsd: reject out-of-range nseconds in NFSv3 SETATTR and" failed to apply to 6.6-stable tree
Subsystems (2)
[email protected]
maintainerThe patch below does not apply to the 6.6-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-6.6.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 '2026090305-viewpoint-outshoot-49ba@gregkh' --subject-prefix 'PATCH 6.6.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 | 8 ++++---- fs/nfsd/nfs3proc.c | 40 ++++++++++++++++++++++++++++++++++++++++ fs/nfsd/nfsfh.c | 10 +++------- fs/nfsd/nfsproc.c | 33 +++++++++++++++++++++++++++++++++ fs/nfsd/trace.h | 27 +++++++++++++++++++++++++++ fs/nfsd/vfs.c | 14 +++++--------- 6 files changed, 112 insertions(+), 20 deletions(-)
fs/nfsd/export.c +4 -4
@@ -1084,7 +1084,7 @@ static struct svc_export *exp_find(struct cache_detail *cd,* * Return values: * %nfs_ok if access is granted, or -* %nfserr_acces or %nfserr_wrongsec if access is denied +* %nfserr_wrongsec if access is denied */ __be32 check_xprtsec_policy (struct svc_export *exp,struct svc_rqst *rqstp) {
@@ -1105,7 +1105,7 @@ __be32 check_xprtsec_policy(struct svc_export *exp, struct svc_rqst *rqstp)return nfs_ok; } -return rqstp->rq_vers <4 ? nfserr_acces : nfserr_wrongsec; +return nfserr_wrongsec; }/**
@@ -1121,7 +1121,7 @@ __be32 check_xprtsec_policy(struct svc_export *exp, struct svc_rqst *rqstp)* * Return values: * %nfs_ok if access is granted, or -* %nfserr_acces or %nfserr_wrongsec if access is denied +* %nfserr_wrongsec if access is denied */ __be32 check_security_flavor (struct svc_export *exp,struct svc_rqst *rqstp) {
@@ -1151,7 +1151,7 @@ __be32 check_security_flavor(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; }__be32 check_nfsd_access (struct svc_export *exp,struct svc_rqst *rqstp)
fs/nfsd/nfs3proc.c +40 -0
@@ -14,6 +14,7 @@#include "xdr3.h" #include "vfs.h" #include "filecache.h" +#include "trace.h" #define NFSDDBG_FACILITY NFSDDBG_PROC
@@ -28,6 +29,22 @@ static int nfs3_ftypes[] = { S_IFIFO, /* NF3FIFO */ }; +static __be32 nfsd3_map_status (__be32 status) +{ +switch (status) { +case nfs_ok: +break ; +case nfserr_nofilehandle: + status = nfserr_badhandle; +break ; +case nfserr_wrongsec: +case nfserr_file_open: + status = nfserr_acces; +break ; + } +return status; +} +/* * NULL call. */
@@ -57,6 +74,7 @@ nfsd3_proc_getattr(struct svc_rqst *rqstp) resp->status =fh_getattr (&resp->fh, &resp->stat); out: + resp->status =nfsd3_map_status (resp->status);return rpc_success; }
@@ -80,6 +98,7 @@ nfsd3_proc_setattr(struct svc_rqst *rqstp)if (argp->check_guard) guardtime = &argp->guardtime; resp->status =nfsd_setattr (rqstp, &resp->fh, &attrs, guardtime); + resp->status =nfsd3_map_status (resp->status);return rpc_success; }
@@ -103,6 +122,7 @@ nfsd3_proc_lookup(struct svc_rqst *rqstp) resp->status =nfsd_lookup (rqstp, &resp->dirfh, argp->name, argp->len, &resp->fh); + resp->status =nfsd3_map_status (resp->status);return rpc_success; }
@@ -122,6 +142,7 @@ nfsd3_proc_access(struct svc_rqst *rqstp)fh_copy (&resp->fh, &argp->fh); resp->access = argp->access; resp->status =nfsd_access (rqstp, &resp->fh, &resp->access,NULL ); + resp->status =nfsd3_map_status (resp->status);return rpc_success; }
@@ -142,6 +163,7 @@ nfsd3_proc_readlink(struct svc_rqst *rqstp) resp->pages = rqstp->rq_next_page++; resp->status =nfsd_readlink (rqstp, &resp->fh,page_address (*resp->pages), &resp->len); + resp->status =nfsd3_map_status (resp->status);return rpc_success; }
@@ -178,6 +200,7 @@ nfsd3_proc_read(struct svc_rqst *rqstp)fh_copy (&resp->fh, &argp->fh); resp->status =nfsd_read (rqstp, &resp->fh, argp->offset, &resp->count, &resp->eof); + resp->status =nfsd3_map_status (resp->status);return rpc_success; }
@@ -211,6 +234,7 @@ nfsd3_proc_write(struct svc_rqst *rqstp) rqstp->rq_vec, nvecs, &cnt, resp->committed, resp->verf); resp->count = cnt; + resp->status =nfsd3_map_status (resp->status);return rpc_success; }
@@ -235,6 +259,8 @@ nfsd3_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,__be32 status;int host_err; +trace_nfsd_vfs_create (rqstp, fhp,S_IFREG , argp->name, argp->len); +if (isdotent (argp->name, argp->len))return nfserr_exist;if (!(iap->ia_valid &ATTR_MODE ))
@@ -358,6 +384,7 @@ nfsd3_proc_create(struct svc_rqst *rqstp) newfhp =fh_init (&resp->fh,NFS3_FHSIZE ); resp->status =nfsd3_create_file (rqstp, dirfhp, newfhp, argp); + resp->status =nfsd3_map_status (resp->status);return rpc_success; }
@@ -383,6 +410,7 @@ nfsd3_proc_mkdir(struct svc_rqst *rqstp)fh_init (&resp->fh,NFS3_FHSIZE ); resp->status =nfsd_create (rqstp, &resp->dirfh, argp->name, argp->len, &attrs,S_IFDIR ,0 , &resp->fh); + resp->status =nfsd3_map_status (resp->status);return rpc_success; }
@@ -423,6 +451,7 @@ nfsd3_proc_symlink(struct svc_rqst *rqstp) argp->flen, argp->tname, &attrs, &resp->fh);kfree (argp->tname); out: + resp->status =nfsd3_map_status (resp->status);return rpc_success; }
@@ -464,6 +493,7 @@ nfsd3_proc_mknod(struct svc_rqst *rqstp) resp->status =nfsd_create (rqstp, &resp->dirfh, argp->name, argp->len, &attrs, type, rdev, &resp->fh); out: + resp->status =nfsd3_map_status (resp->status);return rpc_success; }
@@ -485,6 +515,7 @@ nfsd3_proc_remove(struct svc_rqst *rqstp)fh_copy (&resp->fh, &argp->fh); resp->status =nfsd_unlink (rqstp, &resp->fh, -S_IFDIR , argp->name, argp->len); + resp->status =nfsd3_map_status (resp->status);return rpc_success; }
@@ -505,6 +536,7 @@ nfsd3_proc_rmdir(struct svc_rqst *rqstp)fh_copy (&resp->fh, &argp->fh); resp->status =nfsd_unlink (rqstp, &resp->fh,S_IFDIR , argp->name, argp->len); + resp->status =nfsd3_map_status (resp->status);return rpc_success; }
@@ -527,6 +559,7 @@ nfsd3_proc_rename(struct svc_rqst *rqstp)fh_copy (&resp->tfh, &argp->tfh); resp->status =nfsd_rename (rqstp, &resp->ffh, argp->fname, argp->flen, &resp->tfh, argp->tname, argp->tlen); + resp->status =nfsd3_map_status (resp->status);return rpc_success; }
@@ -547,6 +580,7 @@ nfsd3_proc_link(struct svc_rqst *rqstp)fh_copy (&resp->tfh, &argp->tfh); resp->status =nfsd_link (rqstp, &resp->tfh, argp->tname, argp->tlen, &resp->fh); + resp->status =nfsd3_map_status (resp->status);return rpc_success; }
@@ -599,6 +633,7 @@ nfsd3_proc_readdir(struct svc_rqst *rqstp)/* Recycle only pages that were part of the reply */ rqstp->rq_next_page = resp->xdr.page_ptr +1 ; + resp->status =nfsd3_map_status (resp->status);return rpc_success; }
@@ -643,6 +678,7 @@ nfsd3_proc_readdirplus(struct svc_rqst *rqstp) rqstp->rq_next_page = resp->xdr.page_ptr +1 ; out: + resp->status =nfsd3_map_status (resp->status);return rpc_success; }
@@ -660,6 +696,7 @@ nfsd3_proc_fsstat(struct svc_rqst *rqstp) resp->status =nfsd_statfs (rqstp, &argp->fh, &resp->stats,0 );fh_put (&argp->fh); + resp->status =nfsd3_map_status (resp->status);return rpc_success; }
@@ -703,6 +740,7 @@ nfsd3_proc_fsinfo(struct svc_rqst *rqstp) }fh_put (&argp->fh); + resp->status =nfsd3_map_status (resp->status);return rpc_success; }
@@ -745,6 +783,7 @@ nfsd3_proc_pathconf(struct svc_rqst *rqstp) }fh_put (&argp->fh); + resp->status =nfsd3_map_status (resp->status);return rpc_success; }
@@ -772,6 +811,7 @@ nfsd3_proc_commit(struct svc_rqst *rqstp) argp->count, resp->verf);nfsd_file_put (nf); out: + resp->status =nfsd3_map_status (resp->status);return rpc_success; }
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 +33 -0
@@ -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; }
@@ -109,6 +128,7 @@ nfsd_proc_setattr(struct svc_rqst *rqstp) 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; }
@@ -200,6 +222,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; }
@@ -235,6 +258,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; }
@@ -403,6 +427,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; }
@@ -419,6 +444,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; }
@@ -437,6 +463,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; }
@@ -457,6 +484,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; }
@@ -495,6 +523,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; }
@@ -528,6 +557,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; }
@@ -545,6 +575,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; }
@@ -590,6 +621,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; }
@@ -607,6 +639,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/trace.h +27 -0
@@ -228,6 +228,33 @@ TRACE_EVENT(nfsd_fh_verify, ) ); +TRACE_EVENT (nfsd_vfs_create, +TP_PROTO ( + const struct svc_rqst *rqstp, + const struct svc_fh *fhp, + umode_t type, + const char *name, + unsigned int len + ), +TP_ARGS (rqstp, fhp, type, name, len), +TP_STRUCT__entry ( + __field(u32, xid) + __field(u32, fh_hash) + __field(umode_t, type) +__string_len (name, name, len) + ), +TP_fast_assign ( + __entry->xid = be32_to_cpu(rqstp->rq_xid); + __entry->fh_hash = knfsd_fh_hash(&fhp->fh_handle); + __entry->type = type; + __assign_str_len(name, name, len); + ), +TP_printk ("xid=0x%08x fh_hash=0x%08x type=%s name=%s" , + __entry->xid, __entry->fh_hash, +show_fs_file_type (__entry->type),__get_str (name) + ) +); + TRACE_EVENT_CONDITION(nfsd_fh_verify_err, TP_PROTO( const struct svc_rqst *rqstp,
fs/nfsd/vfs.c +5 -9
@@ -1538,6 +1538,8 @@ nfsd_create(struct svc_rqst *rqstp, struct svc_fh *fhp,__be32 err;int host_err; +trace_nfsd_vfs_create (rqstp, fhp, type, fname, flen); +if (isdotent (fname, flen))return nfserr_exist;
@@ -1745,10 +1747,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:
@@ -1814,7 +1813,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)
@@ -1993,10 +1992,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; } out:return err != nfs_ok ? err :nfserrno (host_err);
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
@@ -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) {
@@ -95,9 +114,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; }
@@ -261,6 +285,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 ))
@@ -408,8 +434,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; }
@@ -423,6 +454,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;
@@ -489,6 +524,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