| Title: | Linux 2.6.x ISO9660 __find_get_block_slow() denial of service |
| Description: | The ISO9660 filesystem handling code of the Linux 2.6.x kernel fails to properly handle corrupted data structures, leading to an exploitable denial of service condition. This particular vulnerability seems to be caused by a race condition and a signedness issue. When performing a read operation on a corrupted ISO9660 fs stream, the isofs_get_blocks() function will enter an infinite loop when __find_get_block_slow() callback from sb_getblk() fails ("due to various races between file io on the block device and getblk"). |
| Author/Contributor: | |
| References: | |
| Proof of concept or exploit: |
The following filesystem image can be used to reproduce the bug:
MOKB-05-11-2006.iso.bz2 Use a loopback device to mount it: bunzip2 MOKB-05-11-2006.iso.bz2 && mount -t iso9660 -o loop MOKB-05-11-2006.iso /media/test && ls /media/test
|
| Debugging information: |
The bug has been found using the Linux version of fsfuzzer on a Fedora Core 6 installation, with up to date packages as of 05-11-2006. A read operation (ex. via file listing) is necessary to trigger the bug. The architecture used to conduct the tests is IA32/x86, SMP enabled. [root@fedora ~]# uname -a Linux fedora 2.6.18-1.2798.fc6 #1 SMP Mon Oct 16 14:37:32 EDT 2006 i686 i686 i386 GNU/Linux Related debugging information and source code:
----------------------- fs/isofs/inode.c (2.6.18)
919 /*
920 * Get a set of blocks; filling in buffer_heads if already allocated
921 * or getblk() if they are not. Returns the number of blocks inserted
922 * (0 == error.)
923 */
924 int isofs_get_blocks(struct inode *inode, sector_t iblock_s,
925 struct buffer_head **bh, unsigned long nblocks)
926 {
(...)
935 lock_kernel(); <------
(...)
945 offset = 0;
946 firstext = ei->i_first_extent;
947 sect_size = ei->i_section_size >> ISOFS_BUFFER_BITS(inode);
948 nextblk = ei->i_next_section_block;
949 nextoff = ei->i_next_section_offset;
950 section = 0;
951
952 while ( nblocks ) {
(...)
993 if ( *bh ) {
994 map_bh(*bh, inode->i_sb, firstext + b_off - offset);
995 } else {
996 *bh = sb_getblk(inode->i_sb, firstext+b_off-offset); <--------- (!)
997 if ( !*bh )
998 goto abort;
999 }
1000 bh++; /* Next buffer head */
1001 b_off++; /* Next buffer offset */
1002 nblocks--;
1003 rv++;
1004 }
1005
1006 abort:
1007 unlock_kernel();
1008 return rv;
1009 }
----------------------- fs/isofs/inode.c (2.6.18)
----------------------- include/linux/buffer_head.h (2.6.18)
265 static inline struct buffer_head *
266 sb_getblk(struct super_block *sb, sector_t block)
267 {
268 return __getblk(sb->s_bdev, block, sb->s_blocksize);
269 }
----------------------- include/linux/buffer_head.h (2.6.18)
----------------------- fs/buffer.c (2.6.18)
1444 /*
1445 * __getblk will locate (and, if necessary, create) the buffer_head
1446 * which corresponds to the passed block_device, block and size. The
1447 * returned buffer has its reference count incremented.
1448 *
1449 * __getblk() cannot fail - it just keeps trying. If you pass it an
1450 * illegal block number, __getblk() will happily return a buffer_head
1451 * which represents the non-existent block. Very weird.
1452 *
1453 * __getblk() will lock up the machine if grow_dev_page's try_to_free_buffers()
1454 * attempt is failing. FIXME, perhaps?
1455 */
1456 struct buffer_head *
1457 __getblk(struct block_device *bdev, sector_t block, int size)
1458 {
1459 struct buffer_head *bh = __find_get_block(bdev, block, size); <---------- (!)
1460
1461 might_sleep();
1462 if (bh == NULL)
1463 bh = __getblk_slow(bdev, block, size);
1464 return bh;
1465 }
----------------------- fs/buffer.c (2.6.18)
----------------------- fs/buffer.c (2.6.18)
1423 /*
1424 * Perform a pagecache lookup for the matching buffer. If it's there, refresh
1425 * it in the LRU and mark it as accessed. If it is not present then return
1426 * NULL
1427 */
1428 struct buffer_head *
1429 __find_get_block(struct block_device *bdev, sector_t block, int size)
1430 {
1431 struct buffer_head *bh = lookup_bh_lru(bdev, block, size);
1432
1433 if (bh == NULL) {
1434 bh = __find_get_block_slow(bdev, block); <---------- (!)
1435 if (bh)
1436 bh_lru_install(bh);
1437 }
1438 if (bh)
1439 touch_buffer(bh);
1440 return bh;
1441 }
1442 EXPORT_SYMBOL(__find_get_block);
----------------------- fs/buffer.c (2.6.18)
----------------------- fs/buffer.c (2.6.18)
386 __find_get_block_slow(struct block_device *bdev, sector_t block)
387 {
388 struct inode *bd_inode = bdev->bd_inode;
389 struct address_space *bd_mapping = bd_inode->i_mapping;
390 struct buffer_head *ret = NULL;
391 pgoff_t index;
392 struct buffer_head *bh;
393 struct buffer_head *head;
394 struct page *page;
395 int all_mapped = 1;
396
397 index = block >> (PAGE_CACHE_SHIFT - bd_inode->i_blkbits);
398 page = find_get_page(bd_mapping, index);
399 if (!page)
400 goto out;
401
402 spin_lock(&bd_mapping->private_lock);
403 if (!page_has_buffers(page))
404 goto out_unlock;
405 head = page_buffers(page);
406 bh = head;
407 do {
408 if (bh->b_blocknr == block) {
409 ret = bh;
410 get_bh(bh);
411 goto out_unlock;
412 }
413 if (!buffer_mapped(bh))
414 all_mapped = 0; <------
415 bh = bh->b_this_page;
416 } while (bh != head);
417
418 /* we might be here because some of the buffers on this page are
419 * not mapped. This is due to various races between <------\
420 * file io on the block device and getblk. It gets dealt with <-------> 'elsewhere'
421 * elsewhere, don't buffer_error if we had some unmapped buffers <------/
422 */
423 if (all_mapped) {
424 printk("__find_get_block_slow() failed. "
425 "block=%llu, b_blocknr=%llu\n",
426 (unsigned long long)block,
427 (unsigned long long)bh->b_blocknr);
428 printk("b_state=0x%08lx, b_size=%zu\n",
429 bh->b_state, bh->b_size);
430 printk("device blocksize: %d\n", 1 << bd_inode->i_blkbits);
431 }
432 out_unlock:
433 spin_unlock(&bd_mapping->private_lock);
434 page_cache_release(page);
435 out:
436 return ret;
437 }
----------------------- fs/buffer.c (2.6.18)
kernel msg buffer:
__find_get_block_slow() failed. block=18446744073457893405, b_blocknr=4043309084
b_state=0x00000020, b_size=2048
device blocksize: 2048
__find_get_block_slow() failed. block=18446744073457893405, b_blocknr=4043309084
b_state=0x00000020, b_size=2048
device blocksize: 2048
__find_get_block_slow() failed. block=18446744073457893405, b_blocknr=4043309084
b_state=0x00000020, b_size=2048
device blocksize: 2048
__find_get_block_slow() failed. block=18446744073457893405, b_blocknr=4043309084
b_state=0x00000020, b_size=2048
device blocksize: 2048
__find_get_block_slow() failed. block=18446744073457893405, b_blocknr=4043309084
b_state=0x00000020, b_size=2048
device blocksize: 2048
__find_get_block_slow() failed. block=18446744073457893405, b_blocknr=4043309084
b_state=0x00000020, b_size=2048
device blocksize: 2048
__find_get_block_slow() failed. block=18446744073457893405, b_blocknr=4043309084
b_state=0x00000020, b_size=2048
device blocksize: 2048
__find_get_block_slow() failed. block=18446744073457893405, b_blocknr=4043309084
b_state=0x00000020, b_size=2048
device blocksize: 2048
__find_get_block_slow() failed. block=18446744073457893405, b_blocknr=4043309084
b_state=0x00000020, b_size=2048
device blocksize: 2048
__find_get_block_slow() failed. block=18446744073457893405, b_blocknr=4043309084
b_state=0x00000020, b_size=2048
device blocksize: 2048
__find_get_block_slow() failed. block=18446744073457893405, b_blocknr=4043309084
b_state=0x00000020, b_size=2048
device blocksize: 2048
__find_get_block_slow() failed. block=18446744073457893405, b_blocknr=4043309084
b_state=0x00000020, b_size=2048
device blocksize: 2048
__find_get_block_slow() failed. block=18446744073457893405, b_blocknr=4043309084
b_state=0x00000020, b_size=2048
device blocksize: 2048
__find_get_block_slow() failed. block=18446744073457893405, b_blocknr=4043309084
b_state=0x00000020, b_size=2048
device blocksize: 2048
__find_get_block_slow() failed. block=18446744073457893405, b_blocknr=4043309084
b_state=0x00000020, b_size=2048
device blocksize: 2048
__find_get_block_slow() failed. block=18446744073457893405, b_blocknr=4043309084
b_state=0x00000020, b_size=2048
device blocksize: 2048
__find_get_block_slow() failed. block=18446744073457893405, b_blocknr=4043309084
b_state=0x00000020, b_size=2048
device blocksize: 2048
__find_get_block_slow() failed. block=18446744073457893405, b_blocknr=4043309084
b_state=0x00000020, b_size=2048
device blocksize: 2048
__find_get_block_slow() failed. block=18446744073457893405, b_blocknr=4043309084
b_state=0x00000020, b_size=2048
device blocksize: 2048
__find_get_block_slow() failed. block=18446744073457893405, b_blocknr=4043309084
b_state=0x00000020, b_size=2048
device blocksize: 2048
__find_get_block_slow() failed. block=18446744073457893405, b_blocknr=4043309084
b_state=0x00000020, b_size=2048
device blocksize: 2048
__find_get_block_slow() failed. block=18446744073457893405, b_blocknr=4043309084
b_state=0x00000020, b_size=2048
device blocksize: 2048
__find_get_block_slow() failed. block=18446744073457893405, b_blocknr=4043309084
b_state=0x00000020, b_size=2048
device blocksize: 2048
__find_get_block_slow() failed. block=18446744073457893405, b_blocknr=4043309084
b_state=0x00000020, b_size=2048
device blocksize: 2048
__find_get_block_slow() failed. block=18446744073457893405, b_blocknr=4043309084
b_state=0x00000020, b_size=2048
device blocksize: 2048
(...)
|