Skip to content

Commit

Permalink
Merge tag 'nfsd-5.17-2' of git://git.kernel.org/pub/scm/linux/kernel/…
Browse files Browse the repository at this point in the history
…git/cel/linux

Pull more nfsd fixes from Chuck Lever:
 "Ensure that NFS clients cannot send file size or offset values that
  can cause the NFS server to crash or to return incorrect or surprising
  results.

  In particular, fix how the NFS server handles values larger than
  OFFSET_MAX"

* tag 'nfsd-5.17-2' of git://git.kernel.org/pub/scm/linux/kernel/git/cel/linux:
  NFSD: Deprecate NFS_OFFSET_MAX
  NFSD: Fix offset type in I/O trace points
  NFSD: COMMIT operations must not return NFS?ERR_INVAL
  NFSD: Clamp WRITE offsets
  NFSD: Fix NFSv3 SETATTR/CREATE's handling of large file sizes
  NFSD: Fix ia_size underflow
  NFSD: Fix the behavior of READ near OFFSET_MAX
  • Loading branch information
torvalds committed Feb 9, 2022
2 parents f9f94c9 + c306d73 commit f4bc5bb
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 55 deletions.
19 changes: 11 additions & 8 deletions fs/nfsd/nfs3proc.c
Original file line number Diff line number Diff line change
Expand Up @@ -150,13 +150,17 @@ nfsd3_proc_read(struct svc_rqst *rqstp)
unsigned int len;
int v;

argp->count = min_t(u32, 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;

v = 0;
len = argp->count;
resp->pages = rqstp->rq_next_page;
Expand Down Expand Up @@ -199,6 +203,11 @@ nfsd3_proc_write(struct svc_rqst *rqstp)
(unsigned long long) argp->offset,
argp->stable? " stable" : "");

resp->status = nfserr_fbig;
if (argp->offset > (u64)OFFSET_MAX ||
argp->offset + argp->len > (u64)OFFSET_MAX)
return rpc_success;

fh_copy(&resp->fh, &argp->fh);
resp->committed = argp->stable;
nvecs = svc_fill_write_vector(rqstp, &argp->payload);
Expand Down Expand Up @@ -651,15 +660,9 @@ nfsd3_proc_commit(struct svc_rqst *rqstp)
argp->count,
(unsigned long long) argp->offset);

if (argp->offset > NFS_OFFSET_MAX) {
resp->status = nfserr_inval;
goto out;
}

fh_copy(&resp->fh, &argp->fh);
resp->status = nfsd_commit(rqstp, &resp->fh, argp->offset,
argp->count, resp->verf);
out:
return rpc_success;
}

