aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthew Wilcox (Oracle) <willy@infradead.org>2019-05-14 16:05:45 -0400
committerMatthew Wilcox (Oracle) <willy@infradead.org>2019-06-02 23:00:24 -0400
commit5c089fd0c73411f2170ab795c9ffc16718c7d007 (patch)
tree896b8028b37e5fe8705f113a7a628c3e1da85de4
parent7b785645e8f13e17cbce492708cf6e7039d32e46 (diff)
idr: Fix idr_get_next race with idr_remove
If the entry is deleted from the IDR between the call to radix_tree_iter_find() and rcu_dereference_raw(), idr_get_next() will return NULL, which will end the iteration prematurely. We should instead continue to the next entry in the IDR. This only happens if the iteration is protected by the RCU lock. Most IDR users use a spinlock or semaphore to exclude simultaneous modifications. It was noticed once the PID allocator was converted to use the IDR, as it uses the RCU lock, but there may be other users elsewhere in the kernel. We can't use the normal pattern of calling radix_tree_deref_retry() (which catches both a retry entry in a leaf node and a node entry in the root) as the IDR supports storing entries which are unaligned, which will trigger an infinite loop if they are encountered. Instead, we have to explicitly check whether the entry is a retry entry. Fixes: 0a835c4f090a ("Reimplement IDR and IDA using the radix tree") Reported-by: Brendan Gregg <bgregg@netflix.com> Tested-by: Brendan Gregg <bgregg@netflix.com> Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org>
-rw-r--r--lib/idr.c14
-rw-r--r--tools/testing/radix-tree/idr-test.c46
2 files changed, 58 insertions, 2 deletions
diff --git a/lib/idr.c b/lib/idr.c
index c34e256d2f01..66a374892482 100644
--- a/lib/idr.c
+++ b/lib/idr.c
@@ -228,11 +228,21 @@ void *idr_get_next(struct idr *idr, int *nextid)
228{ 228{
229 struct radix_tree_iter iter; 229 struct radix_tree_iter iter;
230 void __rcu **slot; 230 void __rcu **slot;
231 void *entry = NULL;
231 unsigned long base = idr->idr_base; 232 unsigned long base = idr->idr_base;
232 unsigned long id = *nextid; 233 unsigned long id = *nextid;
233 234
234 id = (id < base) ? 0 : id - base; 235 id = (id < base) ? 0 : id - base;
235 slot = radix_tree_iter_find(&idr->idr_rt, &iter, id); 236 radix_tree_for_each_slot(slot, &idr->idr_rt, &iter, id) {
237 entry = rcu_dereference_raw(*slot);
238 if (!entry)
239 continue;
240 if (!xa_is_internal(entry))
241 break;
242 if (slot != &idr->idr_rt.xa_head && !xa_is_retry(entry))
243 break;
244 slot = radix_tree_iter_retry(&iter);
245 }
236 if (!slot) 246 if (!slot)
237 return NULL; 247 return NULL;
238 id = iter.index + base; 248 id = iter.index + base;
@@ -241,7 +251,7 @@ void *idr_get_next(struct idr *idr, int *nextid)
241 return NULL; 251 return NULL;
242 252
243 *nextid = id; 253 *nextid = id;
244 return rcu_dereference_raw(*slot); 254 return entry;
245} 255}
246EXPORT_SYMBOL(idr_get_next); 256EXPORT_SYMBOL(idr_get_next);
247 257
diff --git a/tools/testing/radix-tree/idr-test.c b/tools/testing/radix-tree/idr-test.c
index 1b63bdb7688f..fe33be4c2475 100644
--- a/tools/testing/radix-tree/idr-test.c
+++ b/tools/testing/radix-tree/idr-test.c
@@ -287,6 +287,51 @@ static void idr_align_test(struct idr *idr)
287 } 287 }
288} 288}
289 289
290DEFINE_IDR(find_idr);
291
292static void *idr_throbber(void *arg)
293{
294 time_t start = time(NULL);
295 int id = *(int *)arg;
296
297 rcu_register_thread();
298 do {
299 idr_alloc(&find_idr, xa_mk_value(id), id, id + 1, GFP_KERNEL);
300 idr_remove(&find_idr, id);
301 } while (time(NULL) < start + 10);
302 rcu_unregister_thread();
303
304 return NULL;
305}
306
307void idr_find_test_1(int anchor_id, int throbber_id)
308{
309 pthread_t throbber;
310 time_t start = time(NULL);
311
312 pthread_create(&throbber, NULL, idr_throbber, &throbber_id);
313
314 BUG_ON(idr_alloc(&find_idr, xa_mk_value(anchor_id), anchor_id,
315 anchor_id + 1, GFP_KERNEL) != anchor_id);
316
317 do {
318 int id = 0;
319 void *entry = idr_get_next(&find_idr, &id);
320 BUG_ON(entry != xa_mk_value(id));
321 } while (time(NULL) < start + 11);
322
323 pthread_join(throbber, NULL);
324
325 idr_remove(&find_idr, anchor_id);
326 BUG_ON(!idr_is_empty(&find_idr));
327}
328
329void idr_find_test(void)
330{
331 idr_find_test_1(100000, 0);
332 idr_find_test_1(0, 100000);
333}
334
290void idr_checks(void) 335void idr_checks(void)
291{ 336{
292 unsigned long i; 337 unsigned long i;
@@ -368,6 +413,7 @@ void idr_checks(void)
368 idr_u32_test(1); 413 idr_u32_test(1);
369 idr_u32_test(0); 414 idr_u32_test(0);
370 idr_align_test(&idr); 415 idr_align_test(&idr);
416 idr_find_test();
371} 417}
372 418
373#define module_init(x) 419#define module_init(x)