Skip to content

Commit f4bc5bb

Browse files
committed
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
2 parents f9f94c9 + c306d73 commit f4bc5bb

File tree

8 files changed

+74
-55
lines changed

8 files changed

+74
-55
lines changed

fs/nfsd/nfs3proc.c

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -150,13 +150,17 @@ nfsd3_proc_read(struct svc_rqst *rqstp)
150150
unsigned int len;
151151
int v;
152152

153-
argp->count = min_t(u32, argp->count, max_blocksize);
154-
155153
dprintk("nfsd: READ(3) %s %lu bytes at %Lu\n",
156154
SVCFH_fmt(&argp->fh),
157155
(unsigned long) argp->count,
158156
(unsigned long long) argp->offset);
159157

158+
argp->count = min_t(u32, argp->count, max_blocksize);
159+
if (argp->offset > (u64)OFFSET_MAX)
160+
argp->offset = (u64)OFFSET_MAX;
161+
if (argp->offset + argp->count > (u64)OFFSET_MAX)
162+
argp->count = (u64)OFFSET_MAX - argp->offset;
163+
160164
v = 0;
161165
len = argp->count;
162166
resp->pages = rqstp->rq_next_page;
@@ -199,6 +203,11 @@ nfsd3_proc_write(struct svc_rqst *rqstp)
199203
(unsigned long long) argp->offset,
200204
argp->stable? " stable" : "");
201205

206+
resp->status = nfserr_fbig;
207+
if (argp->offset > (u64)OFFSET_MAX ||
208+
argp->offset + argp->len > (u64)OFFSET_MAX)
209+
return rpc_success;
210+
202211
fh_copy(&resp->fh, &argp->fh);
203212
resp->committed = argp->stable;
204213
nvecs = svc_fill_write_vector(rqstp, &argp->payload);
@@ -651,15 +660,9 @@ nfsd3_proc_commit(struct svc_rqst *rqstp)
651660
argp->count,
652661
(unsigned long long) argp->offset);
653662

654-
if (argp->offset > NFS_OFFSET_MAX) {
655-
resp->status = nfserr_inval;
656-
goto out;
657-
}
658-
659663
fh_copy(&resp->fh, &argp->fh);
660664
resp->status = nfsd_commit(rqstp, &resp->fh, argp->offset,
661665
argp->count, resp->verf);
662-
out:
663666
return rpc_success;
664667
}
665668

