mirror of
https://github.com/torvalds/linux.git
synced 2024-11-25 13:41:51 +00:00
ida: Free correct IDA bitmap
There's a relatively rare race where we look at the per-cpu preallocated IDA bitmap, see it's NULL, allocate a new one, and atomically update it. If the kmalloc() happened to sleep and we were rescheduled to a different CPU, or an interrupt came in at the exact right time, another task might have successfully allocated a bitmap and already deposited it. I forgot what the semantics of cmpxchg() were and ended up freeing the wrong bitmap leading to KASAN reporting a use-after-free. Dmitry found the bug with syzkaller & wrote the patch. I wrote the test case that will reproduce the bug without his patch being applied. Reported-by: Dmitry Vyukov <dvyukov@google.com> Signed-off-by: Matthew Wilcox <mawilcox@microsoft.com>
This commit is contained in:
parent
3f1b6f9d49
commit
4ecd9542db
@ -2129,8 +2129,8 @@ int ida_pre_get(struct ida *ida, gfp_t gfp)
|
||||
struct ida_bitmap *bitmap = kmalloc(sizeof(*bitmap), gfp);
|
||||
if (!bitmap)
|
||||
return 0;
|
||||
bitmap = this_cpu_cmpxchg(ida_bitmap, NULL, bitmap);
|
||||
kfree(bitmap);
|
||||
if (this_cpu_cmpxchg(ida_bitmap, NULL, bitmap))
|
||||
kfree(bitmap);
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
@ -363,7 +363,7 @@ void ida_check_random(void)
|
||||
{
|
||||
DEFINE_IDA(ida);
|
||||
DECLARE_BITMAP(bitmap, 2048);
|
||||
int id;
|
||||
int id, err;
|
||||
unsigned int i;
|
||||
time_t s = time(NULL);
|
||||
|
||||
@ -377,8 +377,11 @@ void ida_check_random(void)
|
||||
ida_remove(&ida, bit);
|
||||
} else {
|
||||
__set_bit(bit, bitmap);
|
||||
ida_pre_get(&ida, GFP_KERNEL);
|
||||
assert(!ida_get_new_above(&ida, bit, &id));
|
||||
do {
|
||||
ida_pre_get(&ida, GFP_KERNEL);
|
||||
err = ida_get_new_above(&ida, bit, &id);
|
||||
} while (err == -ENOMEM);
|
||||
assert(!err);
|
||||
assert(id == bit);
|
||||
}
|
||||
}
|
||||
@ -476,11 +479,36 @@ void ida_checks(void)
|
||||
radix_tree_cpu_dead(1);
|
||||
}
|
||||
|
||||
static void *ida_random_fn(void *arg)
|
||||
{
|
||||
rcu_register_thread();
|
||||
ida_check_random();
|
||||
rcu_unregister_thread();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void ida_thread_tests(void)
|
||||
{
|
||||
pthread_t threads[10];
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(threads); i++)
|
||||
if (pthread_create(&threads[i], NULL, ida_random_fn, NULL)) {
|
||||
perror("creating ida thread");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
while (i--)
|
||||
pthread_join(threads[i], NULL);
|
||||
}
|
||||
|
||||
int __weak main(void)
|
||||
{
|
||||
radix_tree_init();
|
||||
idr_checks();
|
||||
ida_checks();
|
||||
ida_thread_tests();
|
||||
radix_tree_cpu_dead(1);
|
||||
rcu_barrier();
|
||||
if (nr_allocated)
|
||||
printf("nr_allocated = %d\n", nr_allocated);
|
||||
|
@ -368,6 +368,7 @@ int main(int argc, char **argv)
|
||||
iteration_test(0, 10 + 90 * long_run);
|
||||
iteration_test(7, 10 + 90 * long_run);
|
||||
single_thread_tests(long_run);
|
||||
ida_thread_tests();
|
||||
|
||||
/* Free any remaining preallocated nodes */
|
||||
radix_tree_cpu_dead(0);
|
||||
|
@ -36,6 +36,7 @@ void iteration_test(unsigned order, unsigned duration);
|
||||
void benchmark(void);
|
||||
void idr_checks(void);
|
||||
void ida_checks(void);
|
||||
void ida_thread_tests(void);
|
||||
|
||||
struct item *
|
||||
item_tag_set(struct radix_tree_root *root, unsigned long index, int tag);
|
||||
|
Loading…
Reference in New Issue
Block a user