MOKB-05-11-2006

Bug details
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
(...)