fs/nfsd/nfs3xdr.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ svcxdr_decode_sattr3(struct svc_rqst *rqstp, struct xdr_stream *xdr,
254254
if (xdr_stream_decode_u64(xdr, &newsize) < 0)
255255
return false;
256256
iap->ia_valid |= ATTR_SIZE;
257-
iap->ia_size = min_t(u64, newsize, NFS_OFFSET_MAX);
257+
iap->ia_size = newsize;
258258
}
259259
if (xdr_stream_decode_u32(xdr, &set_it) < 0)
260260
return false;
@@ -1060,7 +1060,7 @@ svcxdr_encode_entry3_common(struct nfsd3_readdirres *resp, const char *name,
10601060
return false;
10611061
/* cookie */
10621062
resp->cookie_offset = dirlist->len;
1063-
if (xdr_stream_encode_u64(xdr, NFS_OFFSET_MAX) < 0)
1063+
if (xdr_stream_encode_u64(xdr, OFFSET_MAX) < 0)
10641064
return false;
10651065

10661066
return true;

fs/nfsd/nfs4proc.c

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -782,12 +782,16 @@ nfsd4_read(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
782782
__be32 status;
783783

784784
read->rd_nf = NULL;
785-
if (read->rd_offset >= OFFSET_MAX)
786-
return nfserr_inval;
787785

788786
trace_nfsd_read_start(rqstp, &cstate->current_fh,
789787
read->rd_offset, read->rd_length);
790788

789+
read->rd_length = min_t(u32, read->rd_length, svc_max_payload(rqstp));
790+
if (read->rd_offset > (u64)OFFSET_MAX)
791+
read->rd_offset = (u64)OFFSET_MAX;
792+
if (read->rd_offset + read->rd_length > (u64)OFFSET_MAX)
793+
read->rd_length = (u64)OFFSET_MAX - read->rd_offset;
794+
791795
/*
792796
* If we do a zero copy read, then a client will see read data
793797
* 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,
10181022
unsigned long cnt;
10191023
int nvecs;
10201024

1021-
if (write->wr_offset >= OFFSET_MAX)
1022-
return nfserr_inval;
1025+
if (write->wr_offset > (u64)OFFSET_MAX ||
1026+
write->wr_offset + write->wr_buflen > (u64)OFFSET_MAX)
1027+
return nfserr_fbig;
10231028

10241029
cnt = write->wr_buflen;
10251030
trace_nfsd_write_start(rqstp, &cstate->current_fh,

fs/nfsd/nfs4xdr.c

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3495,7 +3495,7 @@ nfsd4_encode_dirent(void *ccdv, const char *name, int namlen,
34953495
p = xdr_reserve_space(xdr, 3*4 + namlen);
34963496
if (!p)
34973497
goto fail;
3498-
p = xdr_encode_hyper(p, NFS_OFFSET_MAX); /* offset of next entry */
3498+
p = xdr_encode_hyper(p, OFFSET_MAX); /* offset of next entry */
34993499
p = xdr_encode_array(p, name, namlen); /* name length & name */
35003500

35013501
nfserr = nfsd4_encode_dirent_fattr(xdr, cd, name, namlen);
@@ -3986,10 +3986,8 @@ nfsd4_encode_read(struct nfsd4_compoundres *resp, __be32 nfserr,
39863986
}
39873987
xdr_commit_encode(xdr);
39883988

3989-
maxcount = svc_max_payload(resp->rqstp);
3990-
maxcount = min_t(unsigned long, maxcount,
3989+
maxcount = min_t(unsigned long, read->rd_length,
39913990
(xdr->buf->buflen - xdr->buf->len));
3992-
maxcount = min_t(unsigned long, maxcount, read->rd_length);
39933991

39943992
if (file->f_op->splice_read &&
39953993
test_bit(RQ_SPLICE_OK, &resp->rqstp->rq_flags))
@@ -4826,10 +4824,8 @@ nfsd4_encode_read_plus(struct nfsd4_compoundres *resp, __be32 nfserr,
48264824
return nfserr_resource;
48274825
xdr_commit_encode(xdr);
48284826

4829-
maxcount = svc_max_payload(resp->rqstp);
4830-
maxcount = min_t(unsigned long, maxcount,
4827+
maxcount = min_t(unsigned long, read->rd_length,
48314828
(xdr->buf->buflen - xdr->buf->len));
4832-
maxcount = min_t(unsigned long, maxcount, read->rd_length);
48334829
count = maxcount;
48344830

48354831
eof = read->rd_offset >= i_size_read(file_inode(file));

fs/nfsd/trace.h

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -306,22 +306,22 @@ TRACE_EVENT(nfsd_export_update,
306306
DECLARE_EVENT_CLASS(nfsd_io_class,
307307
TP_PROTO(struct svc_rqst *rqstp,
308308
struct svc_fh *fhp,
309-
loff_t offset,
310-
unsigned long len),
309+
u64 offset,
310+
u32 len),
311311
TP_ARGS(rqstp, fhp, offset, len),
312312
TP_STRUCT__entry(
313313
__field(u32, xid)
314314
__field(u32, fh_hash)
315-
__field(loff_t, offset)
316-
__field(unsigned long, len)
315+
__field(u64, offset)
316+
__field(u32, len)
317317
),
318318
TP_fast_assign(
319319
__entry->xid = be32_to_cpu(rqstp->rq_xid);
320320
__entry->fh_hash = knfsd_fh_hash(&fhp->fh_handle);
321321
__entry->offset = offset;
322322
__entry->len = len;
323323
),
324-
TP_printk("xid=0x%08x fh_hash=0x%08x offset=%lld len=%lu",
324+
TP_printk("xid=0x%08x fh_hash=0x%08x offset=%llu len=%u",
325325
__entry->xid, __entry->fh_hash,
326326
__entry->offset, __entry->len)
327327
)
@@ -330,8 +330,8 @@ DECLARE_EVENT_CLASS(nfsd_io_class,
330330
DEFINE_EVENT(nfsd_io_class, nfsd_##name, \
331331
TP_PROTO(struct svc_rqst *rqstp, \
332332
struct svc_fh *fhp, \
333-
loff_t offset, \
334-
unsigned long len), \
333+
u64 offset, \
334+
u32 len), \
335335
TP_ARGS(rqstp, fhp, offset, len))
336336

337337
DEFINE_NFSD_IO_EVENT(read_start);

fs/nfsd/vfs.c

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,10 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, struct iattr *iap,
435435
.ia_size = iap->ia_size,
436436
};
437437

438+
host_err = -EFBIG;
439+
if (iap->ia_size < 0)
440+
goto out_unlock;
441+
438442
host_err = notify_change(&init_user_ns, dentry, &size_attr, NULL);
439443
if (host_err)
440444
goto out_unlock;
@@ -1110,42 +1114,61 @@ nfsd_write(struct svc_rqst *rqstp, struct svc_fh *fhp, loff_t offset,
11101114
}
11111115

11121116
#ifdef CONFIG_NFSD_V3
1113-
/*
1114-
* Commit all pending writes to stable storage.
1117+
/**
1118+
* nfsd_commit - Commit pending writes to stable storage
1119+
* @rqstp: RPC request being processed
1120+
* @fhp: NFS filehandle
1121+
* @offset: raw offset from beginning of file
1122+
* @count: raw count of bytes to sync
1123+
* @verf: filled in with the server's current write verifier
11151124
*
1116-
* Note: we only guarantee that data that lies within the range specified
1117-
* by the 'offset' and 'count' parameters will be synced.
1125+
* Note: we guarantee that data that lies within the range specified
1126+
* by the 'offset' and 'count' parameters will be synced. The server
1127+
* is permitted to sync data that lies outside this range at the
1128+
* same time.
11181129
*
11191130
* Unfortunately we cannot lock the file to make sure we return full WCC
11201131
* data to the client, as locking happens lower down in the filesystem.
1132+
*
1133+
* Return values:
1134+
* An nfsstat value in network byte order.
11211135
*/
11221136
__be32
1123-
nfsd_commit(struct svc_rqst *rqstp, struct svc_fh *fhp,
1124-
loff_t offset, unsigned long count, __be32 *verf)
1137+
nfsd_commit(struct svc_rqst *rqstp, struct svc_fh *fhp, u64 offset,
1138+
u32 count, __be32 *verf)
11251139
{
1140+
u64 maxbytes;
1141+
loff_t start, end;
11261142
struct nfsd_net *nn;
11271143
struct nfsd_file *nf;
1128-
loff_t end = LLONG_MAX;
1129-
__be32 err = nfserr_inval;
1130-
1131-
if (offset < 0)
1132-
goto out;
1133-
if (count != 0) {
1134-
end = offset + (loff_t)count - 1;
1135-
if (end < offset)
1136-
goto out;
1137-
}
1144+
__be32 err;
11381145

11391146
err = nfsd_file_acquire(rqstp, fhp,
11401147
NFSD_MAY_WRITE|NFSD_MAY_NOT_BREAK_LEASE, &nf);
11411148
if (err)
11421149
goto out;
1150+
1151+
/*
1152+
* Convert the client-provided (offset, count) range to a
1153+
* (start, end) range. If the client-provided range falls
1154+
* outside the maximum file size of the underlying FS,
1155+
* clamp the sync range appropriately.
1156+
*/
1157+
start = 0;
1158+
end = LLONG_MAX;
1159+
maxbytes = (u64)fhp->fh_dentry->d_sb->s_maxbytes;
1160+
if (offset < maxbytes) {
1161+
start = offset;
1162+
if (count && (offset + count - 1 < maxbytes))
1163+
end = offset + count - 1;
1164+
}
1165+
11431166
nn = net_generic(nf->nf_net, nfsd_net_id);
11441167
if (EX_ISSYNC(fhp->fh_export)) {
11451168
errseq_t since = READ_ONCE(nf->nf_file->f_wb_err);
11461169
int err2;
11471170

1148-
err2 = vfs_fsync_range(nf->nf_file, offset, end, 0);
1171+
err2 = vfs_fsync_range(nf->nf_file, start, end, 0);
11491172
switch (err2) {
11501173
case 0:
11511174
nfsd_copy_write_verifier(verf, nn);

fs/nfsd/vfs.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ __be32 do_nfsd_create(struct svc_rqst *, struct svc_fh *,
7474
char *name, int len, struct iattr *attrs,
7575
struct svc_fh *res, int createmode,
7676
u32 *verifier, bool *truncp, bool *created);
77-
__be32 nfsd_commit(struct svc_rqst *, struct svc_fh *,
78-
loff_t, unsigned long, __be32 *verf);
77+
__be32 nfsd_commit(struct svc_rqst *rqst, struct svc_fh *fhp,
78+
u64 offset, u32 count, __be32 *verf);
7979
#endif /* CONFIG_NFSD_V3 */
8080
#ifdef CONFIG_NFSD_V4
8181
__be32 nfsd_getxattr(struct svc_rqst *rqstp, struct svc_fh *fhp,

include/linux/nfs.h

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,6 @@ static inline void nfs_copy_fh(struct nfs_fh *target, const struct nfs_fh *sourc
3636
memcpy(target->data, source->data, source->size);
3737
}
3838

39-
40-
/*
41-
* This is really a general kernel constant, but since nothing like
42-
* this is defined in the kernel headers, I have to do it here.
43-
*/
44-
#define NFS_OFFSET_MAX ((__s64)((~(__u64)0) >> 1))
45-
46-
4739
enum nfs3_stable_how {
4840
NFS_UNSTABLE = 0,
4941
NFS_DATA_SYNC = 1,

0 commit comments

Comments
 (0)