xorl %eax, %eax

Linux kernel LDM Partition Table Heap Overflow

leave a comment »

This is an interesting vulnerability reported by Timo Warns that affects Linux kernel with ‘CONFIG_LDM_PARTITION‘ option enabled. The susceptible code resides in fs/partitions/ldm.c in the function you see below.

/**
 * ldm_frag_add - Add a VBLK fragment to a list
 * @data:   Raw fragment to be added to the list
 * @size:   Size of the raw fragment
 * @frags:  Linked list of VBLK fragments
 *
 * Fragmented VBLKs may not be consecutive in the database, so they are placed
 * in a list so they can be pieced together later.
 *
 * Return:  'true'   Success, the VBLK was added to the list
 *          'false'  Error, a problem occurred
 */
static bool ldm_frag_add (const u8 *data, int size, struct list_head *frags)
{
        struct frag *f;
        struct list_head *item;
        int rec, num, group

        BUG_ON (!data || !frags);

        group = get_unaligned_be32(data + 0x08);
        rec   = get_unaligned_be16(data + 0x0C);
        num   = get_unaligned_be16(data + 0x0E);
        if ((num < 1) || (num > 4)) {
                ldm_error ("A VBLK claims to have %d parts.", num);
                return false;
        }
  ...
        f = kmalloc (sizeof (*f) + size*num, GFP_KERNEL);
        if (!f) {
                ldm_crit ("Out of memory.");
                return false;
        }

        f->group = group;
        f->num   = num;
        f->rec   = rec;
        f->map   = 0xFF << num;
  ...
        f->map |= (1 << rec);

        if (num > 0) {
                data += VBLK_SIZE_HEAD;
                size -= VBLK_SIZE_HEAD;
        }
        memcpy (f->data+rec*(size-VBLK_SIZE_HEAD)+VBLK_SIZE_HEAD, data, size);

        return true;
}

As you can read, integers ‘rec’, ‘group’ and ‘num’ are initialized directly from user controlled data from the partition table. The code checks that ‘num’ is greater than zero but it does not perform any checks on the ‘rec’ value. In addition, ‘size’ is decremented without ensuring that it is larger than ‘VBLK_SIZE_HEAD’. To fix this, a check for ‘size’ was added.

 	BUG_ON (!data || !frags);
 
+	if (size < 2*VBLK_SIZE_HEAD) {
+		ldm_error ("Value of size is to small.");
+		return false;
+	}
+
 	group = get_unaligned_be32(data + 0x08);

And also REC value now depends on the equivalent NUM one.

 	}
+	if (rec >= num) {
+		ldm_error ("REC value (%d) exceeds NUM value (%d)", rec, num);
+		return false;
+	}
 
 	list_for_each (item, frags) {

And at last, the ‘num’ check was removed since it is no longer needed.

 	f->map |= (1 << rec);
 
-	if (num > 0) {
-		data += VBLK_SIZE_HEAD;
-		size -= VBLK_SIZE_HEAD;
-	}
+	data += VBLK_SIZE_HEAD;
+	size -= VBLK_SIZE_HEAD;
+
 	memcpy (f->data+rec*(size-VBLK_SIZE_HEAD)+VBLK_SIZE_HEAD, data, size);

Written by xorl

May 9, 2011 at 20:44

Posted in bugs, linux

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s