Bug 206328 - CVE-2006-5757 Linux kernel Filesystem Mount Dead Loop
Summary: CVE-2006-5757 Linux kernel Filesystem Mount Dead Loop
Keywords:
Status: CLOSED ERRATA
Alias: None
Product: Red Hat Enterprise Linux 4
Classification: Red Hat
Component: kernel
Version: 4.0
Hardware: All
OS: Linux
medium
low
Target Milestone: ---
: ---
Assignee: Eric Sandeen
QA Contact: Brian Brock
URL:
Whiteboard: impact=low,source=lkml,reported=20060...
Depends On:
Blocks:
TreeView+ depends on / blocked
 
Reported: 2006-09-13 18:05 UTC by Marcel Holtmann
Modified: 2007-11-30 22:07 UTC (History)
3 users (show)

Fixed In Version: RHSA-2007-0014
Doc Type: Bug Fix
Doc Text:
Clone Of:
Environment:
Last Closed: 2007-01-30 14:27:34 UTC
Target Upstream Version:
Embargoed:


Attachments (Terms of Use)


Links
System ID Private Priority Status Summary Last Updated
Red Hat Product Errata RHSA-2007:0014 0 normal SHIPPED_LIVE Important: kernel security update 2007-01-30 14:25:00 UTC

Description Marcel Holtmann 2006-09-13 18:05:53 UTC
Reported by ADLab, Venustech info Ltd CHINA:

There is a vulnerability in function __getblk(). When mount a file system image
with malformed block value, Linux kernel will fall in a dead loop. It will lead
to a kernel hang and denying further service.

Function __getblk() is used to seek a corresponding buffer_head of a block in a
specific block device. When processing a block with a block number more than 4G
and not to be mapped to buffer pages (__find_get_block will return NULL),
__getblk_slow will always run and never return.

1478 struct buffer_head *
1479 __getblk(struct block_device *bdev, sector_t block, int size)
1480 {
1481         struct buffer_head *bh = __find_get_block(bdev, block, size);
1482
1483         might_sleep();
1484         if (bh == NULL)
1485                 bh = __getblk_slow(bdev, block, size);
1486         return bh;
1487 }

The endless for loop only has a exit point (at line 1213). It terminate only
when __find_get_block return a non-NULL value. If the block is not mapped to
buffer pages, the first __find_get_block calling will return a NULL bh and the
function grow_buffers() will be called subsequently.

1194 __getblk_slow(struct block_device *bdev, sector_t block, int size)
1195 {
1196         /* Size must be multiple of hard sectorsize */
1197         if (unlikely(size & (bdev_hardsect_size(bdev)-1) ||
1198                         (size < 512 || size > PAGE_SIZE))) {
1199                 printk(KERN_ERR "getblk(): invalid block size %d requested\n",
1200                                         size);
1201                 printk(KERN_ERR "hardsect size: %d\n",
1202                                         bdev_hardsect_size(bdev));
1203
1204                 dump_stack();
1205                 return NULL;
1206         }
1207
1208         for (;;) {
1209                 struct buffer_head * bh;
1210
1211                 bh = __find_get_block(bdev, block, size);	
1212                 if (bh)
1213                         return bh;
1214
1215                 if (!grow_buffers(bdev, block, size))		
1216                         free_more_memory();
1217         }
1218 }

The function grow_buffers() is responsible for construct the relationships among
page, buffer_head and block. On the 32-bit platform, the length of block and
index are 64-bit and 32-bit respectively. After the operations at line 1201 and
1202, the high 32 bits of block will lost. Consequently, when the block number
is beyond 4G, new block number would be differently with the original.

