-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
radix-tree tests: add iteration test
There are four cases I can see where we could end up with a NULL 'slot' in radix_tree_next_slot(). This unit test exercises all four of them, making sure that if in the future we have an unsafe path through radix_tree_next_slot(), we'll catch it. Here are details on the four cases: 1) radix_tree_iter_retry() via a non-tagged iteration like radix_tree_for_each_slot(). In this case we currently aren't seeing a bug because radix_tree_iter_retry() sets iter->next_index = iter->index; which means that in in the else case in radix_tree_next_slot(), 'count' is zero, so we skip over the while() loop and effectively just return NULL without ever dereferencing 'slot'. 2) radix_tree_iter_retry() via tagged iteration like radix_tree_for_each_tagged(). This case was giving us NULL pointer dereferences in testing, and was fixed with this commit: commit 3cb9185 ("radix-tree: fix radix_tree_iter_retry() for tagged iterators.") This fix doesn't explicitly check for 'slot' being NULL, though, it works around the NULL pointer dereference by instead zeroing iter->tags in radix_tree_iter_retry(), which makes us bail out of the if() case in radix_tree_next_slot() before we dereference 'slot'. 3) radix_tree_iter_next() via via a non-tagged iteration like radix_tree_for_each_slot(). This currently happens in shmem_tag_pins() and shmem_partial_swap_usage(). As with non-tagged iteration, 'count' in the else case of radix_tree_next_slot() is zero, so we skip over the while() loop and effectively just return NULL without ever dereferencing 'slot'. 4) radix_tree_iter_next() via tagged iteration like radix_tree_for_each_tagged(). This happens in shmem_wait_for_pins(). radix_tree_iter_next() zeros out iter->tags, so we end up exiting radix_tree_next_slot() here: if (flags & RADIX_TREE_ITER_TAGGED) { void *canon = slot; iter->tags >>= 1; if (unlikely(!iter->tags)) return NULL; Link: http://lkml.kernel.org/r/20160815194237.25967-3-ross.zwisler@linux.intel.com Signed-off-by: Ross Zwisler <ross.zwisler@linux.intel.com> Cc: Konstantin Khlebnikov <koct9i@gmail.com> Cc: Andrey Ryabinin <aryabinin@virtuozzo.com> Cc: Dmitry Vyukov <dvyukov@google.com> Cc: Shuah Khan <shuahkh@osg.samsung.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
- Loading branch information
Ross Zwisler
authored and
Linus Torvalds
committed
Oct 11, 2016
1 parent
915045f
commit eec4852
Showing
4 changed files
with
184 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
/* | ||
* iteration_check.c: test races having to do with radix tree iteration | ||
* Copyright (c) 2016 Intel Corporation | ||
* Author: Ross Zwisler <ross.zwisler@linux.intel.com> | ||
* | ||
* This program is free software; you can redistribute it and/or modify it | ||
* under the terms and conditions of the GNU General Public License, | ||
* version 2, as published by the Free Software Foundation. | ||
* | ||
* This program is distributed in the hope it will be useful, but WITHOUT | ||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
* more details. | ||
*/ | ||
#include <linux/radix-tree.h> | ||
#include <pthread.h> | ||
#include "test.h" | ||
|
||
#define NUM_THREADS 4 | ||
#define TAG 0 | ||
static pthread_mutex_t tree_lock = PTHREAD_MUTEX_INITIALIZER; | ||
static pthread_t threads[NUM_THREADS]; | ||
RADIX_TREE(tree, GFP_KERNEL); | ||
bool test_complete; | ||
|
||
/* relentlessly fill the tree with tagged entries */ | ||
static void *add_entries_fn(void *arg) | ||
{ | ||
int pgoff; | ||
|
||
while (!test_complete) { | ||
for (pgoff = 0; pgoff < 100; pgoff++) { | ||
pthread_mutex_lock(&tree_lock); | ||
if (item_insert(&tree, pgoff) == 0) | ||
item_tag_set(&tree, pgoff, TAG); | ||
pthread_mutex_unlock(&tree_lock); | ||
} | ||
} | ||
|
||
return NULL; | ||
} | ||
|
||
/* | ||
* Iterate over the tagged entries, doing a radix_tree_iter_retry() as we find | ||
* things that have been removed and randomly resetting our iteration to the | ||
* next chunk with radix_tree_iter_next(). Both radix_tree_iter_retry() and | ||
* radix_tree_iter_next() cause radix_tree_next_slot() to be called with a | ||
* NULL 'slot' variable. | ||
*/ | ||
static void *tagged_iteration_fn(void *arg) | ||
{ | ||
struct radix_tree_iter iter; | ||
void **slot; | ||
|
||
while (!test_complete) { | ||
rcu_read_lock(); | ||
radix_tree_for_each_tagged(slot, &tree, &iter, 0, TAG) { | ||
void *entry; | ||
int i; | ||
|
||
/* busy wait to let removals happen */ | ||
for (i = 0; i < 1000000; i++) | ||
; | ||
|
||
entry = radix_tree_deref_slot(slot); | ||
if (unlikely(!entry)) | ||
continue; | ||
|
||
if (radix_tree_deref_retry(entry)) { | ||
slot = radix_tree_iter_retry(&iter); | ||
continue; | ||
} | ||
|
||
if (rand() % 50 == 0) | ||
slot = radix_tree_iter_next(&iter); | ||
} | ||
rcu_read_unlock(); | ||
} | ||
|
||
return NULL; | ||
} | ||
|
||
/* | ||
* Iterate over the entries, doing a radix_tree_iter_retry() as we find things | ||
* that have been removed and randomly resetting our iteration to the next | ||
* chunk with radix_tree_iter_next(). Both radix_tree_iter_retry() and | ||
* radix_tree_iter_next() cause radix_tree_next_slot() to be called with a | ||
* NULL 'slot' variable. | ||
*/ | ||
static void *untagged_iteration_fn(void *arg) | ||
{ | ||
struct radix_tree_iter iter; | ||
void **slot; | ||
|
||
while (!test_complete) { | ||
rcu_read_lock(); | ||
radix_tree_for_each_slot(slot, &tree, &iter, 0) { | ||
void *entry; | ||
int i; | ||
|
||
/* busy wait to let removals happen */ | ||
for (i = 0; i < 1000000; i++) | ||
; | ||
|
||
entry = radix_tree_deref_slot(slot); | ||
if (unlikely(!entry)) | ||
continue; | ||
|
||
if (radix_tree_deref_retry(entry)) { | ||
slot = radix_tree_iter_retry(&iter); | ||
continue; | ||
} | ||
|
||
if (rand() % 50 == 0) | ||
slot = radix_tree_iter_next(&iter); | ||
} | ||
rcu_read_unlock(); | ||
} | ||
|
||
return NULL; | ||
} | ||
|
||
/* | ||
* Randomly remove entries to help induce radix_tree_iter_retry() calls in the | ||
* two iteration functions. | ||
*/ | ||
static void *remove_entries_fn(void *arg) | ||
{ | ||
while (!test_complete) { | ||
int pgoff; | ||
|
||
pgoff = rand() % 100; | ||
|
||
pthread_mutex_lock(&tree_lock); | ||
item_delete(&tree, pgoff); | ||
pthread_mutex_unlock(&tree_lock); | ||
} | ||
|
||
return NULL; | ||
} | ||
|
||
/* This is a unit test for a bug found by the syzkaller tester */ | ||
void iteration_test(void) | ||
{ | ||
int i; | ||
|
||
printf("Running iteration tests for 10 seconds\n"); | ||
|
||
srand(time(0)); | ||
test_complete = false; | ||
|
||
if (pthread_create(&threads[0], NULL, tagged_iteration_fn, NULL)) { | ||
perror("pthread_create"); | ||
exit(1); | ||
} | ||
if (pthread_create(&threads[1], NULL, untagged_iteration_fn, NULL)) { | ||
perror("pthread_create"); | ||
exit(1); | ||
} | ||
if (pthread_create(&threads[2], NULL, add_entries_fn, NULL)) { | ||
perror("pthread_create"); | ||
exit(1); | ||
} | ||
if (pthread_create(&threads[3], NULL, remove_entries_fn, NULL)) { | ||
perror("pthread_create"); | ||
exit(1); | ||
} | ||
|
||
sleep(10); | ||
test_complete = true; | ||
|
||
for (i = 0; i < NUM_THREADS; i++) { | ||
if (pthread_join(threads[i], NULL)) { | ||
perror("pthread_join"); | ||
exit(1); | ||
} | ||
} | ||
|
||
item_kill_tree(&tree); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters