aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorRoss Zwisler <ross.zwisler@linux.intel.com>2016-10-11 16:51:21 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2016-10-11 18:06:30 -0400
commiteec4852543e4e6edbb6cab512fd1edc70c1f7a18 (patch)
tree502fe54936f238c54a4ade1e20ecd27919ed95fb /tools
parent915045fe15a5fc376f263d594aee4fca4fba5323 (diff)
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 3cb9185c6730 ("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>
Diffstat (limited to 'tools')
-rw-r--r--tools/testing/radix-tree/Makefile3
-rw-r--r--tools/testing/radix-tree/iteration_check.c180
-rw-r--r--tools/testing/radix-tree/main.c1
-rw-r--r--tools/testing/radix-tree/test.h1
4 files changed, 184 insertions, 1 deletions
diff --git a/tools/testing/radix-tree/Makefile b/tools/testing/radix-tree/Makefile
index 9d0919ed52a4..f2e07f2fd4b4 100644
--- a/tools/testing/radix-tree/Makefile
+++ b/tools/testing/radix-tree/Makefile
@@ -3,7 +3,8 @@ CFLAGS += -I. -g -O2 -Wall -D_LGPL_SOURCE
3LDFLAGS += -lpthread -lurcu 3LDFLAGS += -lpthread -lurcu
4TARGETS = main 4TARGETS = main
5OFILES = main.o radix-tree.o linux.o test.o tag_check.o find_next_bit.o \ 5OFILES = main.o radix-tree.o linux.o test.o tag_check.o find_next_bit.o \
6 regression1.o regression2.o regression3.o multiorder.o 6 regression1.o regression2.o regression3.o multiorder.o \
7 iteration_check.o
7 8
8targets: $(TARGETS) 9targets: $(TARGETS)
9 10
diff --git a/tools/testing/radix-tree/iteration_check.c b/tools/testing/radix-tree/iteration_check.c
new file mode 100644
index 000000000000..9adb8e7415a6
--- /dev/null
+++ b/tools/testing/radix-tree/iteration_check.c
@@ -0,0 +1,180 @@
1/*
2 * iteration_check.c: test races having to do with radix tree iteration
3 * Copyright (c) 2016 Intel Corporation
4 * Author: Ross Zwisler <ross.zwisler@linux.intel.com>
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms and conditions of the GNU General Public License,
8 * version 2, as published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 * more details.
14 */
15#include <linux/radix-tree.h>
16#include <pthread.h>
17#include "test.h"
18
19#define NUM_THREADS 4
20#define TAG 0
21static pthread_mutex_t tree_lock = PTHREAD_MUTEX_INITIALIZER;
22static pthread_t threads[NUM_THREADS];
23RADIX_TREE(tree, GFP_KERNEL);
24bool test_complete;
25
26/* relentlessly fill the tree with tagged entries */
27static void *add_entries_fn(void *arg)
28{
29 int pgoff;
30
31 while (!test_complete) {
32 for (pgoff = 0; pgoff < 100; pgoff++) {
33 pthread_mutex_lock(&tree_lock);
34 if (item_insert(&tree, pgoff) == 0)
35 item_tag_set(&tree, pgoff, TAG);
36 pthread_mutex_unlock(&tree_lock);
37 }
38 }
39
40 return NULL;
41}
42
43/*
44 * Iterate over the tagged entries, doing a radix_tree_iter_retry() as we find
45 * things that have been removed and randomly resetting our iteration to the
46 * next chunk with radix_tree_iter_next(). Both radix_tree_iter_retry() and
47 * radix_tree_iter_next() cause radix_tree_next_slot() to be called with a
48 * NULL 'slot' variable.
49 */
50static void *tagged_iteration_fn(void *arg)
51{
52 struct radix_tree_iter iter;
53 void **slot;
54
55 while (!test_complete) {
56 rcu_read_lock();
57 radix_tree_for_each_tagged(slot, &tree, &iter, 0, TAG) {
58 void *entry;
59 int i;
60
61 /* busy wait to let removals happen */
62 for (i = 0; i < 1000000; i++)
63 ;
64
65 entry = radix_tree_deref_slot(slot);
66 if (unlikely(!entry))
67 continue;
68
69 if (radix_tree_deref_retry(entry)) {
70 slot = radix_tree_iter_retry(&iter);
71 continue;
72 }
73
74 if (rand() % 50 == 0)
75 slot = radix_tree_iter_next(&iter);
76 }
77 rcu_read_unlock();
78 }
79
80 return NULL;
81}
82
83/*
84 * Iterate over the entries, doing a radix_tree_iter_retry() as we find things
85 * that have been removed and randomly resetting our iteration to the next
86 * chunk with radix_tree_iter_next(). Both radix_tree_iter_retry() and
87 * radix_tree_iter_next() cause radix_tree_next_slot() to be called with a
88 * NULL 'slot' variable.
89 */
90static void *untagged_iteration_fn(void *arg)
91{
92 struct radix_tree_iter iter;
93 void **slot;
94
95 while (!test_complete) {
96 rcu_read_lock();
97 radix_tree_for_each_slot(slot, &tree, &iter, 0) {
98 void *entry;
99 int i;
100
101 /* busy wait to let removals happen */
102 for (i = 0; i < 1000000; i++)
103 ;
104
105 entry = radix_tree_deref_slot(slot);
106 if (unlikely(!entry))
107 continue;
108
109 if (radix_tree_deref_retry(entry)) {
110 slot = radix_tree_iter_retry(&iter);
111 continue;
112 }
113
114 if (rand() % 50 == 0)
115 slot = radix_tree_iter_next(&iter);
116 }
117 rcu_read_unlock();
118 }
119
120 return NULL;
121}
122
123/*
124 * Randomly remove entries to help induce radix_tree_iter_retry() calls in the
125 * two iteration functions.
126 */
127static void *remove_entries_fn(void *arg)
128{
129 while (!test_complete) {
130 int pgoff;
131
132 pgoff = rand() % 100;
133
134 pthread_mutex_lock(&tree_lock);
135 item_delete(&tree, pgoff);
136 pthread_mutex_unlock(&tree_lock);
137 }
138
139 return NULL;
140}
141
142/* This is a unit test for a bug found by the syzkaller tester */
143void iteration_test(void)
144{
145 int i;
146
147 printf("Running iteration tests for 10 seconds\n");
148
149 srand(time(0));
150 test_complete = false;
151
152 if (pthread_create(&threads[0], NULL, tagged_iteration_fn, NULL)) {
153 perror("pthread_create");
154 exit(1);
155 }
156 if (pthread_create(&threads[1], NULL, untagged_iteration_fn, NULL)) {
157 perror("pthread_create");
158 exit(1);
159 }
160 if (pthread_create(&threads[2], NULL, add_entries_fn, NULL)) {
161 perror("pthread_create");
162 exit(1);
163 }
164 if (pthread_create(&threads[3], NULL, remove_entries_fn, NULL)) {
165 perror("pthread_create");
166 exit(1);
167 }
168
169 sleep(10);
170 test_complete = true;
171
172 for (i = 0; i < NUM_THREADS; i++) {
173 if (pthread_join(threads[i], NULL)) {
174 perror("pthread_join");
175 exit(1);
176 }
177 }
178
179 item_kill_tree(&tree);
180}
diff --git a/tools/testing/radix-tree/main.c b/tools/testing/radix-tree/main.c
index b7619ff3b552..daa9010693e8 100644
--- a/tools/testing/radix-tree/main.c
+++ b/tools/testing/radix-tree/main.c
@@ -332,6 +332,7 @@ int main(int argc, char **argv)
332 regression1_test(); 332 regression1_test();
333 regression2_test(); 333 regression2_test();
334 regression3_test(); 334 regression3_test();
335 iteration_test();
335 single_thread_tests(long_run); 336 single_thread_tests(long_run);
336 337
337 sleep(1); 338 sleep(1);
diff --git a/tools/testing/radix-tree/test.h b/tools/testing/radix-tree/test.h
index e85131369723..217fb2403f09 100644
--- a/tools/testing/radix-tree/test.h
+++ b/tools/testing/radix-tree/test.h
@@ -27,6 +27,7 @@ void item_kill_tree(struct radix_tree_root *root);
27 27
28void tag_check(void); 28void tag_check(void);
29void multiorder_checks(void); 29void multiorder_checks(void);
30void iteration_test(void);
30 31
31struct item * 32struct item *
32item_tag_set(struct radix_tree_root *root, unsigned long index, int tag); 33item_tag_set(struct radix_tree_root *root, unsigned long index, int tag);