Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

KEYS: Fix multiple key add into associative array

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>

+11 -12
+3 -3
Documentation/assoc_array.txt
··· 164 164 165 165 (4) Diff the index keys of two objects. 166 166 167 - int (*diff_objects)(const void *a, const void *b); 167 + int (*diff_objects)(const void *object, const void *index_key); 168 168 169 - Return the bit position at which the index keys of two objects differ or 170 - -1 if they are the same. 169 + Return the bit position at which the index key of the specified object 170 + differs from the given index key or -1 if they are the same. 171 171 172 172 173 173 (5) Free an object.
+3 -3
include/linux/assoc_array.h
··· 41 41 /* Is this the object we're looking for? */ 42 42 bool (*compare_object)(const void *object, const void *index_key); 43 43 44 - /* How different are two objects, to a bit position in their keys? (or 45 - * -1 if they're the same) 44 + /* How different is an object from an index key, to a bit position in 45 + * their keys? (or -1 if they're the same) 46 46 */ 47 - int (*diff_objects)(const void *a, const void *b); 47 + int (*diff_objects)(const void *object, const void *index_key); 48 48 49 49 /* Method to free an object. */ 50 50 void (*free_object)(void *object);
+2 -2
lib/assoc_array.c
··· 759 759 pr_devel("all leaves cluster together\n"); 760 760 diff = INT_MAX; 761 761 for (i = 0; i < ASSOC_ARRAY_FAN_OUT; i++) { 762 - int x = ops->diff_objects(assoc_array_ptr_to_leaf(edit->leaf), 763 - assoc_array_ptr_to_leaf(node->slots[i])); 762 + int x = ops->diff_objects(assoc_array_ptr_to_leaf(node->slots[i]), 763 + index_key); 764 764 if (x < diff) { 765 765 BUG_ON(x < 0); 766 766 diff = x;
+3 -4
security/keys/keyring.c
··· 279 279 * Compare the index keys of a pair of objects and determine the bit position 280 280 * at which they differ - if they differ. 281 281 */ 282 - static int keyring_diff_objects(const void *_a, const void *_b) 282 + static int keyring_diff_objects(const void *object, const void *data) 283 283 { 284 - const struct key *key_a = keyring_ptr_to_key(_a); 285 - const struct key *key_b = keyring_ptr_to_key(_b); 284 + const struct key *key_a = keyring_ptr_to_key(object); 286 285 const struct keyring_index_key *a = &key_a->index_key; 287 - const struct keyring_index_key *b = &key_b->index_key; 286 + const struct keyring_index_key *b = data; 288 287 unsigned long seg_a, seg_b; 289 288 int level, i; 290 289