Commit f4bc5bbb authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'nfsd-5.17-2' of git://git.kernel.org/pub/scm/linux/kernel/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
parents f9f94c9d c306d737
...@@ -150,13 +150,17 @@ nfsd3_proc_read(struct svc_rqst *rqstp) ...@@ -150,13 +150,17 @@ nfsd3_proc_read(struct svc_rqst *rqstp)
unsigned int len; unsigned int len;
int v; int v;
argp->count = min_t(u32, argp->count, max_blocksize);
dprintk("nfsd: READ(3) %s %lu bytes at %Lu\n", dprintk("nfsd: READ(3) %s %lu bytes at %Lu\n",
SVCFH_fmt(&argp->fh), SVCFH_fmt(&argp->fh),
(unsigned long) argp->count, (unsigned long) argp->count,
(unsigned long long) argp->offset); (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; v = 0;
len = argp->count; len = argp->count;
resp->pages = rqstp->rq_next_page; resp->pages = rqstp->rq_next_page;
...@@ -199,6 +203,11 @@ nfsd3_proc_write(struct svc_rqst *rqstp) ...@@ -199,6 +203,11 @@ nfsd3_proc_write(struct svc_rqst *rqstp)
(unsigned long long) argp->offset, (unsigned long long) argp->offset,
argp->stable? " stable" : ""); 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); fh_copy(&resp->fh, &argp->fh);
resp->committed = argp->stable; resp->committed = argp->stable;
nvecs = svc_fill_write_vector(rqstp, &argp->payload); nvecs = svc_fill_write_vector(rqstp, &argp->payload);
...@@ -651,15 +660,9 @@ nfsd3_proc_commit(struct svc_rqst *rqstp) ...@@ -651,15 +660,9 @@ nfsd3_proc_commit(struct svc_rqst *rqstp)
argp->count, argp->count,
(unsigned long long) argp->offset); (unsigned long long) argp->offset);
if (argp->offset > NFS_OFFSET_MAX) {
resp->status = nfserr_inval;
goto out;
}
fh_copy(&resp->fh, &argp->fh); fh_copy(&resp->fh, &argp->fh);
resp->status = nfsd_commit(rqstp, &resp->fh, argp->offset, resp->status = nfsd_commit(rqstp, &resp->fh, argp->offset,
argp->count, resp->verf); argp->count, resp->verf);
out:
return rpc_success; return rpc_success;
} }
......
...@@ -254,7 +254,7 @@ svcxdr_decode_sattr3(struct svc_rqst *rqstp, struct xdr_stream *xdr, ...@@ -254,7 +254,7 @@ svcxdr_decode_sattr3(struct svc_rqst *rqstp, struct xdr_stream *xdr,
if (xdr_stream_decode_u64(xdr, &newsize) < 0) if (xdr_stream_decode_u64(xdr, &newsize) < 0)
return false; return false;
iap->ia_valid |= ATTR_SIZE; 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) if (xdr_stream_decode_u32(xdr, &set_it) < 0)
return false; return false;
...@@ -1060,7 +1060,7 @@ svcxdr_encode_entry3_common(struct nfsd3_readdirres *resp, const char *name, ...@@ -1060,7 +1060,7 @@ svcxdr_encode_entry3_common(struct nfsd3_readdirres *resp, const char *name,
return false; return false;
/* cookie */ /* cookie */
resp->cookie_offset = dirlist->len; 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 false;
return true; return true;
......
...@@ -782,12 +782,16 @@ nfsd4_read(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, ...@@ -782,12 +782,16 @@ nfsd4_read(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
__be32 status; __be32 status;
read->rd_nf = NULL; read->rd_nf = NULL;
if (read->rd_offset >= OFFSET_MAX)
return nfserr_inval;
trace_nfsd_read_start(rqstp, &cstate->current_fh, trace_nfsd_read_start(rqstp, &cstate->current_fh,
read->rd_offset, read->rd_length); 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 * If we do a zero copy read, then a client will see read data
* that reflects the state of the file *after* performing the * that reflects the state of the file *after* performing the
...@@ -1018,8 +1022,9 @@ nfsd4_write(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, ...@@ -1018,8 +1022,9 @@ nfsd4_write(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
unsigned long cnt; unsigned long cnt;
int nvecs; int nvecs;
if (write->wr_offset >= OFFSET_MAX) if (write->wr_offset > (u64)OFFSET_MAX ||
return nfserr_inval; write->wr_offset + write->wr_buflen > (u64)OFFSET_MAX)
return nfserr_fbig;
cnt = write->wr_buflen; cnt = write->wr_buflen;
trace_nfsd_write_start(rqstp, &cstate->current_fh, trace_nfsd_write_start(rqstp, &cstate->current_fh,
......
...@@ -3495,7 +3495,7 @@ nfsd4_encode_dirent(void *ccdv, const char *name, int namlen, ...@@ -3495,7 +3495,7 @@ nfsd4_encode_dirent(void *ccdv, const char *name, int namlen,
p = xdr_reserve_space(xdr, 3*4 + namlen); p = xdr_reserve_space(xdr, 3*4 + namlen);
if (!p) if (!p)
goto fail; 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 */ p = xdr_encode_array(p, name, namlen); /* name length & name */
nfserr = nfsd4_encode_dirent_fattr(xdr, cd, name, namlen); nfserr = nfsd4_encode_dirent_fattr(xdr, cd, name, namlen);
...@@ -3986,10 +3986,8 @@ nfsd4_encode_read(struct nfsd4_compoundres *resp, __be32 nfserr, ...@@ -3986,10 +3986,8 @@ nfsd4_encode_read(struct nfsd4_compoundres *resp, __be32 nfserr,
} }
xdr_commit_encode(xdr); xdr_commit_encode(xdr);
maxcount = svc_max_payload(resp->rqstp); maxcount = min_t(unsigned long, read->rd_length,
maxcount = min_t(unsigned long, maxcount,
(xdr->buf->buflen - xdr->buf->len)); (xdr->buf->buflen - xdr->buf->len));
maxcount = min_t(unsigned long, maxcount, read->rd_length);
if (file->f_op->splice_read && if (file->f_op->splice_read &&
test_bit(RQ_SPLICE_OK, &resp->rqstp->rq_flags)) test_bit(RQ_SPLICE_OK, &resp->rqstp->rq_flags))
...@@ -4826,10 +4824,8 @@ nfsd4_encode_read_plus(struct nfsd4_compoundres *resp, __be32 nfserr, ...@@ -4826,10 +4824,8 @@ nfsd4_encode_read_plus(struct nfsd4_compoundres *resp, __be32 nfserr,
return nfserr_resource; return nfserr_resource;
xdr_commit_encode(xdr); xdr_commit_encode(xdr);
maxcount = svc_max_payload(resp->rqstp); maxcount = min_t(unsigned long, read->rd_length,
maxcount = min_t(unsigned long, maxcount,
(xdr->buf->buflen - xdr->buf->len)); (xdr->buf->buflen - xdr->buf->len));
maxcount = min_t(unsigned long, maxcount, read->rd_length);
count = maxcount; count = maxcount;
eof = read->rd_offset >= i_size_read(file_inode(file)); eof = read->rd_offset >= i_size_read(file_inode(file));
......
...@@ -306,14 +306,14 @@ TRACE_EVENT(nfsd_export_update, ...@@ -306,14 +306,14 @@ TRACE_EVENT(nfsd_export_update,
DECLARE_EVENT_CLASS(nfsd_io_class, DECLARE_EVENT_CLASS(nfsd_io_class,
TP_PROTO(struct svc_rqst *rqstp, TP_PROTO(struct svc_rqst *rqstp,
struct svc_fh *fhp, struct svc_fh *fhp,
loff_t offset, u64 offset,
unsigned long len), u32 len),
TP_ARGS(rqstp, fhp, offset, len), TP_ARGS(rqstp, fhp, offset, len),
TP_STRUCT__entry( TP_STRUCT__entry(
__field(u32, xid) __field(u32, xid)
__field(u32, fh_hash) __field(u32, fh_hash)
__field(loff_t, offset) __field(u64, offset)
__field(unsigned long, len) __field(u32, len)
), ),
TP_fast_assign( TP_fast_assign(
__entry->xid = be32_to_cpu(rqstp->rq_xid); __entry->xid = be32_to_cpu(rqstp->rq_xid);
...@@ -321,7 +321,7 @@ DECLARE_EVENT_CLASS(nfsd_io_class, ...@@ -321,7 +321,7 @@ DECLARE_EVENT_CLASS(nfsd_io_class,
__entry->offset = offset; __entry->offset = offset;
__entry->len = len; __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->xid, __entry->fh_hash,
__entry->offset, __entry->len) __entry->offset, __entry->len)
) )
...@@ -330,8 +330,8 @@ DECLARE_EVENT_CLASS(nfsd_io_class, ...@@ -330,8 +330,8 @@ DECLARE_EVENT_CLASS(nfsd_io_class,
DEFINE_EVENT(nfsd_io_class, nfsd_##name, \ DEFINE_EVENT(nfsd_io_class, nfsd_##name, \
TP_PROTO(struct svc_rqst *rqstp, \ TP_PROTO(struct svc_rqst *rqstp, \
struct svc_fh *fhp, \ struct svc_fh *fhp, \
loff_t offset, \ u64 offset, \
unsigned long len), \ u32 len), \
TP_ARGS(rqstp, fhp, offset, len)) TP_ARGS(rqstp, fhp, offset, len))
DEFINE_NFSD_IO_EVENT(read_start); DEFINE_NFSD_IO_EVENT(read_start);
......
...@@ -435,6 +435,10 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, struct iattr *iap, ...@@ -435,6 +435,10 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, struct iattr *iap,
.ia_size = iap->ia_size, .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); host_err = notify_change(&init_user_ns, dentry, &size_attr, NULL);
if (host_err) if (host_err)
goto out_unlock; goto out_unlock;
...@@ -1110,42 +1114,61 @@ nfsd_write(struct svc_rqst *rqstp, struct svc_fh *fhp, loff_t offset, ...@@ -1110,42 +1114,61 @@ nfsd_write(struct svc_rqst *rqstp, struct svc_fh *fhp, loff_t offset,
} }
#ifdef CONFIG_NFSD_V3 #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 * Note: we guarantee that data that lies within the range specified
* by the 'offset' and 'count' parameters will be synced. * 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 * 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. * data to the client, as locking happens lower down in the filesystem.
*
* Return values:
* An nfsstat value in network byte order.
*/ */
__be32 __be32
nfsd_commit(struct svc_rqst *rqstp, struct svc_fh *fhp, nfsd_commit(struct svc_rqst *rqstp, struct svc_fh *fhp, u64 offset,
loff_t offset, unsigned long count, __be32 *verf) u32 count, __be32 *verf)
{ {
u64 maxbytes;
loff_t start, end;
struct nfsd_net *nn; struct nfsd_net *nn;
struct nfsd_file *nf; struct nfsd_file *nf;
loff_t end = LLONG_MAX; __be32 err;
__be32 err = nfserr_inval;
if (offset < 0)
goto out;
if (count != 0) {
end = offset + (loff_t)count - 1;
if (end < offset)
goto out;
}
err = nfsd_file_acquire(rqstp, fhp, err = nfsd_file_acquire(rqstp, fhp,
NFSD_MAY_WRITE|NFSD_MAY_NOT_BREAK_LEASE, &nf); NFSD_MAY_WRITE|NFSD_MAY_NOT_BREAK_LEASE, &nf);
if (err) if (err)
goto out; 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); nn = net_generic(nf->nf_net, nfsd_net_id);
if (EX_ISSYNC(fhp->fh_export)) { if (EX_ISSYNC(fhp->fh_export)) {
errseq_t since = READ_ONCE(nf->nf_file->f_wb_err); errseq_t since = READ_ONCE(nf->nf_file->f_wb_err);
int err2; int err2;
err2 = vfs_fsync_range(nf->nf_file, offset, end, 0); err2 = vfs_fsync_range(nf->nf_file, start, end, 0);
switch (err2) { switch (err2) {
case 0: case 0:
nfsd_copy_write_verifier(verf, nn); nfsd_copy_write_verifier(verf, nn);
......
...@@ -74,8 +74,8 @@ __be32 do_nfsd_create(struct svc_rqst *, struct svc_fh *, ...@@ -74,8 +74,8 @@ __be32 do_nfsd_create(struct svc_rqst *, struct svc_fh *,
char *name, int len, struct iattr *attrs, char *name, int len, struct iattr *attrs,
struct svc_fh *res, int createmode, struct svc_fh *res, int createmode,
u32 *verifier, bool *truncp, bool *created); u32 *verifier, bool *truncp, bool *created);
__be32 nfsd_commit(struct svc_rqst *, struct svc_fh *, __be32 nfsd_commit(struct svc_rqst *rqst, struct svc_fh *fhp,
loff_t, unsigned long, __be32 *verf); u64 offset, u32 count, __be32 *verf);
#endif /* CONFIG_NFSD_V3 */ #endif /* CONFIG_NFSD_V3 */
#ifdef CONFIG_NFSD_V4 #ifdef CONFIG_NFSD_V4
__be32 nfsd_getxattr(struct svc_rqst *rqstp, struct svc_fh *fhp, __be32 nfsd_getxattr(struct svc_rqst *rqstp, struct svc_fh *fhp,
......
...@@ -36,14 +36,6 @@ static inline void nfs_copy_fh(struct nfs_fh *target, const struct nfs_fh *sourc ...@@ -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); 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 { enum nfs3_stable_how {
NFS_UNSTABLE = 0, NFS_UNSTABLE = 0,
NFS_DATA_SYNC = 1, NFS_DATA_SYNC = 1,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment