xorl %eax, %eax

CVE-2009-3624: Linux kernel get_instantiation_keyring() Integer Underflow

leave a comment »

This is a really interesting vulnerability, discovered and reported by David Howells of Red Hat and quickly spotted and characterized as a security related bug by Brad Spengler of grsecurity. A few days later, Eugene Teo requested a CVE for that bug on oss-security mailing list.
The bug was introduced in 2.6.29-rc1 and here is the buggy function from 2.6.31 release of the Linux kernel (located in security/keys/keyctl.c):

/*
 * get the destination keyring for instantiation
 */
static long get_instantiation_keyring(key_serial_t ringid,
                                      struct request_key_auth *rka,
                                      struct key **_dest_keyring)
{
        key_ref_t dkref;

        *_dest_keyring = NULL;

        /* just return a NULL pointer if we weren't asked to make a link */
        if (ringid == 0)
                return 0;

        /* if a specific keyring is nominated by ID, then use that */
        if (ringid > 0) {
                dkref = lookup_user_key(ringid, 1, 0, KEY_WRITE);
                if (IS_ERR(dkref))
                        return PTR_ERR(dkref);
                *_dest_keyring = key_ref_to_ptr(dkref);
                return 0;
        }

        if (ringid == KEY_SPEC_REQKEY_AUTH_KEY)
                return -EINVAL;

        /* otherwise specify the destination keyring recorded in the
         * authorisation key (any KEY_SPEC_*_KEYRING) */
        if (ringid >= KEY_SPEC_REQUESTOR_KEYRING) {
                *_dest_keyring = rka->dest_keyring;
                return 0;
        }

        return -ENOKEY;
}

The aim of this code is quite clear, just read the comments. Now, the actual code. Data type ‘key_serial_t’ is a common 32-bit long signed integer that is used to store the key handle serial number. If this is 0 the function will return a NULL pointer.
On the other hand, if this ID is greater than zero, it will use lookup_user_key() (from security/keys/process_keys.c) that will return a key from user-space, relevant to the requested key ID. After a successful return of lookup_user_key(), it will initialize ‘*_dest_keyring’ with key_ref_to_ptr() inline function which resides at include/linux/key.h like this:

static inline struct key *key_ref_to_ptr(const key_ref_t key_ref)
{
        return (struct key *) ((unsigned long) key_ref & ~1UL);
}

Otherwise, if the requested key ID is equal to ‘KEY_SPEC_REQKEY_AUTH_KEY’ (which is -7 and it is used to describe an ID for assumed ‘request_key’ authorization key) it will return with ‘-EINVAL’ (Invalid Argument) or, if it equal or greater than ‘KEY_SPEC_REQUESTOR_KEYRING’ (which is -8 and it describes a key ID for request_key() destination keyring), it will initialize ‘*_dest_keyring’ to the passed request key authentication pointer, and specifically to ‘rka->dest_keyring’ member of that structure.
Now, David Howells found that there are two cases that perform different operations on the reference counter of the requested key…

1]– Case One

Routines keyctl_instantiate_key() and keyctl_negate_key() call the above function to attach the keyring in the end of instantiation of the constructed key and they could give a specific keyring that will be later used to make a link. In this case, lookup_user_key() would increment the reference counter like this:

/*****************************************************************************/
/*
 * lookup a key given a key ID from userspace with a given permissions mask
 * - don't create special keyrings unless so requested
 * - partially constructed keys aren't found unless requested
 */
key_ref_t lookup_user_key(key_serial_t id, int create, int partial,
                          key_perm_t perm)
{
        struct request_key_auth *rka;
        const struct cred *cred;
        struct key *key;
        key_ref_t key_ref, skey_ref;
        int ret;
      ...
                key = cred->user->session_keyring;
                atomic_inc(&key->usage);
                key_ref = make_key_ref(key, 1);
      ...
} /* end lookup_user_key() */

Structure ‘key’ contains an atomic_t variable named ‘usage’ which is used to store the number of references. As you all know from include/linux/types.h, atomic_t is just:

typedef struct {
        volatile int counter;
} atomic_t;

And as you can read, it increments it using atomic_inc().

2]– Case Two

Routines keyctl_instantiate_key() and keyctl_negate_key() call the above function to attach the keyring in the end of instantiation of the constructed key and they could ask to find the keyring passed to request_key(). Here is what will happen in this case…

/*
 * request a key
 * - search the process's keyrings
 * - check the list of keys being created or updated
 * - call out to userspace for a key if supplementary info was provided
 * - waits uninterruptible for creation to complete
 */
struct key *request_key(struct key_type *type,
                        const char *description,
                        const char *callout_info)
{
        struct key *key;
        size_t callout_len = 0;
        int ret;
    ...
        key = request_key_and_link(type, description, callout_info, callout_len,
                                   NULL, NULL, KEY_ALLOC_IN_QUOTA);
    ...
        return key;
}

This function will simply find the ‘key’ and return it without incrementing the reference counter at ‘key->usage’.

During the destruction of a key key_put() will be invoked:

/*****************************************************************************/
/*
 * dispose of a reference to a key
 * - when all the references are gone, we schedule the cleanup task to come and
 *   pull it out of the tree in definite process context
 */
void key_put(struct key *key)
{
        if (key) {
                key_check(key);

                if (atomic_dec_and_test(&key->usage))
                        schedule_work(&key_cleanup_task);
        }

} /* end key_put() */

EXPORT_SYMBOL(key_put);

The key_check() is used only if kernel is compiled with KEY_DEBUGGING so there is no need to discuss it. Here, what it does is decrement the reference counter and schedule the key_cleanup_task routine. Because of the incosistent increment/decrement in the reference counter invalid object destruction might happen.
The bug was fixed by using the following patch:

        if (ringid >= KEY_SPEC_REQUESTOR_KEYRING) {
-               *_dest_keyring = rka->dest_keyring;
+               *_dest_keyring = key_get(rka->dest_keyring);
                return 0;

Where key_get() is a very simple inline routine that just increments the reference counter like this:

static inline struct key *key_get(struct key *key)
{
        if (key)
                atomic_inc(&key->usage);
        return key;
}

and it can be found at include/linux/key.h.

Written by xorl

October 26, 2009 at 21:43

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