xorl %eax, %eax

CVE-2011-1577: Linux kernel Corrupted EFI GUID Invalid Memory Access

leave a comment »

This vulnerability was reported by Timo Warns at the linux-mm-commits mailing list on 12 April 2011. In the EFI (Extensible Firmware Interface) source code of Linux kernel, there is a function responsible for evaluating the GUID in the partition table(s). At fs/partitions/efi.c we can find the routine that performs those tests.

/**
 * is_gpt_valid() - tests one GPT header and PTEs for validity
 * @state
 * @lba is the logical block address of the GPT header to test
 * @gpt is a GPT header ptr, filled on return.
 * @ptes is a PTEs ptr, filled on return.
 *
 * Description: returns 1 if valid,  0 on error.
 * If valid, returns pointers to newly allocated GPT header and PTEs.
 */
static int is_gpt_valid(struct parsed_partitions *state, u64 lba,
                        gpt_header **gpt, gpt_entry **ptes)
{
        u32 crc, origcrc;
        u64 lastlba;
   ...
        /* Check the GUID Partition Table CRC */
        origcrc = le32_to_cpu((*gpt)->header_crc32);
        (*gpt)->header_crc32 = 0;
        crc = efi_crc32((const unsigned char *) (*gpt), le32_to_cpu((*gpt)->header_size));
   ...
 fail:
        kfree(*gpt);
        *gpt = NULL;
        return 0;
}

In order to perform the validation, the code reads the ‘(*gpt)->header_crc32’ value, resets it to 0 and finally invokes efi_crc32() to calculate the CRC32 hash. The problem is that there is no check of the user controlled ‘header_size’ value which is defined in fs/partitions/efi.h header file.

typedef struct _gpt_header {
        __le64 signature;
        __le32 revision;
        __le32 header_size;
        __le32 header_crc32;
        __le32 reserved1;
        __le64 my_lba;
        __le64 alternate_lba;
        __le64 first_usable_lba;
        __le64 last_usable_lba;
        efi_guid_t disk_guid;
        __le64 partition_entry_lba;
        __le32 num_partition_entries;
        __le32 sizeof_partition_entry;
        __le32 partition_entry_array_crc32;

        /* The rest of the logical block is reserved by UEFI and must be zero.
         * EFI standard handles this by:
         *
         * uint8_t              reserved2[ BlockSize - 92 ];
         */
} __attribute__ ((packed)) gpt_header;

Because of this, a user could construct a USB flash drive with a huge (since its type is 32-bit unsigned integer) ‘header_size’ value. This value reaches efi_crc32() routine which is shown here.

/**
 * efi_crc32() - EFI version of crc32 function
 * @buf: buffer to calculate crc32 of
 * @len - length of buf
 *
 * Description: Returns EFI-style CRC32 value for @buf
 * 
 * This function uses the little endian Ethernet polynomial
 * but seeds the function with ~0, and xor's with ~0 at the end.
 * Note, the EFI Specification, v1.02, has a reference to
 * Dr. Dobbs Journal, May 1994 (actually it's in May 1992).
 */
static inline u32
efi_crc32(const void *buf, unsigned long len)
{
        return (crc32(~0L, buf, len) ^ ~0L);
}

Which in turn will end up calling crc32_le() because of a definition in include/linux/crc32.h header file.

extern u32  crc32_le(u32 crc, unsigned char const *p, size_t len);
extern u32  crc32_be(u32 crc, unsigned char const *p, size_t len);

#define crc32(seed, data, length)  crc32_le(seed, (unsigned char const *)data, length)

This is a simple routine located at lib/crc32.c

/**
 * crc32_le() - Calculate bitwise little-endian Ethernet AUTODIN II CRC32
 * @crc: seed value for computation.  ~0 for Ethernet, sometimes 0 for
 *      other uses, or the previous crc32 value if computing incrementally.
 * @p: pointer to buffer over which CRC is run
 * @len: length of buffer @p
 */
u32 __pure crc32_le(u32 crc, unsigned char const *p, size_t len);

#if CRC_LE_BITS == 1
/*
 * In fact, the table-based code will work in this case, but it can be
 * simplified by inlining the table in ?: form.
 */

u32 __pure crc32_le(u32 crc, unsigned char const *p, size_t len)
{
        int i;
        while (len--) {
                crc ^= *p++;
                for (i = 0; i < 8; i++)
                        crc = (crc >> 1) ^ ((crc & 1) ? CRCPOLY_LE : 0);
        }
        return crc;
}
#else                           /* Table-based approach */

u32 __pure crc32_le(u32 crc, unsigned char const *p, size_t len)
{
# if CRC_LE_BITS == 8
        const u32      (*tab)[] = crc32table_le;

        crc = __cpu_to_le32(crc);
        crc = crc32_body(crc, p, len, tab);
        return __le32_to_cpu(crc);
# elif CRC_LE_BITS == 4
        while (len--) {
                crc ^= *p++;
                crc = (crc >> 4) ^ crc32table_le[crc & 15];
                crc = (crc >> 4) ^ crc32table_le[crc & 15];
        }
        return crc;
# elif CRC_LE_BITS == 2
        while (len--) {
                crc ^= *p++;
                crc = (crc >> 2) ^ crc32table_le[crc & 3];
                crc = (crc >> 2) ^ crc32table_le[crc & 3];
                crc = (crc >> 2) ^ crc32table_le[crc & 3];
                crc = (crc >> 2) ^ crc32table_le[crc & 3];
        }
        return crc;
# endif
}
#endif

As you can see, regardless of the CRC-32 code path, the ‘len’ value which is derived directly from the user controlled ‘header_size’ will be used as the buffer’s length value. Clearly, this will result in accessing heap memory beyond the buffer’s limits.
The fix to this bug was to add a check in the is_gpt_valid() routine.

 
+	/* Check the GUID Partition Table header size */
+	if (le32_to_cpu((*gpt)->header_size) > bdev_logical_block_size(state->bdev)) {
+		pr_debug("GUID Partition Table Header size is wrong:"
+			 "%u > %u\n",
+             le32_to_cpu((*gpt)->header_size),
+             bdev_logical_block_size(state->bdev));
+		goto fail;
+	}
+
 	/* Check the GUID Partition Table CRC */
 	origcrc = le32_to_cpu((*gpt)->header_crc32);

So, before moving to the CRC calculation, it checks that the ‘header_size’ is not greater than the device’s logical block size. The bdev_logical_block_size() is a wrapper around queue_logical_block_size() which is defined in include/linux/blkdev.h header file.

static inline unsigned short bdev_logical_block_size(struct block_device *bdev)
{
        return queue_logical_block_size(bdev_get_queue(bdev));
}

This is nothing more than a simple return of the ‘limits.logical_block_size’ value for the given device.

static inline unsigned short queue_logical_block_size(struct request_queue *q)
{
        int retval = 512;

        if (q && q->limits.logical_block_size)
                retval = q->limits.logical_block_size;

        return retval;
}

Written by xorl

April 26, 2011 at 22:13

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