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

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>

+35 -5
+2 -2
lib/radix-tree.c
··· 2129 2129 struct ida_bitmap *bitmap = kmalloc(sizeof(*bitmap), gfp); 2130 2130 if (!bitmap) 2131 2131 return 0; 2132 - bitmap = this_cpu_cmpxchg(ida_bitmap, NULL, bitmap); 2133 - kfree(bitmap); 2132 + if (this_cpu_cmpxchg(ida_bitmap, NULL, bitmap)) 2133 + kfree(bitmap); 2134 2134 } 2135 2135 2136 2136 return 1;
+31 -3
tools/testing/radix-tree/idr-test.c
··· 363 363 { 364 364 DEFINE_IDA(ida); 365 365 DECLARE_BITMAP(bitmap, 2048); 366 - int id; 366 + int id, err; 367 367 unsigned int i; 368 368 time_t s = time(NULL); 369 369 ··· 377 377 ida_remove(&ida, bit); 378 378 } else { 379 379 __set_bit(bit, bitmap); 380 - ida_pre_get(&ida, GFP_KERNEL); 381 - assert(!ida_get_new_above(&ida, bit, &id)); 380 + do { 381 + ida_pre_get(&ida, GFP_KERNEL); 382 + err = ida_get_new_above(&ida, bit, &id); 383 + } while (err == -ENOMEM); 384 + assert(!err); 382 385 assert(id == bit); 383 386 } 384 387 } ··· 479 476 radix_tree_cpu_dead(1); 480 477 } 481 478 479 + static void *ida_random_fn(void *arg) 480 + { 481 + rcu_register_thread(); 482 + ida_check_random(); 483 + rcu_unregister_thread(); 484 + return NULL; 485 + } 486 + 487 + void ida_thread_tests(void) 488 + { 489 + pthread_t threads[10]; 490 + int i; 491 + 492 + for (i = 0; i < ARRAY_SIZE(threads); i++) 493 + if (pthread_create(&threads[i], NULL, ida_random_fn, NULL)) { 494 + perror("creating ida thread"); 495 + exit(1); 496 + } 497 + 498 + while (i--) 499 + pthread_join(threads[i], NULL); 500 + } 501 + 482 502 int __weak main(void) 483 503 { 484 504 radix_tree_init(); 485 505 idr_checks(); 486 506 ida_checks(); 507 + ida_thread_tests(); 508 + radix_tree_cpu_dead(1); 487 509 rcu_barrier(); 488 510 if (nr_allocated) 489 511 printf("nr_allocated = %d\n", nr_allocated);
+1
tools/testing/radix-tree/main.c
··· 368 368 iteration_test(0, 10 + 90 * long_run); 369 369 iteration_test(7, 10 + 90 * long_run); 370 370 single_thread_tests(long_run); 371 + ida_thread_tests(); 371 372 372 373 /* Free any remaining preallocated nodes */ 373 374 radix_tree_cpu_dead(0);
+1
tools/testing/radix-tree/test.h
··· 36 36 void benchmark(void); 37 37 void idr_checks(void); 38 38 void ida_checks(void); 39 + void ida_thread_tests(void); 39 40 40 41 struct item * 41 42 item_tag_set(struct radix_tree_root *root, unsigned long index, int tag);