NFS: Check lengths more thoroughly in NFS4 readdir XDR decode
Subsystems (3)
PATCH 1/1 NFS: Check lengths more thoroughly in NFS4 readdir XDR decode
Check the bounds of length specifiers more thoroughly in the XDR decoding of NFS4 readdir reply data.
Currently, if the server returns a bitmap or attr length that causes the current decode point pointer to wrap, this could go undetected (consider a small "negative" length on a 32-bit machine).
Also add a check into the main XDR decode handler to make sure that the amount of data is a multiple of four bytes (as specified by RFC-1014). This makes sure that we can do u32* pointer subtraction in the NFS client without risking an undefined result (the result is undefined if the pointers are not correctly aligned with respect to one another).
---
fs/nfs/nfs4xdr.c | 22 +++++++++++----------- net/sunrpc/clnt.c | 11 +++++++++++ 2 files changed, 22 insertions(+), 11 deletions(-)
fs/nfs/nfs4xdr.c +11 -11
@@ -3355,7 +3355,7 @@ static int decode_readdir(struct xdr_strstruct kvec *iov = rcvbuf->head;unsigned int nr, pglen = rcvbuf->page_len;uint32_t *end, *entry, *p, *kaddr; -uint32_t len, attrlen; +uint32_t len, attrlen, xlen;int hdrlen, recvd, status; status =decode_op_hdr (xdr,OP_READDIR );
@@ -3374,13 +3374,12 @@ static int decode_readdir(struct xdr_strif (pglen > recvd) pglen = recvd;xdr_read_pages (xdr, pglen); -BUG_ON (pglen + readdir->pgbase >PAGE_CACHE_SIZE ); kaddr = p = (uint32_t *)kmap_atomic (page,KM_USER0 ); - end = (uint32_t *) ((char *)p + pglen + readdir->pgbase); + end = p + ((pglen + readdir->pgbase) >>2 ); entry = p;for (nr =0 ; *p++; nr++) { -if (p +3 > end) +if (end - p <3 ) goto short_pkt;dprintk ("cookie = %Lu, " , *((unsigned long long *)p)); p +=2 ;/* cookie */
@@ -3389,18 +3388,19 @@ static int decode_readdir(struct xdr_strprintk (KERN_WARNING "NFS: giant filename in readdir (len 0x%x)\n" , len); goto err_unmap; } -dprintk ("filename = %*s\n" , len, (char *)p); - p +=XDR_QUADLEN (len); -if (p +1 > end) + xlen =XDR_QUADLEN (len); +if (end - p < xlen) goto short_pkt; +dprintk ("filename = %*s\n" , len, (char *)p); + p += xlen; len =ntohl (*p++);/* bitmap length */ - p += len; -if (p +1 > end) +if (end - p < len) goto short_pkt; + p += len; attrlen =XDR_QUADLEN (ntohl (*p++)); - p += attrlen;/* attributes */ -if (p +2 > end) +if (end - p < attrlen +1 ) goto short_pkt; + p += attrlen;/* attributes */ entry = p; }if (!nr && (entry[0 ] !=0 || entry[1 ] ==0 ))
net/sunrpc/clnt.c +11 -0
@@ -1181,6 +1181,17 @@ call_verify(struct rpc_task *task)u32 *p = iov->iov_base, n;int error = -EACCES ; +if ((task->tk_rqstp->rq_rcv_buf.len &3 ) !=0 ) { +/* RFC-1014 says that the representation of XDR data must be a +* multiple of four bytes +* - if it isn't pointer subtraction in the NFS client may give +* undefined results +*/ +printk (KERN_WARNING +"call_verify: XDR representation not a multiple of" +" 4 bytes: 0x%x\n" , task->tk_rqstp->rq_rcv_buf.len); + goto out_eio; + }if ((len -=3 ) <0 ) goto out_overflow; p +=1 ;/* skip XID */
Quoted 2 lines · David Howells
Check the bounds of length specifiers more thoroughly in the XDR decoding of NFS4 readdir reply data.
Hmm... Your patch fails to check for buffer overflows on the read of the bitmap/attribute length, and on the end-of-record markers. The attached slightly revamped patch corrects those oversights.
Cheers,
Trond
Check the bounds of length specifiers more thoroughly in the XDR decoding of NFS4 readdir reply data.
Currently, if the server returns a bitmap or attr length that causes the current decode point pointer to wrap, this could go undetected (consider a small "negative" length on a 32-bit machine).
Also add a check into the main XDR decode handler to make sure that the amount of data is a multiple of four bytes (as specified by RFC-1014). This makes sure that we can do u32* pointer subtraction in the NFS client without risking an undefined result (the result is undefined if the pointers are not correctly aligned with respect to one another).
---
fs/nfs/nfs4xdr.c | 21 +++++++++++---------- net/sunrpc/clnt.c | 11 +++++++++++ 2 files changed, 22 insertions(+), 10 deletions(-)
fs/nfs/nfs4xdr.c +11 -10
@@ -3355,7 +3355,7 @@ static int decode_readdir(struct xdr_strstruct kvec *iov = rcvbuf->head;unsigned int nr, pglen = rcvbuf->page_len;uint32_t *end, *entry, *p, *kaddr; -uint32_t len, attrlen; +uint32_t len, attrlen, xlen;int hdrlen, recvd, status; status =decode_op_hdr (xdr,OP_READDIR );
@@ -3377,10 +3377,10 @@ static int decode_readdir(struct xdr_strBUG_ON (pglen + readdir->pgbase >PAGE_CACHE_SIZE ); kaddr = p = (uint32_t *)kmap_atomic (page,KM_USER0 ); - end = (uint32_t *) ((char *)p + pglen + readdir->pgbase); + end = p + ((pglen + readdir->pgbase) >>2 ); entry = p;for (nr =0 ; *p++; nr++) { -if (p +3 > end) +if (end - p <3 ) goto short_pkt;dprintk ("cookie = %Lu, " , *((unsigned long long *)p)); p +=2 ;/* cookie */
@@ -3389,18 +3389,19 @@ static int decode_readdir(struct xdr_strprintk (KERN_WARNING "NFS: giant filename in readdir (len 0x%x)\n" , len); goto err_unmap; } -dprintk ("filename = %*s\n" , len, (char *)p); - p +=XDR_QUADLEN (len); -if (p +1 > end) + xlen =XDR_QUADLEN (len); +if (end - p < xlen +1 ) goto short_pkt; +dprintk ("filename = %*s\n" , len, (char *)p); + p += xlen; len =ntohl (*p++);/* bitmap length */ - p += len; -if (p +1 > end) +if (end - p < len +1 ) goto short_pkt; + p += len; attrlen =XDR_QUADLEN (ntohl (*p++)); - p += attrlen;/* attributes */ -if (p +2 > end) +if (end - p < attrlen +2 ) goto short_pkt; + p += attrlen;/* attributes */ entry = p; }if (!nr && (entry[0 ] !=0 || entry[1 ] ==0 ))
net/sunrpc/clnt.c +11 -0
@@ -1229,6 +1229,17 @@ call_verify(struct rpc_task *task)u32 *p = iov->iov_base, n;int error = -EACCES ; +if ((task->tk_rqstp->rq_rcv_buf.len &3 ) !=0 ) { +/* RFC-1014 says that the representation of XDR data must be a +* multiple of four bytes +* - if it isn't pointer subtraction in the NFS client may give +* undefined results +*/ +printk (KERN_WARNING +"call_verify: XDR representation not a multiple of" +" 4 bytes: 0x%x\n" , task->tk_rqstp->rq_rcv_buf.len); + goto out_eio; + }if ((len -=3 ) <0 ) goto out_overflow; p +=1 ;/* skip XID */
So, what you've done is:
-+ if (end - p < xlen)
++ if (end - p < xlen + 1)
goto short_pkt;
+ dprintk("filename = %*s\n", len, (char *)p);
+ p += xlen;
len = ntohl(*p++); /* bitmap length */
- p += len;
- if (p + 1 > end)
-+ if (end - p < len)
++ if (end - p < len + 1)
goto short_pkt;
+ p += len;
attrlen = XDR_QUADLEN(ntohl(*p++));
- p += attrlen; /* attributes */
- if (p + 2 > end)
-+ if (end - p < attrlen + 1)
++ if (end - p < attrlen + 2)
But is this equivalent:
-+ if (end - p < xlen)
++ if (end - p <= xlen)
goto short_pkt;
+ dprintk("filename = %*s\n", len, (char *)p);
+ p += xlen;
len = ntohl(*p++); /* bitmap length */
- p += len;
- if (p + 1 > end)
-+ if (end - p < len)
++ if (end - p <= len)
goto short_pkt;
+ p += len;
attrlen = XDR_QUADLEN(ntohl(*p++));
- p += attrlen; /* attributes */
- if (p + 2 > end)
-+ if (end - p < attrlen + 1)
++ if (end - p <= attrlen + 1)
Do you think?
David
Quoted 31 lines · David Howells
So, what you've done is:
-+ if (end - p < xlen)
++ if (end - p < xlen + 1)
gotoshort_pkt;
+dprintk("filename = %*s\n", len, (char *)p);
+ p += xlen;
len =ntohl(*p++); /* bitmap length */
- p += len;
- if (p + 1 > end)
-+ if (end - p < len)
++ if (end - p < len + 1)
gotoshort_pkt;
+ p += len;
attrlen = XDR_QUADLEN(ntohl(*p++));
- p += attrlen; /* attributes */
- if (p + 2 > end)
-+ if (end - p < attrlen + 1)
++ if (end - p < attrlen + 2)But is this equivalent:
-+ if (end - p < xlen)
++ if (end - p <= xlen)
gotoshort_pkt;
+dprintk("filename = %*s\n", len, (char *)p);
+ p += xlen;
len =ntohl(*p++); /* bitmap length */
- p += len;
- if (p + 1 > end)
-+ if (end - p < len)
Quoted 8 lines
++ if (end - p <= len)
gotoshort_pkt;
+ p += len;
attrlen = XDR_QUADLEN(ntohl(*p++));
- p += attrlen; /* attributes */
- if (p + 2 > end)
-+ if (end - p < attrlen + 1)
++ if (end - p <= attrlen + 1)
^^^^^^^^^^^^^^
Quoted 2 lines
Do you think?
No. I find that mixture of < and <= is much less easy to read. Besides, the compiler should be able to optimise that for me.
Cheers,
Trond
Quoted 2 lines · Trond Myklebust
No. I find that mixture of < and <= is much less easy to read. Besides, the compiler should be able to optimise that for me.
So you don't think they're mathematically equivalent?
David
Quoted 6 lines · David Howells
Quoted 2 lines · Trond Myklebust
No. I find that mixture of < and <= is much less easy to read. Besides, the compiler should be able to optimise that for me.
So you don't think they're mathematically equivalent?
The fact that they are mathematically equivalent does not make them equivalently easy to read.
As long as the compiler is able to optimise it, we should be quite free to choose one or the other style of code.
I therefore chose the style which explicitly lists the number of 32-bit words we want to scan.
Trond