Expand Down
4 changes: 2 additions & 2 deletions fs/nfsd/nfs3xdr.c
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ svcxdr_decode_sattr3(struct svc_rqst *rqstp, struct xdr_stream *xdr,
if (xdr_stream_decode_u64(xdr, &newsize) < 0)
return false;
iap->ia_valid |= ATTR_SIZE;
iap->ia_size = min_t(u64, newsize, NFS_OFFSET_MAX);
iap->ia_size = newsize;
}
if (xdr_stream_decode_u32(xdr, &set_it) < 0)
return false;
Expand Down Expand Up @@ -1060,7 +1060,7 @@ svcxdr_encode_entry3_common(struct nfsd3_readdirres *resp, const char *name,
return false;
/* cookie */
resp->cookie_offset = dirlist->len;
if (xdr_stream_encode_u64(xdr, NFS_OFFSET_MAX) < 0)
if (xdr_stream_encode_u64(xdr, OFFSET_MAX) < 0)
return false;

return true;
Expand Down
13 changes: 9 additions & 4 deletions fs/nfsd/nfs4proc.c
Original file line number Diff line number Diff line change
Expand Up @@ -782,12 +782,16 @@ nfsd4_read(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
__be32 status;

read->rd_nf = 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
Expand Down Expand Up @@ -1018,8 +1022,9 @@ nfsd4_write(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
unsigned long cnt;
int nvecs;

if (write->wr_offset >= OFFSET_MAX)
return nfserr_inval;
if (write->wr_offset > (u64)OFFSET_MAX ||
write->wr_offset + write->wr_buflen > (u64)OFFSET_MAX)
return nfserr_fbig;

cnt = write->wr_buflen;
trace_nfsd_write_start(rqstp, &cstate->current_fh,
Expand Down
10 changes: 3 additions & 7 deletions fs/nfsd/nfs4xdr.c
Original file line number Diff line number Diff line change
Expand Up @@ -3495,7 +3495,7 @@ nfsd4_encode_dirent(void *ccdv, const char *name, int namlen,
p = xdr_reserve_space(xdr, 3*4 + namlen);
if (!p)
goto fail;
p = xdr_encode_hyper(p, NFS_OFFSET_MAX); /* offset of next entry */
p = xdr_encode_hyper(p, OFFSET_MAX); /* offset of next entry */
p = xdr_encode_array(p, name, namlen); /* name length & name */

nfserr = nfsd4_encode_dirent_fattr(xdr, cd, name, namlen);
Expand Down Expand Up @@ -3986,10 +3986,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 (file->f_op->splice_read &&
test_bit(RQ_SPLICE_OK, &resp->rqstp->rq_flags))
Expand Down Expand Up @@ -4826,10 +4824,8 @@ nfsd4_encode_read_plus(struct nfsd4_compoundres *resp, __be32 nfserr,
return nfserr_resource;
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);
count = maxcount;

eof = read->rd_offset >= i_size_read(file_inode(file));
Expand Down
14 changes: 7 additions & 7 deletions fs/nfsd/trace.h
Original file line number Diff line number Diff line change
Expand Up @@ -306,22 +306,22 @@ TRACE_EVENT(nfsd_export_update,
DECLARE_EVENT_CLASS(nfsd_io_class,
TP_PROTO(struct svc_rqst *rqstp,
struct svc_fh *fhp,
loff_t offset,
unsigned long len),
u64 offset,
u32 len),
TP_ARGS(rqstp, fhp, offset, len),
TP_STRUCT__entry(
__field(u32, xid)
__field(u32, fh_hash)
__field(loff_t, offset)
__field(unsigned long, len)
__field(u64, offset)
__field(u32, len)
),
TP_fast_assign(
__entry->xid = be32_to_cpu(rqstp->rq_xid);
__entry->fh_hash = knfsd_fh_hash(&fhp->fh_handle);
__entry->offset = offset;
__entry->len = len;
),
TP_printk("xid=0x%08x fh_hash=0x%08x offset=%lld len=%lu",
TP_printk("xid=0x%08x fh_hash=0x%08x offset=%llu len=%u",
__entry->xid, __entry->fh_hash,
__entry->offset, __entry->len)
)
Expand All @@ -330,8 +330,8 @@ DECLARE_EVENT_CLASS(nfsd_io_class,
DEFINE_EVENT(nfsd_io_class, nfsd_##name, \
TP_PROTO(struct svc_rqst *rqstp, \
struct svc_fh *fhp, \
loff_t offset, \
unsigned long len), \
u64 offset, \
u32 len), \
TP_ARGS(rqstp, fhp, offset, len))

DEFINE_NFSD_IO_EVENT(read_start);
Expand Down
57 changes: 40 additions & 17 deletions fs/nfsd/vfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,10 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, struct iattr *iap,
.ia_size = iap->ia_size,
};

host_err = -EFBIG;
if (iap->ia_size < 0)
goto out_unlock;

host_err = notify_change(&init_user_ns, dentry, &size_attr, NULL);
if (host_err)
goto out_unlock;
Expand Down Expand Up @@ -1110,42 +1114,61 @@ nfsd_write(struct svc_rqst *rqstp, struct svc_fh *fhp, loff_t offset,
}

#ifdef CONFIG_NFSD_V3
/*
* Commit all pending writes to stable storage.
/**
* nfsd_commit - Commit pending writes to stable storage
* @rqstp: RPC request being processed
* @fhp: NFS filehandle
* @offset: raw offset from beginning of file
* @count: raw count of bytes to sync
* @verf: filled in with the server's current write verifier
*
* Note: we only guarantee that data that lies within the range specified
* by the 'offset' and 'count' parameters will be synced.
* Note: we guarantee that data that lies within the range specified
* by the 'offset' and 'count' parameters will be synced. The server
* is permitted to sync data that lies outside this range at the
* same time.
*
* Unfortunately we cannot lock the file to make sure we return full WCC
* data to the client, as locking happens lower down in the filesystem.
*
* Return values:
* An nfsstat value in network byte order.
*/
__be32
nfsd_commit(struct svc_rqst *rqstp, struct svc_fh *fhp,
loff_t offset, unsigned long count, __be32 *verf)
nfsd_commit(struct svc_rqst *rqstp, struct svc_fh *fhp, u64 offset,
u32 count, __be32 *verf)
{
u64 maxbytes;
loff_t start, end;
struct nfsd_net *nn;
struct nfsd_file *nf;
loff_t end = LLONG_MAX;
__be32 err = nfserr_inval;

if (offset < 0)
goto out;
if (count != 0) {
end = offset + (loff_t)count - 1;
if (end < offset)
goto out;
}
__be32 err;

err = nfsd_file_acquire(rqstp, fhp,
NFSD_MAY_WRITE|NFSD_MAY_NOT_BREAK_LEASE, &nf);
if (err)
goto out;

/*
* Convert the client-provided (offset, count) range to a
* (start, end) range. If the client-provided range falls
* outside the maximum file size of the underlying FS,
* clamp the sync range appropriately.
*/
start = 0;
end = LLONG_MAX;
maxbytes = (u64)fhp->fh_dentry->d_sb->s_maxbytes;
if (offset < maxbytes) {
start = offset;
if (count && (offset + count - 1 < maxbytes))
end = offset + count - 1;
}

nn = net_generic(nf->nf_net, nfsd_net_id);
if (EX_ISSYNC(fhp->fh_export)) {
errseq_t since = READ_ONCE(nf->nf_file->f_wb_err);
int err2;

err2 = vfs_fsync_range(nf->nf_file, offset, end, 0);
err2 = vfs_fsync_range(nf->nf_file, start, end, 0);
switch (err2) {
case 0:
nfsd_copy_write_verifier(verf, nn);
Expand Down
4 changes: 2 additions & 2 deletions fs/nfsd/vfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ __be32 do_nfsd_create(struct svc_rqst *, struct svc_fh *,
char *name, int len, struct iattr *attrs,
struct svc_fh *res, int createmode,
u32 *verifier, bool *truncp, bool *created);
__be32 nfsd_commit(struct svc_rqst *, struct svc_fh *,
loff_t, unsigned long, __be32 *verf);
__be32 nfsd_commit(struct svc_rqst *rqst, struct svc_fh *fhp,
u64 offset, u32 count, __be32 *verf);
#endif /* CONFIG_NFSD_V3 */
#ifdef CONFIG_NFSD_V4
__be32 nfsd_getxattr(struct svc_rqst *rqstp, struct svc_fh *fhp,
Expand Down
8 changes: 0 additions & 8 deletions include/linux/nfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,6 @@ static inline void nfs_copy_fh(struct nfs_fh *target, const struct nfs_fh *sourc
memcpy(target->data, source->data, source->size);
}


/*
* This is really a general kernel constant, but since nothing like
* this is defined in the kernel headers, I have to do it here.
*/
#define NFS_OFFSET_MAX ((__s64)((~(__u64)0) >> 1))


enum nfs3_stable_how {
NFS_UNSTABLE = 0,
NFS_DATA_SYNC = 1,
Expand Down

0 comments on commit f4bc5bb

Please sign in to comment.