Re: [PATCH] nfsd: reject out-of-range nseconds in NFSv3 nfstime3 decode

"Chuck Lever" <[email protected]>
Newsgroups gmane.linux.nfs
Message-ID <[email protected]>
On Thu, Jun 11, 2026, at 12:09 AM, robbieko wrote:
> From: Robbie Ko <robbieko-UelDjCVBxVpWk0Htik3J/[email protected]>
>
> The NFSv3 nfstime3 decoder svcxdr_decode_nfstime3() accepts the 32-bit
> nseconds field from the wire without validating its range. RFC 1813
> does not constrain the value, but a tv_nsec >= NSEC_PER_SEC is not a
> valid timespec64. The NFSv4 decoder nfsd4_decode_nfstime4() already
> rejects such values with NFS4ERR_INVAL; NFSv3 had no equivalent check.
>
> A malicious or buggy client can therefore send a SETATTR carrying an
> out-of-range nseconds (up to 4294967295). The value flows through
> nfsd_setattr() -> notify_change() -> timestamp_truncate(), which does
> not clamp tv_nsec to < NSEC_PER_SEC when the filesystem supports
> nanosecond granularity (s_time_gran == 1). 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 (year) are wrong on read-back. XFS with bigtime
> mis-stores the timestamp for the same reason. This is silent, with no
> WARN_ON anywhere in the path to catch it.
>
> Validate the decoded nseconds in svcxdr_decode_nfstime3() and fail the
> XDR decode if it is out of range, mirroring the NFSv4 behavior.
>
> Signed-off-by: Robbie Ko <robbieko-UelDjCVBxVpWk0Htik3J/[email protected]>
> ---
>  fs/nfsd/nfs3xdr.c | 2 ++
>  1 file changed, 2 insertions(+)
>
> diff --git a/fs/nfsd/nfs3xdr.c b/fs/nfsd/nfs3xdr.c
> index 2ff9a991a8fb..59e82ce03c20 100644
> --- a/fs/nfsd/nfs3xdr.c
> +++ b/fs/nfsd/nfs3xdr.c
> @@ -64,6 +64,8 @@ svcxdr_decode_nfstime3(struct xdr_stream *xdr, struct 
> timespec64 *timep)
>  		return false;
>  	timep->tv_sec = be32_to_cpup(p++);
>  	timep->tv_nsec = be32_to_cpup(p);
> +	if (timep->tv_nsec >= (u32)1000000000)
> +		return false;
> 
>  	return true;
>  }

I agree this issue needs to be addressed, but I have a reservation
about how the request is rejected.

Returning false from svcxdr_decode_nfstime3() turns into SVC_GARBAGE,
so the client gets an RPC-level GARBAGE_ARGS. But an nseconds of
4294967295 is perfectly well-formed XDR: it decodes cleanly into a
valid uint32. Nothing about the request failed to parse. GARBAGE_ARGS
tells the client "I could not decode your arguments," which is not
what happened -- we decoded them fine and are rejecting the value.

RFC 1813 supports a different response here. It places no range
constraint on nfstime3.nseconds, and the SETATTR ERRORS list includes
NFS3ERR_INVAL. The IMPLEMENTATION notes describe NFS3ERR_INVAL as the
error for a value the server "can not store ... in its own
representation" -- an out-of-range nanoseconds is exactly that class
of problem. So the RFC-aligned status is NFS3ERR_INVAL, returned from
the SETATTR proc, not GARBAGE_ARGS from the XDR layer.

That is also what NFSv4 actually does. nfsd4_decode_nfstime4() returns
nfserr_inval, which becomes the SETATTR operation's status -- an
INVAL the client maps to EINVAL. The v3 patch returns GARBAGE_ARGS,
which maps to EIO. So the two paths produce different errors on the
wire, and the commit message's claim that this "mirrors the NFSv4
behavior" is not accurate. I'd ask that the message be reworded to say
the value is well-formed XDR but not a valid timespec64, rather than
framing it as an XDR failure or a 1:1 match with v4.

One more wrinkle: svcxdr_decode_nfstime3() is also used to decode
sattrguard3.obj_ctime. The guard is a comparison value, not a value
to be stored; RFC 1813 says a guard that does not match the object's
ctime yields NFS3ERR_NOT_SYNC. An out-of-range guard ctime simply
never matches, so the protocol-correct outcome there is NOT_SYNC.
Rejecting it at decode converts a legitimate stale-guard case into
"your RPC is garbage."

So let's rework this fix to handle the range check in the proc
functions and return appropriate NFSv3 protocol status codes rather
than rejecting the RPC Call outright.


-- 
Chuck Lever
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.