Notable bug fixes:
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. -----BEGIN PGP SIGNATURE----- iQIzBAABCAAdFiEEKLLlsBKG3yQ88j7+M2qzM29mf5cFAmIDzr4ACgkQM2qzM29m f5eJRBAAikdh0PYOlZbvy9M1eY6wq3k+Y10JsnCZk4T8Uq0NJF/7CJ3R4/4+xGOh ZA/2vE1dN4IfqnIOdxw1cXbzzgAO5p/nDLMo9wC6NimrVLkE+S8j38oWvEHOCJXC TzUbIKkxqBBcfDw4pO4BT42iHx+cqVUuRFd2qkob1ZRoe+BKI+F4+7QNVc8iEw5z j85i2/h6JohsItzekRbMO1q1iXxBc+IZRYafjibtVRWxRuNUWP8C1cv0eXrlSy3O L07kZRwzrd52PAi1Q8K07Ip+yTHUMZptyHoB6S863uuz/mOzlpXewvXHMGA1btlr POHYG/lBXpDS0e2pjksyXXp2I7HJV/HuaMyyLveWRO0qleBc3G5PsvIJNBW7xl5f NPGdgfaa+8ZeOCGolvPruykL9Eh7QAyWTdPKz1J+NuhjkAB4p6ba9QcKVwP7kYTi I8zdeUPgbjuFW35hal0ZIlNi2RfcuSGk1FKjotrQ6J3XNIaqPkUWK+1Zz3MzqPUW +1ElzoXQugJASPBkEZuf1aXr8/vRjKT16l8EX1kbtJ5wjj2OPbnWWZk03ZncLVfv CzbJTZLqiM0JuRqXvYpUGAQdryWcwvTCAuWxcqrt4ALNWW6Z4Y35Vl8H4sTh8wkr Q3m6bAVYJx3FmFop7y5ubVH137k1SFJ0NzGJJK0mYoZQSMZoPZI= =64n/ -----END PGP SIGNATURE----- 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
This commit is contained in:
commit
f4bc5bbb5f
@ -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;
|
||||
@ -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);
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
@ -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;
|
||||
|
@ -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
|
||||
@ -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,
|
||||
|
@ -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);
|
||||
@ -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))
|
||||
@ -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));
|
||||
|
@ -306,14 +306,14 @@ 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);
|
||||
@ -321,7 +321,7 @@ DECLARE_EVENT_CLASS(nfsd_io_class,
|
||||
__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)
|
||||
)
|
||||
@ -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);
|
||||
|
@ -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;
|
||||
@ -1110,42 +1114,61 @@ out:
|
||||
}
|
||||
|
||||
#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);
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user