From: Nick Piggin Date: Tue, 20 Feb 2007 21:58:08 +0000 (-0800) Subject: [PATCH] fs: fix libfs data leak X-Git-Tag: v2.6.21-rc1~22 X-Git-Url: https://err.no/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=955eff5acc8b8cd1c7d4eec0229c35eaabe013db;p=linux-2.6 [PATCH] fs: fix libfs data leak simple_prepare_write leaks uninitialised kernel data. This happens because the it leaves an uninitialised "hole" over the part of the page that the write is expected to go to. This is fine, but it then marks the page uptodate, which means a concurrent read can come in and copy the uninitialised memory into userspace before it written to. Fix it by simply marking it uptodate in simple_commit_write instead, after the hole has been filled in. This could theoretically break an fs that uses simple_prepare_write and not simple_commit_write, and that relies on the incorrect simple_prepare_write behaviour. Luckily, none of those exists in the tree. Signed-off-by: Nick Piggin Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- diff --git a/Documentation/filesystems/vfs.txt b/Documentation/filesystems/vfs.txt index 7737bfd03c..ea271f2d39 100644 --- a/Documentation/filesystems/vfs.txt +++ b/Documentation/filesystems/vfs.txt @@ -617,6 +617,11 @@ struct address_space_operations { In this case the prepare_write will be retried one the lock is regained. + Note: the page _must not_ be marked uptodate in this function + (or anywhere else) unless it actually is uptodate right now. As + soon as a page is marked uptodate, it is possible for a concurrent + read(2) to copy it to userspace. + commit_write: If prepare_write succeeds, new data will be copied into the page and then commit_write will be called. It will typically update the size of the file (if appropriate) and diff --git a/fs/libfs.c b/fs/libfs.c index 7d487047db..cf79196535 100644 --- a/fs/libfs.c +++ b/fs/libfs.c @@ -335,17 +335,18 @@ int simple_prepare_write(struct file *file, struct page *page, flush_dcache_page(page); kunmap_atomic(kaddr, KM_USER0); } - SetPageUptodate(page); } return 0; } int simple_commit_write(struct file *file, struct page *page, - unsigned offset, unsigned to) + unsigned from, unsigned to) { struct inode *inode = page->mapping->host; loff_t pos = ((loff_t)page->index << PAGE_CACHE_SHIFT) + to; + if (!PageUptodate(page)) + SetPageUptodate(page); /* * No need to use i_size_read() here, the i_size * cannot change under us because we hold the i_mutex.