xorl %eax, %eax

CVE-2009-2407: Linux kernel eCryptFS Heap Buffer Overflow

leave a comment »

This bug was reported by Ramon de Carvalho Valle of RiseSecurity and it affects Linux kernel prior to 2.6.30.4 release as we can read from its ChangeLog. Even though Ramon de Carvalho Valle gave an excellent description in his advisory I’m going to write about it too. The vulnerable routine can be found at fs/ecryptfs/keystore.c file. Here is that function from 2.6.30 release of the Linux kernel.

/**
 * parse_tag_3_packet
 * @crypt_stat: The cryptographic context to modify based on packet
 *              contents.
 * @data: The raw bytes of the packet.
 * @auth_tok_list: eCryptfs parses packets into authentication tokens;
 *                 a new authentication token will be placed at the end
 *                 of this list for this packet.
 * @new_auth_tok: Pointer to a pointer to memory that this function
 *                allocates; sets the memory address of the pointer to
 *                NULL on error. This object is added to the
 *                auth_tok_list.
 * @packet_size: This function writes the size of the parsed packet
 *               into this memory location; zero on error.
 * @max_packet_size: maximum number of bytes to parse
 *
 * Returns zero on success; non-zero on error.
 */
static int
parse_tag_3_packet(struct ecryptfs_crypt_stat *crypt_stat,
                   unsigned char *data, struct list_head *auth_tok_list,
                   struct ecryptfs_auth_tok **new_auth_tok,
                   size_t *packet_size, size_t max_packet_size)
{
        size_t body_size;
        struct ecryptfs_auth_tok_list_item *auth_tok_list_item;
        size_t length_size;
        int rc = 0;
  ...
        /* Released: wipe_auth_tok_list called in ecryptfs_parse_packet_set or
         * at end of function upon failure */
        auth_tok_list_item =
            kmem_cache_zalloc(ecryptfs_auth_tok_list_item_cache, GFP_KERNEL);
        if (!auth_tok_list_item) {
  ...
        (*new_auth_tok) = &auth_tok_list_item->auth_tok;
        rc = ecryptfs_parse_packet_length(&data[(*packet_size)], &body_size,
                                          &length_size);
        if (rc) {
                printk(KERN_WARNING "Error parsing packet length; rc = [%d]\n",
                       rc);
                goto out_free;
        }
  ...
        (*new_auth_tok)->session_key.encrypted_key_size =
                (body_size - (ECRYPTFS_SALT_SIZE + 5));
        if (unlikely(data[(*packet_size)++] != 0x04)) {
                printk(KERN_WARNING "Unknown version number [%d]\n",
                       data[(*packet_size) - 1]);
                rc = -EINVAL;
                goto out_free;
        }
        ecryptfs_cipher_code_to_string(crypt_stat->cipher,
                                       (u16)data[(*packet_size)]);
  ...
        /* TODO: finish the hash mapping */
        switch (data[(*packet_size)++]) {
        case 0x01: /* See RFC2440 for these numbers and their mappings */
  ...
                /* Friendly reminder:
                 * (*new_auth_tok)->session_key.encrypted_key_size =
                 *         (body_size - (ECRYPTFS_SALT_SIZE + 5)); */
                memcpy((*new_auth_tok)->session_key.encrypted_key,
                       &data[(*packet_size)],
                       (*new_auth_tok)->session_key.encrypted_key_size);
                (*packet_size) +=
                        (*new_auth_tok)->session_key.encrypted_key_size;
  ...
        return rc;
}

You can read the purpose of that function from the comment above. It is quite detailed. So, it first initializes auth_tok_list_item with kmem_cache_zalloc(). This variable is a pointer to a ecryptfs_auth_tok_list_item structure which is defined at fs/ecryptfs/ecryptfs_kernel.h like this:

/* auth_tok <=> encrypted_session_key mappings */
struct ecryptfs_auth_tok_list_item {
        unsigned char encrypted_session_key[ECRYPTFS_MAX_KEY_BYTES];
        struct list_head list;
        struct ecryptfs_auth_tok auth_tok;
};

The encrypted_session_key[] has size of ECRYPTFS_MAX_KEY_BYTES which is 64 as we can read in the same header file. For completeness, here is also the ecryptfs_auth_tok structure defined at the same header file like this:

/* May be a password or a private key */
struct ecryptfs_auth_tok {
        u16 version; /* 8-bit major and 8-bit minor */
        u16 token_type;
#define ECRYPTFS_ENCRYPT_ONLY 0x00000001
        u32 flags;
        struct ecryptfs_session_key session_key;
        u8 reserved[32];
        union {
                struct ecryptfs_password password;
                struct ecryptfs_private_key private_key;
        } token;
} __attribute__ ((packed));

And the important for this vulnerability ecryptfs_session_key structure is simply…

/**
 * For convenience, we may need to pass around the encrypted session
 * key between kernel and userspace because the authentication token
 * may not be extractable.  For example, the TPM may not release the
 * private key, instead requiring the encrypted data and returning the
 * decrypted data.
 */
struct ecryptfs_session_key {
#define ECRYPTFS_USERSPACE_SHOULD_TRY_TO_DECRYPT 0x00000001
#define ECRYPTFS_USERSPACE_SHOULD_TRY_TO_ENCRYPT 0x00000002
#define ECRYPTFS_CONTAINS_DECRYPTED_KEY 0x00000004
#define ECRYPTFS_CONTAINS_ENCRYPTED_KEY 0x00000008
        u32 flags;
        u32 encrypted_key_size;
        u32 decrypted_key_size;
        u8 encrypted_key[ECRYPTFS_MAX_ENCRYPTED_KEY_BYTES];
        u8 decrypted_key[ECRYPTFS_MAX_KEY_BYTES];
};

Now back to the vulnerable code… new_auth_tok pointer is initialized with the session auth_tok and then ecryptfs_parse_packet_length() is called to retrieve the size from data[(*packet_size)], write the decoded size to body_size and finally, write the number of bytes of the encoded length to variable length_size.
Now, the next snippet of that function handles the encryption key size. It sets (*new_auth_tok)->session_key.encrypted_key_size to the decoded size minus (ECRYPTFS_SALT_SIZE + 5) and checks the version number at data[(*packet_size)++] which should be 4. If the check passes, it will immediately invoke ecryptfs_cipher_code_to_string() from fs/ecryptfs/crypto.c to convert data[(*packet_size)] to cipher string name and store it to crypt_stat->cipher.
The final snippet of the parsing routine enters a switch statement and if data[(*packet_size)++] is set to 0x01 (for selecting MD5 hash according to RFC2440) it will invoke memcpy() in order to copy (*new_auth_tok)->session_key.encrypted_key_size bytes from data[(*packet_size)] to (*new_auth_tok)->session_key.encrypted_key and the equivalent (*packet_size) is updated. However, as it has been demonstrated earlier, encrypted_key has a static size of 64 and here no checks are being performed. This means that if the calculated size from the user controlled encryption key is larger than ECRYPTFS_MAX_KEY_BYTES it will result to heap memory corruption at (*new_auth_tok)->session_key.encrypted_key.
To fix this, the following patch was applied to this routine:

        (*new_auth_tok)->session_key.encrypted_key_size =
                (body_size - (ECRYPTFS_SALT_SIZE + 5));
+       if ((*new_auth_tok)->session_key.encrypted_key_size
+           > ECRYPTFS_MAX_ENCRYPTED_KEY_BYTES) {
+               printk(KERN_WARNING "Tag 3 packet contains key larger "
+                      "than ECRYPTFS_MAX_ENCRYPTED_KEY_BYTES\n");
+               rc = -EINVAL;
+               goto out_free;
+       }
        if (unlikely(data[(*packet_size)++] != 0x04)) {

This performs bound check on (*new_auth_tok)->session_key.encrypted_key_size in order to be less than ECRYPTFS_MAX_ENCRYPTED_KEY_BYTES. A user can construct an encryption key larger than 64 bytes but with the appropriate flags to reach this code path in order to trigger this.

Written by xorl

August 6, 2009 at 10:45

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