diff options
author | Andrew Morton <akpm@osdl.org> | 2007-01-09 03:23:15 +0100 |
---|---|---|
committer | Adrian Bunk <bunk@stusta.de> | 2007-01-09 03:23:15 +0100 |
commit | d9be428145481c9c23e02e25a49205fc9def5d36 (patch) | |
tree | 75a9bce9c079f5a2d37277462fe31b51a0fd94ed | |
parent | 09d9056ce65466da2a4634c62fcfecfa70fc9605 (diff) |
grow_buffers() infinite loop fix (CVE-2006-5757/CVE-2006-6060)
If grow_buffers() is for some reason passed a block number which wants to li
outside the maximum-addressable pagecache range (PAGE_SIZE * 4G bytes) then
will accidentally truncate `index' and will then instnatiate a page at the
wrong pagecache offset. This causes __getblk_slow() to go into an infinite
loop.
This can happen with corrupted disks, or with software errors elsewhere.
Detect that, and handle it.
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Adrian Bunk <bunk@stusta.de>
-rw-r--r-- | fs/buffer.c | 21 |
1 files changed, 19 insertions, 2 deletions
diff --git a/fs/buffer.c b/fs/buffer.c index a9b39940200..8a17ebb9742 100644 --- a/fs/buffer.c +++ b/fs/buffer.c @@ -1179,8 +1179,21 @@ grow_buffers(struct block_device *bdev, sector_t block, int size) } while ((size << sizebits) < PAGE_SIZE); index = block >> sizebits; - block = index << sizebits; + /* + * Check for a block which wants to lie outside our maximum possible + * pagecache index. (this comparison is done using sector_t types). + */ + if (unlikely(index != block >> sizebits)) { + char b[BDEVNAME_SIZE]; + + printk(KERN_ERR "%s: requested out-of-range block %llu for " + "device %s\n", + __FUNCTION__, (unsigned long long)block, + bdevname(bdev, b)); + return -EIO; + } + block = index << sizebits; /* Create a page with the proper size buffers.. */ page = grow_dev_page(bdev, block, index, size); if (!page) @@ -1207,12 +1220,16 @@ __getblk_slow(struct block_device *bdev, sector_t block, int size) for (;;) { struct buffer_head * bh; + int ret; bh = __find_get_block(bdev, block, size); if (bh) return bh; - if (!grow_buffers(bdev, block, size)) + ret = grow_buffers(bdev, block, size); + if (ret < 0) + return NULL; + if (ret == 0) free_more_memory(); } } |