diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2017-04-27 16:39:19 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2017-04-27 16:39:19 -0400 |
commit | 8b5d11e4b095450e2f259d5f60ea18c13d2fe0a2 (patch) | |
tree | 8f33faf583528a703b3bf5bbf69ad0e7dc3ad94b /fs | |
parent | 19ac4474203863a8141663d73d5976fe25464bfd (diff) | |
parent | 13bf9fbff0e5e099e2b6f003a0ab8ae145436309 (diff) |
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
Diffstat (limited to 'fs')
-rw-r--r-- | fs/nfsd/nfs3xdr.c | 13 | ||||
-rw-r--r-- | fs/nfsd/nfssvc.c | 36 | ||||
-rw-r--r-- | fs/nfsd/nfsxdr.c | 10 |
3 files changed, 51 insertions, 8 deletions
diff --git a/fs/nfsd/nfs3xdr.c b/fs/nfsd/nfs3xdr.c index dba2ff8eaa68..452334694a5d 100644 --- a/fs/nfsd/nfs3xdr.c +++ b/fs/nfsd/nfs3xdr.c | |||
@@ -358,6 +358,8 @@ nfs3svc_decode_writeargs(struct svc_rqst *rqstp, __be32 *p, | |||
358 | { | 358 | { |
359 | unsigned int len, v, hdr, dlen; | 359 | unsigned int len, v, hdr, dlen; |
360 | u32 max_blocksize = svc_max_payload(rqstp); | 360 | u32 max_blocksize = svc_max_payload(rqstp); |
361 | struct kvec *head = rqstp->rq_arg.head; | ||
362 | struct kvec *tail = rqstp->rq_arg.tail; | ||
361 | 363 | ||
362 | p = decode_fh(p, &args->fh); | 364 | p = decode_fh(p, &args->fh); |
363 | if (!p) | 365 | if (!p) |
@@ -367,6 +369,8 @@ nfs3svc_decode_writeargs(struct svc_rqst *rqstp, __be32 *p, | |||
367 | args->count = ntohl(*p++); | 369 | args->count = ntohl(*p++); |
368 | args->stable = ntohl(*p++); | 370 | args->stable = ntohl(*p++); |
369 | len = args->len = ntohl(*p++); | 371 | len = args->len = ntohl(*p++); |
372 | if ((void *)p > head->iov_base + head->iov_len) | ||
373 | return 0; | ||
370 | /* | 374 | /* |
371 | * The count must equal the amount of data passed. | 375 | * The count must equal the amount of data passed. |
372 | */ | 376 | */ |
@@ -377,9 +381,8 @@ nfs3svc_decode_writeargs(struct svc_rqst *rqstp, __be32 *p, | |||
377 | * Check to make sure that we got the right number of | 381 | * Check to make sure that we got the right number of |
378 | * bytes. | 382 | * bytes. |
379 | */ | 383 | */ |
380 | hdr = (void*)p - rqstp->rq_arg.head[0].iov_base; | 384 | hdr = (void*)p - head->iov_base; |
381 | dlen = rqstp->rq_arg.head[0].iov_len + rqstp->rq_arg.page_len | 385 | dlen = head->iov_len + rqstp->rq_arg.page_len + tail->iov_len - hdr; |
382 | + rqstp->rq_arg.tail[0].iov_len - hdr; | ||
383 | /* | 386 | /* |
384 | * Round the length of the data which was specified up to | 387 | * Round the length of the data which was specified up to |
385 | * the next multiple of XDR units and then compare that | 388 | * the next multiple of XDR units and then compare that |
@@ -396,7 +399,7 @@ nfs3svc_decode_writeargs(struct svc_rqst *rqstp, __be32 *p, | |||
396 | len = args->len = max_blocksize; | 399 | len = args->len = max_blocksize; |
397 | } | 400 | } |
398 | rqstp->rq_vec[0].iov_base = (void*)p; | 401 | 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; |
400 | v = 0; | 403 | v = 0; |
401 | while (len > rqstp->rq_vec[v].iov_len) { | 404 | while (len > rqstp->rq_vec[v].iov_len) { |
402 | len -= rqstp->rq_vec[v].iov_len; | 405 | len -= rqstp->rq_vec[v].iov_len; |
@@ -471,6 +474,8 @@ nfs3svc_decode_symlinkargs(struct svc_rqst *rqstp, __be32 *p, | |||
471 | /* first copy and check from the first page */ | 474 | /* first copy and check from the first page */ |
472 | old = (char*)p; | 475 | old = (char*)p; |
473 | vec = &rqstp->rq_arg.head[0]; | 476 | vec = &rqstp->rq_arg.head[0]; |
477 | if ((void *)old > vec->iov_base + vec->iov_len) | ||
478 | return 0; | ||
474 | avail = vec->iov_len - (old - (char*)vec->iov_base); | 479 | avail = vec->iov_len - (old - (char*)vec->iov_base); |
475 | while (len && avail && *old) { | 480 | while (len && avail && *old) { |
476 | *new++ = *old++; | 481 | *new++ = *old++; |
diff --git a/fs/nfsd/nfssvc.c b/fs/nfsd/nfssvc.c index 31e1f9593457..59979f0bbd4b 100644 --- a/fs/nfsd/nfssvc.c +++ b/fs/nfsd/nfssvc.c | |||
@@ -747,6 +747,37 @@ static __be32 map_new_errors(u32 vers, __be32 nfserr) | |||
747 | return nfserr; | 747 | return nfserr; |
748 | } | 748 | } |
749 | 749 | ||
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 | |||
750 | int | 781 | int |
751 | nfsd_dispatch(struct svc_rqst *rqstp, __be32 *statp) | 782 | nfsd_dispatch(struct svc_rqst *rqstp, __be32 *statp) |
752 | { | 783 | { |
@@ -759,6 +790,11 @@ nfsd_dispatch(struct svc_rqst *rqstp, __be32 *statp) | |||
759 | rqstp->rq_vers, rqstp->rq_proc); | 790 | rqstp->rq_vers, rqstp->rq_proc); |
760 | proc = rqstp->rq_procinfo; | 791 | proc = rqstp->rq_procinfo; |
761 | 792 | ||
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 | } | ||
762 | /* | 798 | /* |
763 | * Give the xdr decoder a chance to change this if it wants | 799 | * Give the xdr decoder a chance to change this if it wants |
764 | * (necessary in the NFSv4.0 compound case) | 800 | * (necessary in the NFSv4.0 compound case) |
diff --git a/fs/nfsd/nfsxdr.c b/fs/nfsd/nfsxdr.c index 41b468a6a90f..de07ff625777 100644 --- a/fs/nfsd/nfsxdr.c +++ b/fs/nfsd/nfsxdr.c | |||
@@ -280,6 +280,7 @@ nfssvc_decode_writeargs(struct svc_rqst *rqstp, __be32 *p, | |||
280 | struct nfsd_writeargs *args) | 280 | struct nfsd_writeargs *args) |
281 | { | 281 | { |
282 | unsigned int len, hdr, dlen; | 282 | unsigned int len, hdr, dlen; |
283 | struct kvec *head = rqstp->rq_arg.head; | ||
283 | int v; | 284 | int v; |
284 | 285 | ||
285 | p = decode_fh(p, &args->fh); | 286 | p = decode_fh(p, &args->fh); |
@@ -300,9 +301,10 @@ nfssvc_decode_writeargs(struct svc_rqst *rqstp, __be32 *p, | |||
300 | * Check to make sure that we got the right number of | 301 | * Check to make sure that we got the right number of |
301 | * bytes. | 302 | * bytes. |
302 | */ | 303 | */ |
303 | hdr = (void*)p - rqstp->rq_arg.head[0].iov_base; | 304 | hdr = (void*)p - head->iov_base; |
304 | dlen = rqstp->rq_arg.head[0].iov_len + rqstp->rq_arg.page_len | 305 | if (hdr > head->iov_len) |
305 | - hdr; | 306 | return 0; |
307 | dlen = head->iov_len + rqstp->rq_arg.page_len - hdr; | ||
306 | 308 | ||
307 | /* | 309 | /* |
308 | * Round the length of the data which was specified up to | 310 | * Round the length of the data which was specified up to |
@@ -316,7 +318,7 @@ nfssvc_decode_writeargs(struct svc_rqst *rqstp, __be32 *p, | |||
316 | return 0; | 318 | return 0; |
317 | 319 | ||
318 | rqstp->rq_vec[0].iov_base = (void*)p; | 320 | 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; |
320 | v = 0; | 322 | v = 0; |
321 | while (len > rqstp->rq_vec[v].iov_len) { | 323 | while (len > rqstp->rq_vec[v].iov_len) { |
322 | len -= rqstp->rq_vec[v].iov_len; | 324 | len -= rqstp->rq_vec[v].iov_len; |