Commit 23fd78d7 authored by David Howells's avatar David Howells

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: default avatarDavid Howells <dhowells@redhat.com>
Tested-by: default avatarStephen Gallagher <sgallagh@redhat.com>
parent d54e58b7
...@@ -164,10 +164,10 @@ This points to a number of methods, all of which need to be provided: ...@@ -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. (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 Return the bit position at which the index key of the specified object
-1 if they are the same. differs from the given index key or -1 if they are the same.
(5) Free an object. (5) Free an object.
......
...@@ -41,10 +41,10 @@ struct assoc_array_ops { ...@@ -41,10 +41,10 @@ struct assoc_array_ops {
/* Is this the object we're looking for? */ /* Is this the object we're looking for? */
bool (*compare_object)(const void *object, const void *index_key); bool (*compare_object)(const void *object, const void *index_key);
/* How different are two objects, to a bit position in their keys? (or /* How different is an object from an index key, to a bit position in
* -1 if they're the same) * 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. */ /* Method to free an object. */
void (*free_object)(void *object); void (*free_object)(void *object);
......
...@@ -759,8 +759,8 @@ static bool assoc_array_insert_into_terminal_node(struct assoc_array_edit *edit, ...@@ -759,8 +759,8 @@ static bool assoc_array_insert_into_terminal_node(struct assoc_array_edit *edit,
pr_devel("all leaves cluster together\n"); pr_devel("all leaves cluster together\n");
diff = INT_MAX; diff = INT_MAX;
for (i = 0; i < ASSOC_ARRAY_FAN_OUT; i++) { for (i = 0; i < ASSOC_ARRAY_FAN_OUT; i++) {
int x = ops->diff_objects(assoc_array_ptr_to_leaf(edit->leaf), int x = ops->diff_objects(assoc_array_ptr_to_leaf(node->slots[i]),
assoc_array_ptr_to_leaf(node->slots[i])); index_key);
if (x < diff) { if (x < diff) {
BUG_ON(x < 0); BUG_ON(x < 0);
diff = x; diff = x;
......
...@@ -279,12 +279,11 @@ static bool keyring_compare_object(const void *object, const void *data) ...@@ -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 * Compare the index keys of a pair of objects and determine the bit position
* at which they differ - if they differ. * 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_a = keyring_ptr_to_key(object);
const struct key *key_b = keyring_ptr_to_key(_b);
const struct keyring_index_key *a = &key_a->index_key; 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; unsigned long seg_a, seg_b;
int level, i; int level, i;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment