From: Chuck Lever chuck.lever@oracle.com
mainline inclusion from mainline-v5.17-rc4 commit 0cb4d23ae08c48f6bf3c29a8e5c4a74b8388b960 category: bugfix bugzilla: https://gitee.com/src-openeuler/kernel/issues/IADG80 CVE: CVE-2022-48827
Reference: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?i...
--------------------------------
Dan Aloni reports:
Due to commit 8cfb9015280d ("NFS: Always provide aligned buffers to the RPC read layers") on the client, a read of 0xfff is aligned up to server rsize of 0x1000.
As a result, in a test where the server has a file of size 0x7fffffffffffffff, and the client tries to read from the offset 0x7ffffffffffff000, the read causes loff_t overflow in the server and it returns an NFS code of EINVAL to the client. The client as a result indefinitely retries the request.
The Linux NFS client does not handle NFS?ERR_INVAL, even though all NFS specifications permit servers to return that status code for a READ.
Instead of NFS?ERR_INVAL, have out-of-range READ requests succeed and return a short result. Set the EOF flag in the result to prevent the client from retrying the READ request. This behavior appears to be consistent with Solaris NFS servers.
Note that NFSv3 and NFSv4 use u64 offset values on the wire. These must be converted to loff_t internally before use -- an implicit type cast is not adequate for this purpose. Otherwise VFS checks against sb->s_maxbytes do not work properly.
Reported-by: Dan Aloni dan.aloni@vastdata.com Cc: stable@vger.kernel.org Signed-off-by: Chuck Lever chuck.lever@oracle.com
Conflicts: fs/nfsd/nfs3proc.c fs/nfsd/nfs4proc.c fs/nfsd/nfs4xdr.c [Commit be63bd2ac6bb("NFSD: Update READ3arg decoder to use struct xdr_stream") modified the method of initializing resp->count; Commit 5c4583b2b78e("nfsd: hook up nfs4_preprocess_stateid_op to the nfsd_file cache") remove rd_filp from nfsd4_read and remove the check of rd_tmp_file in nfsd4_encode_read; Commit 528b84934eb9("NFSD: Add READ_PLUS data support") add READ_PLUS data support.] Signed-off-by: Li Lingfeng lilingfeng3@huawei.com --- fs/nfsd/nfs3proc.c | 11 ++++++++--- fs/nfsd/nfs4proc.c | 8 ++++++-- fs/nfsd/nfs4xdr.c | 5 +---- 3 files changed, 15 insertions(+), 9 deletions(-)
diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c index c9cf46e0c040..2ac8f5912dfa 100644 --- a/fs/nfsd/nfs3proc.c +++ b/fs/nfsd/nfs3proc.c @@ -154,18 +154,23 @@ nfsd3_proc_read(struct svc_rqst *rqstp) struct nfsd3_readres *resp = rqstp->rq_resp; __be32 nfserr; u32 max_blocksize = svc_max_payload(rqstp); - unsigned long cnt = min(argp->count, max_blocksize);
dprintk("nfsd: READ(3) %s %lu bytes at %Lu\n", SVCFH_fmt(&argp->fh), (unsigned long) argp->count, (unsigned long long) argp->offset);
+ argp->count = min_t(u32, argp->count, max_blocksize); + if (argp->offset > (u64)OFFSET_MAX) + argp->offset = (u64)OFFSET_MAX; + if (argp->offset + argp->count > (u64)OFFSET_MAX) + argp->count = (u64)OFFSET_MAX - argp->offset; + /* Obtain buffer pointer for payload. * 1 (status) + 22 (post_op_attr) + 1 (count) + 1 (eof) * + 1 (xdr opaque byte count) = 26 */ - resp->count = cnt; + resp->count = argp->count; svc_reserve_auth(rqstp, ((1 + NFS3_POST_OP_ATTR_WORDS + 3)<<2) + resp->count +4);
fh_copy(&resp->fh, &argp->fh); @@ -175,7 +180,7 @@ nfsd3_proc_read(struct svc_rqst *rqstp) &resp->count); if (nfserr == 0) { struct inode *inode = d_inode(resp->fh.fh_dentry); - resp->eof = nfsd_eof_on_read(cnt, resp->count, argp->offset, + resp->eof = nfsd_eof_on_read(resp->count, resp->count, argp->offset, inode->i_size); }
diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c index 1c3e6de6bcba..8bb0b6d2ea4e 100644 --- a/fs/nfsd/nfs4proc.c +++ b/fs/nfsd/nfs4proc.c @@ -760,12 +760,16 @@ nfsd4_read(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, __be32 status;
read->rd_filp = NULL; - if (read->rd_offset >= OFFSET_MAX) - return nfserr_inval;
trace_nfsd_read_start(rqstp, &cstate->current_fh, read->rd_offset, read->rd_length);
+ read->rd_length = min_t(u32, read->rd_length, svc_max_payload(rqstp)); + if (read->rd_offset > (u64)OFFSET_MAX) + read->rd_offset = (u64)OFFSET_MAX; + if (read->rd_offset + read->rd_length > (u64)OFFSET_MAX) + read->rd_length = (u64)OFFSET_MAX - read->rd_offset; + /* * If we do a zero copy read, then a client will see read data * that reflects the state of the file *after* performing the diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c index db0beefe65ec..7dae38d21041 100644 --- a/fs/nfsd/nfs4xdr.c +++ b/fs/nfsd/nfs4xdr.c @@ -3595,11 +3595,8 @@ nfsd4_encode_read(struct nfsd4_compoundres *resp, __be32 nfserr, } xdr_commit_encode(xdr);
- maxcount = svc_max_payload(resp->rqstp); - maxcount = min_t(unsigned long, maxcount, + maxcount = min_t(unsigned long, read->rd_length, (xdr->buf->buflen - xdr->buf->len)); - maxcount = min_t(unsigned long, maxcount, read->rd_length); - if (read->rd_tmp_file) ra = nfsd_init_raparms(file);