Skip to content

Commit

Permalink
ida: Free correct IDA bitmap
Browse files Browse the repository at this point in the history
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 <[email protected]>
Signed-off-by: Matthew Wilcox <[email protected]>
  • Loading branch information
Matthew Wilcox committed Mar 7, 2017
1 parent 3f1b6f9 commit 4ecd954
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 5 deletions.
4 changes: 2 additions & 2 deletions lib/radix-tree.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
34 changes: 31 additions & 3 deletions tools/testing/radix-tree/idr-test.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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);
}
}
Expand Down Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions tools/testing/radix-tree/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions tools/testing/radix-tree/test.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit 4ecd954

Please sign in to comment.