1189 static inline int
1190 grow_buffers(struct block_device *bdev, sector_t block, int size)
1191 {
1192         struct page *page;
1193         pgoff_t index;
1194         int sizebits;
1195
1196         sizebits = -1;
1197         do {
1198                 sizebits++;
1199         } while ((size << sizebits) < PAGE_SIZE);
1200
1201         index = block >> sizebits;
1202         block = index << sizebits;
1203
1204         /* Create a page with the proper size buffers.. */
1205         page = grow_dev_page(bdev, block, index, size);
1206         if (!page)
1207                 return 0;
1208         unlock_page(page);
1209         page_cache_release(page);
1210         return 1;
1211 }

Follow the call sequence (grow_dev_page ==> init_page_buffers ==>
init_page_buffers). In init_page_buffers(), a new buffer_head will be
initialized, it's block number (bh->b_blocknr) corresponding to mapped block is
assigned with the new block number.

static void
init_page_buffers(struct page *page, struct block_device *bdev,
                        sector_t block, int size)
{
	...
	...

        do {
                if (!buffer_mapped(bh)) {		
                        init_buffer(bh, NULL, NULL);
                        bh->b_bdev = bdev;
                        bh->b_blocknr = block;
                        if (uptodate)
                                set_buffer_uptodate(bh);
                        set_buffer_mapped(bh);
                }
                block++;
                bh = bh->b_this_page;
        } while (bh != head);
}
 
However, __find_get_block seeks buffer head base on the original block number in
__getblk_slow (). Because of wrong block number argument, __find_get_block will
always return NULL. As results, system will fall in a dead loop and consume
resource endlessly.
 
1194 __getblk_slow(struct block_device *bdev, sector_t block, int size)
1195 {
	...
	...
1207
1208         for (;;) {
1209                 struct buffer_head * bh;
1210
1211                 bh = __find_get_block(bdev, block, size);	
1212                 if (bh)
1213                         return bh;
1214
1215                 if (!grow_buffers(bdev, block, size))		
1216                         free_more_memory();
1217         }
1218 }

The vulnerability can be triggered by mount a malformed Reiser filesystem image,
the arguments of __getblk() are:

__getblk(0xcd0ed0c0, 0xffffffffa10020d9, 0x1000)

sys_mount ->
do_mount ->
do_kern_mount ->
get_super_block ->
get_sb_bdev  ->
reiserfs_fill_super ->
reiserfs_read_locked_inode ->
search_by_key ->
__getblk ->
__find_get_block

Comment 8 Eric Sandeen 2006-11-06 22:43:57 UTC
Whoops.  Actually this upstream patch already resolves the issue:
http://git.kernel.org/git/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commitdiff;h=e5657933863f43cc6bb76a54d659303dafaa9e58;hp=e0ab2928cc2202f13f0574d4c6f567f166d307eb

Text of the patch is attached to bug 214288.

Comment 9 Eric Sandeen 2006-11-07 04:33:52 UTC
patch sent to rhkernel-list on 11/6/06

Comment 10 Marcel Holtmann 2006-11-22 20:49:34 UTC
This is the same as bug 214288 and so it has also the same CVE name.

Comment 11 Jason Baron 2006-12-11 22:33:51 UTC
committed in stream U5 build 42.24. A test kernel with this patch is available
from http://people.redhat.com/~jbaron/rhel4/

Comment 12 Jason Baron 2006-12-18 21:43:20 UTC
committed in stream E5 build 42.0.4

Comment 14 Mike Gahagan 2007-01-16 22:37:53 UTC
I was not able to reproduce the problem with
http://projects.info-pull.com/mokb/bug-files/MOKB-05-11-2006.iso.bz2 using the
-42 kernel

however I can confirm that the changes needed to fix this are in the 42.0.6 kernel.



Comment 16 Red Hat Bugzilla 2007-01-30 14:27:34 UTC
An advisory has been issued which should help the problem
described in this bug report. This report is therefore being
closed with a resolution of ERRATA. For more information
on the solution and/or where to find the updated files,
please follow the link below. You may reopen this bug report
if the solution does not work for you.

http://rhn.redhat.com/errata/RHSA-2007-0014.html



Note You need to log in before you can comment on or make changes to this bug.