Commit 29ceefc7 authored by Trond Myklebust's avatar Trond Myklebust

[PATCH] Improve READDIR/READDIRPLUS sanity checking..

 - Use req->rq_received to determine the message length instead of
   assuming that it goes to the end of the page.

 - If the server returned an illegal record so that we cannot make
   progress by retrying the request on a fresh page, truncate the
   entire listing and return a syslog error.
parent d9a4ea27
......@@ -393,7 +393,7 @@ nfs_xdr_readdirres(struct rpc_rqst *req, u32 *p, void *dummy)
struct xdr_buf *rcvbuf = &req->rq_rcv_buf;
struct iovec *iov = rcvbuf->head;
struct page **page;
int hdrlen;
int hdrlen, recvd;
int status, nr;
unsigned int len, pglen;
u32 *end, *entry;
......@@ -402,17 +402,24 @@ nfs_xdr_readdirres(struct rpc_rqst *req, u32 *p, void *dummy)
return -nfs_stat_to_errno(status);
hdrlen = (u8 *) p - (u8 *) iov->iov_base;
if (iov->iov_len > hdrlen) {
if (iov->iov_len < hdrlen) {
printk(KERN_WARNING "NFS: READDIR reply header overflowed:"
"length %d > %d\n", hdrlen, iov->iov_len);
return -errno_NFSERR_IO;
} else if (iov->iov_len != hdrlen) {
dprintk("NFS: READDIR header is short. iovec will be shifted.\n");
xdr_shift_buf(rcvbuf, iov->iov_len - hdrlen);
}
pglen = rcvbuf->page_len;
recvd = req->rq_received - hdrlen;
if (pglen > recvd)
pglen = recvd;
page = rcvbuf->pages;
p = kmap(*page);
end = (u32 *)((char *)p + pglen);
entry = p;
for (nr = 0; *p++; nr++) {
entry = p - 1;
if (p + 2 > end)
goto short_pkt;
p++; /* fileid */
......@@ -425,14 +432,21 @@ nfs_xdr_readdirres(struct rpc_rqst *req, u32 *p, void *dummy)
}
if (p + 2 > end)
goto short_pkt;
entry = p;
}
if (!nr)
goto short_pkt;
out:
kunmap(*page);
return nr;
short_pkt:
printk(KERN_NOTICE "NFS: short packet in readdir reply!\n");
entry[0] = entry[1] = 0;
kunmap(*page);
return nr;
/* truncate listing ? */
if (!nr) {
printk(KERN_NOTICE "NFS: readdir reply truncated!\n");
entry[1] = 1;
}
goto out;
err_unmap:
kunmap(*page);
return -errno_NFSERR_IO;
......
......@@ -504,7 +504,7 @@ nfs3_xdr_readdirres(struct rpc_rqst *req, u32 *p, struct nfs3_readdirres *res)
struct xdr_buf *rcvbuf = &req->rq_rcv_buf;
struct iovec *iov = rcvbuf->head;
struct page **page;
int hdrlen;
int hdrlen, recvd;
int status, nr;
unsigned int len, pglen;
u32 *entry, *end;
......@@ -523,17 +523,24 @@ nfs3_xdr_readdirres(struct rpc_rqst *req, u32 *p, struct nfs3_readdirres *res)
}
hdrlen = (u8 *) p - (u8 *) iov->iov_base;
if (iov->iov_len > hdrlen) {
if (iov->iov_len < hdrlen) {
printk(KERN_WARNING "NFS: READDIR reply header overflowed:"
"length %d > %d\n", hdrlen, iov->iov_len);
return -errno_NFSERR_IO;
} else if (iov->iov_len != hdrlen) {
dprintk("NFS: READDIR header is short. iovec will be shifted.\n");
xdr_shift_buf(rcvbuf, iov->iov_len - hdrlen);
}
pglen = rcvbuf->page_len;
recvd = req->rq_received - hdrlen;
if (pglen > recvd)
pglen = recvd;
page = rcvbuf->pages;
p = kmap(*page);
end = (u32 *)((char *)p + pglen);
entry = p;
for (nr = 0; *p++; nr++) {
entry = p - 1;
if (p + 3 > end)
goto short_pkt;
p += 2; /* inode # */
......@@ -570,15 +577,21 @@ nfs3_xdr_readdirres(struct rpc_rqst *req, u32 *p, struct nfs3_readdirres *res)
if (p + 2 > end)
goto short_pkt;
entry = p;
}
if (!nr)
goto short_pkt;
out:
kunmap(*page);
return nr;
short_pkt:
printk(KERN_NOTICE "NFS: short packet in readdir reply!\n");
/* truncate listing */
entry[0] = entry[1] = 0;
kunmap(*page);
return nr;
/* truncate listing ? */
if (!nr) {
printk(KERN_NOTICE "NFS: readdir reply truncated!\n");
entry[1] = 1;
}
goto out;
err_unmap:
kunmap(*page);
return -errno_NFSERR_IO;
......
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