[PATCH] Improve READDIR/READDIRPLUS sanity checking..

Trond Myklebust (trond.myklebust@fys.uio.no)
Tue, 20 Aug 2002 18:24:02 +0200


- 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.

Cheers,
Trond

diff -u --recursive --new-file linux-2.5.31-fix_read/fs/nfs/nfs2xdr.c linux-2.5.31-fix_readdir/fs/nfs/nfs2xdr.c
--- linux-2.5.31-fix_read/fs/nfs/nfs2xdr.c Tue Aug 20 17:10:08 2002
+++ linux-2.5.31-fix_readdir/fs/nfs/nfs2xdr.c Tue Aug 20 17:10:52 2002
@@ -393,7 +393,7 @@
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 @@
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 @@
}
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;
diff -u --recursive --new-file linux-2.5.31-fix_read/fs/nfs/nfs3xdr.c linux-2.5.31-fix_readdir/fs/nfs/nfs3xdr.c
--- linux-2.5.31-fix_read/fs/nfs/nfs3xdr.c Tue Aug 20 17:10:08 2002
+++ linux-2.5.31-fix_readdir/fs/nfs/nfs3xdr.c Tue Aug 20 17:10:52 2002
@@ -504,7 +504,7 @@
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 @@
}

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 @@

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;
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/