Re: [PATCH v3 1/3] nfsd: reject out-of-range useconds in NFSv2 SETATTR/CREATE

"Chuck Lever" <[email protected]>
Newsgroups gmane.linux.nfs
Message-ID <[email protected]>

On Tue, Jun 16, 2026, at 3:18 PM, Jeff Layton wrote:
> On Tue, 2026-06-16 at 13:39 +0800, robbieko wrote:
>> From: Robbie Ko <robbieko-UelDjCVBxVpWk0Htik3J/[email protected]>
>> 
>> The NFSv2 sattr decoder converts the wire useconds to nanoseconds in
>> svcxdr_decode_sattr():
>> 
>> 	iap->ia_atime.tv_nsec = tmp2 * NSEC_PER_USEC;
>> 
>> tmp2 is a u32 and NSEC_PER_USEC is 1000, so the product is computed in
>> unsigned long. On ILP32 that is 32 bits, and an out-of-range useconds
>> value such as 4294968 wraps to tv_nsec == 704. The corruption therefore
>> happens during decode, before any proc function can inspect the value,
>> and a later range check on tv_nsec would see an in-range result and
>> accept it.
>> 
>> Guard the raw useconds before the multiplication and reject values
>> greater than 1000000. useconds == 1000000 is kept: it is the Sun
>> convention for "set to the current server time", and the in-tree Linux
>> NFSv2 client emits it in both the atime and the mtime field for a plain
>> touch / utimes(file, NULL) (see encode_sattr() and
>> xdr_encode_current_server_time() in fs/nfs/nfs2xdr.c). Rejecting 1000000
>> would turn that common operation into a hard decode failure for both
>> SETATTR and CREATE. 1000000 * NSEC_PER_USEC is 10^9, which does not wrap
>> on ILP32, so the Sun convention value passes through safely; only
>> genuinely out-of-range values (> 1000000) are rejected. The atime and
>> mtime guards are therefore symmetric.
>> 
>> The decoder only applied the Sun convention in the mtime block, which
>> clears ATTR_ATIME_SET|ATTR_MTIME_SET when mtime useconds == 1000000. If a
>> client puts 1000000 in the atime field but not in the mtime field, the
>> atime block stored an out-of-range tv_nsec (10^9) and left ATTR_ATIME_SET
>> set, so the bogus value reached the filesystem. Apply the convention in
>> the atime block as well, clearing ATTR_ATIME_SET so the server uses its
>> current time and ignores the value. Only ATTR_ATIME_SET is cleared there;
>> the mtime block keeps its existing behavior, where 1000000 means "set
>> both atime and mtime to now".
>> 
>> Signed-off-by: Robbie Ko <robbieko-UelDjCVBxVpWk0Htik3J/[email protected]>
>> ---
>>  fs/nfsd/nfsxdr.c | 31 +++++++++++++++++++++++++++++++
>>  1 file changed, 31 insertions(+)
>> 
>> diff --git a/fs/nfsd/nfsxdr.c b/fs/nfsd/nfsxdr.c
>> index ae71e0621317..48a4e89a5f41 100644
>> --- a/fs/nfsd/nfsxdr.c
>> +++ b/fs/nfsd/nfsxdr.c
>> @@ -172,14 +172,45 @@ svcxdr_decode_sattr(struct svc_rqst *rqstp, struct xdr_stream *xdr,
>>  	tmp1 = be32_to_cpup(p++);
>>  	tmp2 = be32_to_cpup(p++);
>>  	if (tmp1 != (u32)-1 && tmp2 != (u32)-1) {
>> +		/*
>> +		 * Guard the raw useconds before the unit conversion below.
>> +		 * tmp2 * NSEC_PER_USEC is computed in unsigned long, which is
>> +		 * 32 bits on ILP32, so an out-of-range value would wrap and
>> +		 * silently produce a bogus in-range tv_nsec. useconds ==
>> +		 * 1000000 is the Sun "set to current server time" convention
>> +		 * (see the mtime block below); allow it and reject anything
>> +		 * larger. Note 1000000 * NSEC_PER_USEC is 10^9, which does not
>> +		 * wrap on ILP32.
>> +		 */
>> +		if (tmp2 > 1000000)
>> +			return false;
>
> The logic looks fine, but rather than having these verbose comments
> above each use of 1000000, it'd be better to declare a constant and
> document its use once above that.
>
> Something like this (and consolidate the comments above that):
>
> #define		SUN_V2_SET_TO_NOW	1000000
>
> Though I think the current fashion is to use enums for this so they
> show up in the debugger.

The applied version of this patch defines such a symbolic constant.
Robbie, no need to resend.


-- 
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.