Skip to content

Commit 8b5d11e

Browse files
committed
Merge tag 'nfsd-4.11-3' of git://linux-nfs.org/~bfields/linux
Pull nfsd fixes from Bruce Fields: "Thanks to Ari Kauppi and Tuomas Haanpää at Synopsis for spotting bugs in our NFSv2/v3 xdr code that could crash the server or leak memory" * tag 'nfsd-4.11-3' of git://linux-nfs.org/~bfields/linux: nfsd: stricter decoding of write-like NFSv2/v3 ops nfsd4: minor NFSv2/v3 write decoding cleanup nfsd: check for oversized NFSv2/v3 arguments
2 parents 19ac447 + 13bf9fb commit 8b5d11e

File tree

3 files changed

+51
-8
lines changed

3 files changed

+51
-8
lines changed

fs/nfsd/nfs3xdr.c

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,8 @@ nfs3svc_decode_writeargs(struct svc_rqst *rqstp, __be32 *p,
358358
{
359359
unsigned int len, v, hdr, dlen;
360360
u32 max_blocksize = svc_max_payload(rqstp);
361+
struct kvec *head = rqstp->rq_arg.head;
362+
struct kvec *tail = rqstp->rq_arg.tail;
361363

362364
p = decode_fh(p, &args->fh);
363365
if (!p)
@@ -367,6 +369,8 @@ nfs3svc_decode_writeargs(struct svc_rqst *rqstp, __be32 *p,
367369
args->count = ntohl(*p++);
368370
args->stable = ntohl(*p++);
369371
len = args->len = ntohl(*p++);
372+
if ((void *)p > head->iov_base + head->iov_len)
373+
return 0;
370374
/*
371375
* The count must equal the amount of data passed.
372376
*/
@@ -377,9 +381,8 @@ nfs3svc_decode_writeargs(struct svc_rqst *rqstp, __be32 *p,
377381
* Check to make sure that we got the right number of
378382
* bytes.
379383
*/
380-
hdr = (void*)p - rqstp->rq_arg.head[0].iov_base;
381-
dlen = rqstp->rq_arg.head[0].iov_len + rqstp->rq_arg.page_len
382-
+ rqstp->rq_arg.tail[0].iov_len - hdr;
384+
hdr = (void*)p - head->iov_base;
385+
dlen = head->iov_len + rqstp->rq_arg.page_len + tail->iov_len - hdr;
383386
/*
384387
* Round the length of the data which was specified up to
385388
* the next multiple of XDR units and then compare that
@@ -396,7 +399,7 @@ nfs3svc_decode_writeargs(struct svc_rqst *rqstp, __be32 *p,
396399
len = args->len = max_blocksize;
397400
}
398401
rqstp->rq_vec[0].iov_base = (void*)p;
399-
rqstp->rq_vec[0].iov_len = rqstp->rq_arg.head[0].iov_len - hdr;
402+
rqstp->rq_vec[0].iov_len = head->iov_len - hdr;
400403
v = 0;
401404
while (len > rqstp->rq_vec[v].iov_len) {
402405
len -= rqstp->rq_vec[v].iov_len;
@@ -471,6 +474,8 @@ nfs3svc_decode_symlinkargs(struct svc_rqst *rqstp, __be32 *p,
471474
/* first copy and check from the first page */
472475
old = (char*)p;
473476
vec = &rqstp->rq_arg.head[0];
477+
if ((void *)old > vec->iov_base + vec->iov_len)
478+
return 0;
474479
avail = vec->iov_len - (old - (char*)vec->iov_base);
475480
while (len && avail && *old) {
476481
*new++ = *old++;

fs/nfsd/nfssvc.c

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,37 @@ static __be32 map_new_errors(u32 vers, __be32 nfserr)
747747
return nfserr;
748748
}
749749

750+
/*
751+
* A write procedure can have a large argument, and a read procedure can
752+
* have a large reply, but no NFSv2 or NFSv3 procedure has argument and
753+
* reply that can both be larger than a page. The xdr code has taken
754+
* advantage of this assumption to be a sloppy about bounds checking in
755+
* some cases. Pending a rewrite of the NFSv2/v3 xdr code to fix that
756+
* problem, we enforce these assumptions here:
757+
*/
758+
static bool nfs_request_too_big(struct svc_rqst *rqstp,
759+
struct svc_procedure *proc)
760+
{
761+
/*
762+
* The ACL code has more careful bounds-checking and is not
763+
* susceptible to this problem:
764+
*/
765+
if (rqstp->rq_prog != NFS_PROGRAM)
766+
return false;
767+
/*
768+
* Ditto NFSv4 (which can in theory have argument and reply both
769+
* more than a page):
770+
*/
771+
if (rqstp->rq_vers >= 4)
772+
return false;
773+
/* The reply will be small, we're OK: */
774+
if (proc->pc_xdrressize > 0 &&
775+
proc->pc_xdrressize < XDR_QUADLEN(PAGE_SIZE))
776+
return false;
777+
778+
return rqstp->rq_arg.len > PAGE_SIZE;
779+
}
780+
750781
int
751782
nfsd_dispatch(struct svc_rqst *rqstp, __be32 *statp)
752783
{
@@ -759,6 +790,11 @@ nfsd_dispatch(struct svc_rqst *rqstp, __be32 *statp)
759790
rqstp->rq_vers, rqstp->rq_proc);
760791
proc = rqstp->rq_procinfo;
761792

793+
if (nfs_request_too_big(rqstp, proc)) {
794+
dprintk("nfsd: NFSv%d argument too large\n", rqstp->rq_vers);
795+
*statp = rpc_garbage_args;
796+
return 1;
797+
}
762798
/*
763799
* Give the xdr decoder a chance to change this if it wants
764800
* (necessary in the NFSv4.0 compound case)

fs/nfsd/nfsxdr.c

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,7 @@ nfssvc_decode_writeargs(struct svc_rqst *rqstp, __be32 *p,
280280
struct nfsd_writeargs *args)
281281
{
282282
unsigned int len, hdr, dlen;
283+
struct kvec *head = rqstp->rq_arg.head;
283284
int v;
284285

285286
p = decode_fh(p, &args->fh);
@@ -300,9 +301,10 @@ nfssvc_decode_writeargs(struct svc_rqst *rqstp, __be32 *p,
300301
* Check to make sure that we got the right number of
301302
* bytes.
302303
*/
303-
hdr = (void*)p - rqstp->rq_arg.head[0].iov_base;
304-
dlen = rqstp->rq_arg.head[0].iov_len + rqstp->rq_arg.page_len
305-
- hdr;
304+
hdr = (void*)p - head->iov_base;
305+
if (hdr > head->iov_len)
306+
return 0;
307+
dlen = head->iov_len + rqstp->rq_arg.page_len - hdr;
306308

307309
/*
308310
* Round the length of the data which was specified up to
@@ -316,7 +318,7 @@ nfssvc_decode_writeargs(struct svc_rqst *rqstp, __be32 *p,
316318
return 0;
317319

318320
rqstp->rq_vec[0].iov_base = (void*)p;
319-
rqstp->rq_vec[0].iov_len = rqstp->rq_arg.head[0].iov_len - hdr;
321+
rqstp->rq_vec[0].iov_len = head->iov_len - hdr;
320322
v = 0;
321323
while (len > rqstp->rq_vec[v].iov_len) {
322324
len -= rqstp->rq_vec[v].iov_len;

0 commit comments

Comments
 (0)