23fd78d764
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>
92 lines
3.1 KiB
C
92 lines
3.1 KiB
C
/* Generic associative array implementation.
|
|
*
|
|
* See Documentation/assoc_array.txt for information.
|
|
*
|
|
* Copyright (C) 2013 Red Hat, Inc. All Rights Reserved.
|
|
* Written by David Howells (dhowells@redhat.com)
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public Licence
|
|
* as published by the Free Software Foundation; either version
|
|
* 2 of the Licence, or (at your option) any later version.
|
|
*/
|
|
|
|
#ifndef _LINUX_ASSOC_ARRAY_H
|
|
#define _LINUX_ASSOC_ARRAY_H
|
|
|
|
#ifdef CONFIG_ASSOCIATIVE_ARRAY
|
|
|
|
#include <linux/types.h>
|
|
|
|
#define ASSOC_ARRAY_KEY_CHUNK_SIZE BITS_PER_LONG /* Key data retrieved in chunks of this size */
|
|
|
|
/*
|
|
* Generic associative array.
|
|
*/
|
|
struct assoc_array {
|
|
struct assoc_array_ptr *root; /* The node at the root of the tree */
|
|
unsigned long nr_leaves_on_tree;
|
|
};
|
|
|
|
/*
|
|
* Operations on objects and index keys for use by array manipulation routines.
|
|
*/
|
|
struct assoc_array_ops {
|
|
/* Method to get a chunk of an index key from caller-supplied data */
|
|
unsigned long (*get_key_chunk)(const void *index_key, int level);
|
|
|
|
/* Method to get a piece of an object's index key */
|
|
unsigned long (*get_object_key_chunk)(const void *object, int level);
|
|
|
|
/* Is this the object we're looking for? */
|
|
bool (*compare_object)(const void *object, const void *index_key);
|
|
|
|
/* 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 *object, const void *index_key);
|
|
|
|
/* Method to free an object. */
|
|
void (*free_object)(void *object);
|
|
};
|
|
|
|
/*
|
|
* Access and manipulation functions.
|
|
*/
|
|
struct assoc_array_edit;
|
|
|
|
static inline void assoc_array_init(struct assoc_array *array)
|
|
{
|
|
array->root = NULL;
|
|
array->nr_leaves_on_tree = 0;
|
|
}
|
|
|
|
extern int assoc_array_iterate(const struct assoc_array *array,
|
|
int (*iterator)(const void *object,
|
|
void *iterator_data),
|
|
void *iterator_data);
|
|
extern void *assoc_array_find(const struct assoc_array *array,
|
|
const struct assoc_array_ops *ops,
|
|
const void *index_key);
|
|
extern void assoc_array_destroy(struct assoc_array *array,
|
|
const struct assoc_array_ops *ops);
|
|
extern struct assoc_array_edit *assoc_array_insert(struct assoc_array *array,
|
|
const struct assoc_array_ops *ops,
|
|
const void *index_key,
|
|
void *object);
|
|
extern void assoc_array_insert_set_object(struct assoc_array_edit *edit,
|
|
void *object);
|
|
extern struct assoc_array_edit *assoc_array_delete(struct assoc_array *array,
|
|
const struct assoc_array_ops *ops,
|
|
const void *index_key);
|
|
extern struct assoc_array_edit *assoc_array_clear(struct assoc_array *array,
|
|
const struct assoc_array_ops *ops);
|
|
extern void assoc_array_apply_edit(struct assoc_array_edit *edit);
|
|
extern void assoc_array_cancel_edit(struct assoc_array_edit *edit);
|
|
extern int assoc_array_gc(struct assoc_array *array,
|
|
const struct assoc_array_ops *ops,
|
|
bool (*iterator)(void *object, void *iterator_data),
|
|
void *iterator_data);
|
|
|
|
#endif /* CONFIG_ASSOCIATIVE_ARRAY */
|
|
#endif /* _LINUX_ASSOC_ARRAY_H */
|