KEYS: Fix multiple key add into associative array
authorDavid Howells <dhowells@redhat.com>
Mon, 2 Dec 2013 11:24:18 +0000 (11:24 +0000)
committerDavid Howells <dhowells@redhat.com>
Mon, 2 Dec 2013 11:24:18 +0000 (11:24 +0000)
If sufficient keys (or keyrings) are added into a keyring such that a node in
the associative array's tree overflows (each node has a capacity N, currently
16) and such that all N+1 keys have the same index key segment for that level
of the tree (the level'th nibble of the index key), then assoc_array_insert()
calls ops->diff_objects() to indicate at which bit position the two index keys
vary.

However, __key_link_begin() passes a NULL object to assoc_array_insert() with
the intention of supplying the correct pointer later before we commit the
change.  This means that keyring_diff_objects() is given a NULL pointer as one
of its arguments which it does not expect.  This results in an oops like the
attached.

With the previous patch to fix the keyring hash function, this can be forced
much more easily by creating a keyring and only adding keyrings to it.  Add any
other sort of key and a different insertion path is taken - all 16+1 objects
must want to cluster in the same node slot.

This can be tested by:

r=`keyctl newring sandbox @s`
for ((i=0; i<=16; i++)); do keyctl newring ring$i $r; done

This should work fine, but oopses when the 17th keyring is added.

Since ops->diff_objects() is always called with the first pointer pointing to
the object to be inserted (ie. the NULL pointer), we can fix the problem by
changing the to-be-inserted object pointer to point to the index key passed
into assoc_array_insert() instead.

Whilst we're at it, we also switch the arguments so that they are the same as
for ->compare_object().

BUG: unable to handle kernel NULL pointer dereference at 0000000000000088
IP: [<ffffffff81191ee4>] hash_key_type_and_desc+0x18/0xb0
...
RIP: 0010:[<ffffffff81191ee4>] hash_key_type_and_desc+0x18/0xb0
...
Call Trace:
 [<ffffffff81191f9d>] keyring_diff_objects+0x21/0xd2
 [<ffffffff811f09ef>] assoc_array_insert+0x3b6/0x908
 [<ffffffff811929a7>] __key_link_begin+0x78/0xe5
 [<ffffffff81191a2e>] key_create_or_update+0x17d/0x36a
 [<ffffffff81192e0a>] SyS_add_key+0x123/0x183
 [<ffffffff81400ddb>] tracesys+0xdd/0xe2

Signed-off-by: David Howells <dhowells@redhat.com>
Tested-by: Stephen Gallagher <sgallagh@redhat.com>
Documentation/assoc_array.txt
include/linux/assoc_array.h
lib/assoc_array.c
security/keys/keyring.c

index f4faec0f66e400dc18c176eb7c567dd2aa18f761..2f2c6cdd73c0c24ab29dcd3f68034f99f17c3125 100644 (file)
@@ -164,10 +164,10 @@ This points to a number of methods, all of which need to be provided:
 
  (4) Diff the index keys of two objects.
 
-       int (*diff_objects)(const void *a, const void *b);
+       int (*diff_objects)(const void *object, const void *index_key);
 
-     Return the bit position at which the index keys of two objects differ or
-     -1 if they are the same.
+     Return the bit position at which the index key of the specified object
+     differs from the given index key or -1 if they are the same.
 
 
  (5) Free an object.
index 9a193b84238a30c97814fcee82148739a5a82b30..a89df3be16863302dcedde84da6f0dbc278fde8c 100644 (file)
@@ -41,10 +41,10 @@ struct assoc_array_ops {
        /* Is this the object we're looking for? */
        bool (*compare_object)(const void *object, const void *index_key);
 
-       /* How different are two objects, to a bit position in their keys? (or
-        * -1 if they're the same)
+       /* How different is an object from an index key, to a bit position in
+        * their keys? (or -1 if they're the same)
         */
-       int (*diff_objects)(const void *a, const void *b);
+       int (*diff_objects)(const void *object, const void *index_key);
 
        /* Method to free an object. */
        void (*free_object)(void *object);
index 17edeaf1918019b43c53dffd6e620298dcb710a2..1b6a44f1ec3e3b10f3b02d19f8ab0ce59453f6d5 100644 (file)
@@ -759,8 +759,8 @@ all_leaves_cluster_together:
        pr_devel("all leaves cluster together\n");
        diff = INT_MAX;
        for (i = 0; i < ASSOC_ARRAY_FAN_OUT; i++) {
-               int x = ops->diff_objects(assoc_array_ptr_to_leaf(edit->leaf),
-                                         assoc_array_ptr_to_leaf(node->slots[i]));
+               int x = ops->diff_objects(assoc_array_ptr_to_leaf(node->slots[i]),
+                                         index_key);
                if (x < diff) {
                        BUG_ON(x < 0);
                        diff = x;
index 0adbc77a59b97efd78b714c311df8df3d5c0626a..3dd8445cd489605802861cfc6363224d2ec91461 100644 (file)
@@ -279,12 +279,11 @@ static bool keyring_compare_object(const void *object, const void *data)
  * Compare the index keys of a pair of objects and determine the bit position
  * at which they differ - if they differ.
  */
-static int keyring_diff_objects(const void *_a, const void *_b)
+static int keyring_diff_objects(const void *object, const void *data)
 {
-       const struct key *key_a = keyring_ptr_to_key(_a);
-       const struct key *key_b = keyring_ptr_to_key(_b);
+       const struct key *key_a = keyring_ptr_to_key(object);
        const struct keyring_index_key *a = &key_a->index_key;
-       const struct keyring_index_key *b = &key_b->index_key;
+       const struct keyring_index_key *b = data;
        unsigned long seg_a, seg_b;
        int level, i;