Commit 8b5d11e4 authored by Linus Torvalds's avatar Linus Torvalds

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
parents 19ac4474 13bf9fbf
...@@ -358,6 +358,8 @@ nfs3svc_decode_writeargs(struct svc_rqst *rqstp, __be32 *p, ...@@ -358,6 +358,8 @@ nfs3svc_decode_writeargs(struct svc_rqst *rqstp, __be32 *p,
{ {
unsigned int len, v, hdr, dlen; unsigned int len, v, hdr, dlen;
u32 max_blocksize = svc_max_payload(rqstp); u32 max_blocksize = svc_max_payload(rqstp);
struct kvec *head = rqstp->rq_arg.head;
struct kvec *tail = rqstp->rq_arg.tail;
p = decode_fh(p, &args->fh); p = decode_fh(p, &args->fh);
if (!p) if (!p)
...@@ -367,6 +369,8 @@ nfs3svc_decode_writeargs(struct svc_rqst *rqstp, __be32 *p, ...@@ -367,6 +369,8 @@ nfs3svc_decode_writeargs(struct svc_rqst *rqstp, __be32 *p,
args->count = ntohl(*p++); args->count = ntohl(*p++);
args->stable = ntohl(*p++); args->stable = ntohl(*p++);
len = args->len = ntohl(*p++); len = args->len = ntohl(*p++);
if ((void *)p > head->iov_base + head->iov_len)
return 0;
/* /*
* The count must equal the amount of data passed. * The count must equal the amount of data passed.
*/ */
...@@ -377,9 +381,8 @@ nfs3svc_decode_writeargs(struct svc_rqst *rqstp, __be32 *p, ...@@ -377,9 +381,8 @@ nfs3svc_decode_writeargs(struct svc_rqst *rqstp, __be32 *p,
* Check to make sure that we got the right number of * Check to make sure that we got the right number of
* bytes. * bytes.
*/ */
hdr = (void*)p - rqstp->rq_arg.head[0].iov_base; hdr = (void*)p - head->iov_base;
dlen = rqstp->rq_arg.head[0].iov_len + rqstp->rq_arg.page_len dlen = head->iov_len + rqstp->rq_arg.page_len + tail->iov_len - hdr;
+ rqstp->rq_arg.tail[0].iov_len - hdr;
/* /*
* Round the length of the data which was specified up to * Round the length of the data which was specified up to
* the next multiple of XDR units and then compare that * the next multiple of XDR units and then compare that
...@@ -396,7 +399,7 @@ nfs3svc_decode_writeargs(struct svc_rqst *rqstp, __be32 *p, ...@@ -396,7 +399,7 @@ nfs3svc_decode_writeargs(struct svc_rqst *rqstp, __be32 *p,
len = args->len = max_blocksize; len = args->len = max_blocksize;
} }
rqstp->rq_vec[0].iov_base = (void*)p; rqstp->rq_vec[0].iov_base = (void*)p;
rqstp->rq_vec[0].iov_len = rqstp->rq_arg.head[0].iov_len - hdr; rqstp->rq_vec[0].iov_len = head->iov_len - hdr;
v = 0; v = 0;
while (len > rqstp->rq_vec[v].iov_len) { while (len > rqstp->rq_vec[v].iov_len) {
len -= rqstp->rq_vec[v].iov_len; len -= rqstp->rq_vec[v].iov_len;
...@@ -471,6 +474,8 @@ nfs3svc_decode_symlinkargs(struct svc_rqst *rqstp, __be32 *p, ...@@ -471,6 +474,8 @@ nfs3svc_decode_symlinkargs(struct svc_rqst *rqstp, __be32 *p,
/* first copy and check from the first page */ /* first copy and check from the first page */
old = (char*)p; old = (char*)p;
vec = &rqstp->rq_arg.head[0]; vec = &rqstp->rq_arg.head[0];
if ((void *)old > vec->iov_base + vec->iov_len)
return 0;
avail = vec->iov_len - (old - (char*)vec->iov_base); avail = vec->iov_len - (old - (char*)vec->iov_base);
while (len && avail && *old) { while (len && avail && *old) {
*new++ = *old++; *new++ = *old++;
......
...@@ -747,6 +747,37 @@ static __be32 map_new_errors(u32 vers, __be32 nfserr) ...@@ -747,6 +747,37 @@ static __be32 map_new_errors(u32 vers, __be32 nfserr)
return nfserr; return nfserr;
} }
/*
* A write procedure can have a large argument, and a read procedure can
* have a large reply, but no NFSv2 or NFSv3 procedure has argument and
* reply that can both be larger than a page. The xdr code has taken
* advantage of this assumption to be a sloppy about bounds checking in
* some cases. Pending a rewrite of the NFSv2/v3 xdr code to fix that
* problem, we enforce these assumptions here:
*/
static bool nfs_request_too_big(struct svc_rqst *rqstp,
struct svc_procedure *proc)
{
/*
* The ACL code has more careful bounds-checking and is not
* susceptible to this problem:
*/
if (rqstp->rq_prog != NFS_PROGRAM)
return false;
/*
* Ditto NFSv4 (which can in theory have argument and reply both
* more than a page):
*/
if (rqstp->rq_vers >= 4)
return false;
/* The reply will be small, we're OK: */
if (proc->pc_xdrressize > 0 &&
proc->pc_xdrressize < XDR_QUADLEN(PAGE_SIZE))
return false;
return rqstp->rq_arg.len > PAGE_SIZE;
}
int int
nfsd_dispatch(struct svc_rqst *rqstp, __be32 *statp) nfsd_dispatch(struct svc_rqst *rqstp, __be32 *statp)
{ {
...@@ -759,6 +790,11 @@ nfsd_dispatch(struct svc_rqst *rqstp, __be32 *statp) ...@@ -759,6 +790,11 @@ nfsd_dispatch(struct svc_rqst *rqstp, __be32 *statp)
rqstp->rq_vers, rqstp->rq_proc); rqstp->rq_vers, rqstp->rq_proc);
proc = rqstp->rq_procinfo; proc = rqstp->rq_procinfo;
if (nfs_request_too_big(rqstp, proc)) {
dprintk("nfsd: NFSv%d argument too large\n", rqstp->rq_vers);
*statp = rpc_garbage_args;
return 1;
}
/* /*
* Give the xdr decoder a chance to change this if it wants * Give the xdr decoder a chance to change this if it wants
* (necessary in the NFSv4.0 compound case) * (necessary in the NFSv4.0 compound case)
......
...@@ -280,6 +280,7 @@ nfssvc_decode_writeargs(struct svc_rqst *rqstp, __be32 *p, ...@@ -280,6 +280,7 @@ nfssvc_decode_writeargs(struct svc_rqst *rqstp, __be32 *p,
struct nfsd_writeargs *args) struct nfsd_writeargs *args)
{ {
unsigned int len, hdr, dlen; unsigned int len, hdr, dlen;
struct kvec *head = rqstp->rq_arg.head;
int v; int v;
p = decode_fh(p, &args->fh); p = decode_fh(p, &args->fh);
...@@ -300,9 +301,10 @@ nfssvc_decode_writeargs(struct svc_rqst *rqstp, __be32 *p, ...@@ -300,9 +301,10 @@ nfssvc_decode_writeargs(struct svc_rqst *rqstp, __be32 *p,
* Check to make sure that we got the right number of * Check to make sure that we got the right number of
* bytes. * bytes.
*/ */
hdr = (void*)p - rqstp->rq_arg.head[0].iov_base; hdr = (void*)p - head->iov_base;
dlen = rqstp->rq_arg.head[0].iov_len + rqstp->rq_arg.page_len if (hdr > head->iov_len)
- hdr; return 0;
dlen = head->iov_len + rqstp->rq_arg.page_len - hdr;
/* /*
* Round the length of the data which was specified up to * 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,7 +318,7 @@ nfssvc_decode_writeargs(struct svc_rqst *rqstp, __be32 *p,
return 0; return 0;
rqstp->rq_vec[0].iov_base = (void*)p; rqstp->rq_vec[0].iov_base = (void*)p;
rqstp->rq_vec[0].iov_len = rqstp->rq_arg.head[0].iov_len - hdr; rqstp->rq_vec[0].iov_len = head->iov_len - hdr;
v = 0; v = 0;
while (len > rqstp->rq_vec[v].iov_len) { while (len > rqstp->rq_vec[v].iov_len) {
len -= rqstp->rq_vec[v].iov_len; len -= rqstp->rq_vec[v].iov_len;
......
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