[PATCH] NFS: Fix a potential panic in O_DIRECT
authorTrond Myklebust <Trond.Myklebust@netapp.com>
Tue, 14 Mar 2006 05:20:46 +0000 (21:20 -0800)
committerLinus Torvalds <torvalds@g5.osdl.org>
Tue, 14 Mar 2006 15:57:17 +0000 (07:57 -0800)
Based on an original patch by Mike O'Connor and Greg Banks of SGI.

Mike states:

A normal user can panic an NFS client and cause a local DoS with
'judicious'(?) use of O_DIRECT.  Any O_DIRECT write to an NFS file where the
user buffer starts with a valid mapped page and contains an unmapped page,
will crash in this way.  I haven't followed the code, but O_DIRECT reads with
similar user buffers will probably also crash albeit in different ways.

Details: when nfs_get_user_pages() calls get_user_pages(), it detects and
correctly handles get_user_pages() returning an error, which happens if the
first page covered by the user buffer's address range is unmapped.  However,
if the first page is mapped but some subsequent page isn't, get_user_pages()
will return a positive number which is less than the number of pages requested
(this behaviour is sort of analagous to a short write() call and appears to be
intentional).  nfs_get_user_pages() doesn't detect this and hands off the
array of pages (whose last few elements are random rubbish from the newly
allocated array memory) to it's caller, whence they go to
nfs_direct_write_seg(), which then totally ignores the nr_pages it's given,
and calculates its own idea of how many pages are in the array from the user
buffer length.  Needless to say, when it comes to transmit those uninitialised
page* pointers, we see a crash in the network stack.

Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
fs/nfs/direct.c

index 04ab2fc..4e9b3a1 100644 (file)
@@ -57,6 +57,7 @@
 #define NFSDBG_FACILITY                NFSDBG_VFS
 #define MAX_DIRECTIO_SIZE      (4096UL << PAGE_SHIFT)
 
+static void nfs_free_user_pages(struct page **pages, int npages, int do_dirty);
 static kmem_cache_t *nfs_direct_cachep;
 
 /*
@@ -107,6 +108,15 @@ nfs_get_user_pages(int rw, unsigned long user_addr, size_t size,
                                        page_count, (rw == READ), 0,
                                        *pages, NULL);
                up_read(&current->mm->mmap_sem);
+               /*
+                * If we got fewer pages than expected from get_user_pages(),
+                * the user buffer runs off the end of a mapping; return EFAULT.
+                */
+               if (result >= 0 && result < page_count) {
+                       nfs_free_user_pages(*pages, result, 0);
+                       *pages = NULL;
+                       result = -EFAULT;
+               }
        }
        return result;
 }