diff options
| author | Nick Piggin <npiggin@kernel.dk> | 2010-11-11 17:05:19 -0500 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2010-11-12 10:55:32 -0500 |
| commit | 27d20fddc8af539464fc3ba499d6a830054c3bd6 (patch) | |
| tree | 23514cfe88f90150a8635c47586a8a378fb905e3 /lib | |
| parent | eaf06b241b091357e72b76863ba16e89610d31bd (diff) | |
radix-tree: fix RCU bug
Salman Qazi describes the following radix-tree bug:
In the following case, we get can get a deadlock:
0. The radix tree contains two items, one has the index 0.
1. The reader (in this case find_get_pages) takes the rcu_read_lock.
2. The reader acquires slot(s) for item(s) including the index 0 item.
3. The non-zero index item is deleted, and as a consequence the other item is
moved to the root of the tree. The place where it used to be is queued for
deletion after the readers finish.
3b. The zero item is deleted, removing it from the direct slot, it remains in
the rcu-delayed indirect node.
4. The reader looks at the index 0 slot, and finds that the page has 0 ref
count
5. The reader looks at it again, hoping that the item will either be freed or
the ref count will increase. This never happens, as the slot it is looking
at will never be updated. Also, this slot can never be reclaimed because
the reader is holding rcu_read_lock and is in an infinite loop.
The fix is to re-use the same "indirect" pointer case that requires a slot
lookup retry into a general "retry the lookup" bit.
Signed-off-by: Nick Piggin <npiggin@kernel.dk>
Reported-by: Salman Qazi <sqazi@google.com>
Cc: <stable@kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/radix-tree.c | 83 |
1 files changed, 58 insertions, 25 deletions
diff --git a/lib/radix-tree.c b/lib/radix-tree.c index 6f412ab4c24f..5086bb962b4d 100644 --- a/lib/radix-tree.c +++ b/lib/radix-tree.c | |||
| @@ -82,6 +82,16 @@ struct radix_tree_preload { | |||
| 82 | }; | 82 | }; |
| 83 | static DEFINE_PER_CPU(struct radix_tree_preload, radix_tree_preloads) = { 0, }; | 83 | static DEFINE_PER_CPU(struct radix_tree_preload, radix_tree_preloads) = { 0, }; |
| 84 | 84 | ||
| 85 | static inline void *ptr_to_indirect(void *ptr) | ||
| 86 | { | ||
| 87 | return (void *)((unsigned long)ptr | RADIX_TREE_INDIRECT_PTR); | ||
| 88 | } | ||
| 89 | |||
| 90 | static inline void *indirect_to_ptr(void *ptr) | ||
| 91 | { | ||
| 92 | return (void *)((unsigned long)ptr & ~RADIX_TREE_INDIRECT_PTR); | ||
| 93 | } | ||
| 94 | |||
| 85 | static inline gfp_t root_gfp_mask(struct radix_tree_root *root) | 95 | static inline gfp_t root_gfp_mask(struct radix_tree_root *root) |
| 86 | { | 96 | { |
| 87 | return root->gfp_mask & __GFP_BITS_MASK; | 97 | return root->gfp_mask & __GFP_BITS_MASK; |
| @@ -265,7 +275,7 @@ static int radix_tree_extend(struct radix_tree_root *root, unsigned long index) | |||
| 265 | return -ENOMEM; | 275 | return -ENOMEM; |
| 266 | 276 | ||
| 267 | /* Increase the height. */ | 277 | /* Increase the height. */ |
| 268 | node->slots[0] = radix_tree_indirect_to_ptr(root->rnode); | 278 | node->slots[0] = indirect_to_ptr(root->rnode); |
| 269 | 279 | ||
| 270 | /* Propagate the aggregated tag info into the new root */ | 280 | /* Propagate the aggregated tag info into the new root */ |
| 271 | for (tag = 0; tag < RADIX_TREE_MAX_TAGS; tag++) { | 281 | for (tag = 0; tag < RADIX_TREE_MAX_TAGS; tag++) { |
| @@ -276,7 +286,7 @@ static int radix_tree_extend(struct radix_tree_root *root, unsigned long index) | |||
| 276 | newheight = root->height+1; | 286 | newheight = root->height+1; |
| 277 | node->height = newheight; | 287 | node->height = newheight; |
| 278 | node->count = 1; | 288 | node->count = 1; |
| 279 | node = radix_tree_ptr_to_indirect(node); | 289 | node = ptr_to_indirect(node); |
| 280 | rcu_assign_pointer(root->rnode, node); | 290 | rcu_assign_pointer(root->rnode, node); |
| 281 | root->height = newheight; | 291 | root->height = newheight; |
| 282 | } while (height > root->height); | 292 | } while (height > root->height); |
| @@ -309,7 +319,7 @@ int radix_tree_insert(struct radix_tree_root *root, | |||
| 309 | return error; | 319 | return error; |
| 310 | } | 320 | } |
| 311 | 321 | ||
| 312 | slot = radix_tree_indirect_to_ptr(root->rnode); | 322 | slot = indirect_to_ptr(root->rnode); |
| 313 | 323 | ||
| 314 | height = root->height; | 324 | height = root->height; |
| 315 | shift = (height-1) * RADIX_TREE_MAP_SHIFT; | 325 | shift = (height-1) * RADIX_TREE_MAP_SHIFT; |
| @@ -325,8 +335,7 @@ int radix_tree_insert(struct radix_tree_root *root, | |||
| 325 | rcu_assign_pointer(node->slots[offset], slot); | 335 | rcu_assign_pointer(node->slots[offset], slot); |
| 326 | node->count++; | 336 | node->count++; |
| 327 | } else | 337 | } else |
| 328 | rcu_assign_pointer(root->rnode, | 338 | rcu_assign_pointer(root->rnode, ptr_to_indirect(slot)); |
| 329 | radix_tree_ptr_to_indirect(slot)); | ||
| 330 | } | 339 | } |
| 331 | 340 | ||
| 332 | /* Go a level down */ | 341 | /* Go a level down */ |
| @@ -374,7 +383,7 @@ static void *radix_tree_lookup_element(struct radix_tree_root *root, | |||
| 374 | return NULL; | 383 | return NULL; |
| 375 | return is_slot ? (void *)&root->rnode : node; | 384 | return is_slot ? (void *)&root->rnode : node; |
| 376 | } | 385 | } |
| 377 | node = radix_tree_indirect_to_ptr(node); | 386 | node = indirect_to_ptr(node); |
| 378 | 387 | ||
| 379 | height = node->height; | 388 | height = node->height; |
| 380 | if (index > radix_tree_maxindex(height)) | 389 | if (index > radix_tree_maxindex(height)) |
| @@ -393,7 +402,7 @@ static void *radix_tree_lookup_element(struct radix_tree_root *root, | |||
| 393 | height--; | 402 | height--; |
| 394 | } while (height > 0); | 403 | } while (height > 0); |
| 395 | 404 | ||
| 396 | return is_slot ? (void *)slot:node; | 405 | return is_slot ? (void *)slot : indirect_to_ptr(node); |
| 397 | } | 406 | } |
| 398 | 407 | ||
| 399 | /** | 408 | /** |
| @@ -455,7 +464,7 @@ void *radix_tree_tag_set(struct radix_tree_root *root, | |||
| 455 | height = root->height; | 464 | height = root->height; |
| 456 | BUG_ON(index > radix_tree_maxindex(height)); | 465 | BUG_ON(index > radix_tree_maxindex(height)); |
| 457 | 466 | ||
| 458 | slot = radix_tree_indirect_to_ptr(root->rnode); | 467 | slot = indirect_to_ptr(root->rnode); |
| 459 | shift = (height - 1) * RADIX_TREE_MAP_SHIFT; | 468 | shift = (height - 1) * RADIX_TREE_MAP_SHIFT; |
| 460 | 469 | ||
| 461 | while (height > 0) { | 470 | while (height > 0) { |
| @@ -509,7 +518,7 @@ void *radix_tree_tag_clear(struct radix_tree_root *root, | |||
| 509 | 518 | ||
| 510 | shift = (height - 1) * RADIX_TREE_MAP_SHIFT; | 519 | shift = (height - 1) * RADIX_TREE_MAP_SHIFT; |
| 511 | pathp->node = NULL; | 520 | pathp->node = NULL; |
| 512 | slot = radix_tree_indirect_to_ptr(root->rnode); | 521 | slot = indirect_to_ptr(root->rnode); |
| 513 | 522 | ||
| 514 | while (height > 0) { | 523 | while (height > 0) { |
| 515 | int offset; | 524 | int offset; |
| @@ -579,7 +588,7 @@ int radix_tree_tag_get(struct radix_tree_root *root, | |||
| 579 | 588 | ||
| 580 | if (!radix_tree_is_indirect_ptr(node)) | 589 | if (!radix_tree_is_indirect_ptr(node)) |
| 581 | return (index == 0); | 590 | return (index == 0); |
| 582 | node = radix_tree_indirect_to_ptr(node); | 591 | node = indirect_to_ptr(node); |
| 583 | 592 | ||
| 584 | height = node->height; | 593 | height = node->height; |
| 585 | if (index > radix_tree_maxindex(height)) | 594 | if (index > radix_tree_maxindex(height)) |
| @@ -666,7 +675,7 @@ unsigned long radix_tree_range_tag_if_tagged(struct radix_tree_root *root, | |||
| 666 | } | 675 | } |
| 667 | 676 | ||
| 668 | shift = (height - 1) * RADIX_TREE_MAP_SHIFT; | 677 | shift = (height - 1) * RADIX_TREE_MAP_SHIFT; |
| 669 | slot = radix_tree_indirect_to_ptr(root->rnode); | 678 | slot = indirect_to_ptr(root->rnode); |
| 670 | 679 | ||
| 671 | /* | 680 | /* |
| 672 | * we fill the path from (root->height - 2) to 0, leaving the index at | 681 | * we fill the path from (root->height - 2) to 0, leaving the index at |
| @@ -897,7 +906,7 @@ radix_tree_gang_lookup(struct radix_tree_root *root, void **results, | |||
| 897 | results[0] = node; | 906 | results[0] = node; |
| 898 | return 1; | 907 | return 1; |
| 899 | } | 908 | } |
| 900 | node = radix_tree_indirect_to_ptr(node); | 909 | node = indirect_to_ptr(node); |
| 901 | 910 | ||
| 902 | max_index = radix_tree_maxindex(node->height); | 911 | max_index = radix_tree_maxindex(node->height); |
| 903 | 912 | ||
| @@ -916,7 +925,8 @@ radix_tree_gang_lookup(struct radix_tree_root *root, void **results, | |||
| 916 | slot = *(((void ***)results)[ret + i]); | 925 | slot = *(((void ***)results)[ret + i]); |
| 917 | if (!slot) | 926 | if (!slot) |
| 918 | continue; | 927 | continue; |
| 919 | results[ret + nr_found] = rcu_dereference_raw(slot); | 928 | results[ret + nr_found] = |
| 929 | indirect_to_ptr(rcu_dereference_raw(slot)); | ||
| 920 | nr_found++; | 930 | nr_found++; |
| 921 | } | 931 | } |
| 922 | ret += nr_found; | 932 | ret += nr_found; |
| @@ -965,7 +975,7 @@ radix_tree_gang_lookup_slot(struct radix_tree_root *root, void ***results, | |||
| 965 | results[0] = (void **)&root->rnode; | 975 | results[0] = (void **)&root->rnode; |
| 966 | return 1; | 976 | return 1; |
| 967 | } | 977 | } |
| 968 | node = radix_tree_indirect_to_ptr(node); | 978 | node = indirect_to_ptr(node); |
| 969 | 979 | ||
| 970 | max_index = radix_tree_maxindex(node->height); | 980 | max_index = radix_tree_maxindex(node->height); |
| 971 | 981 | ||
| @@ -1090,7 +1100,7 @@ radix_tree_gang_lookup_tag(struct radix_tree_root *root, void **results, | |||
| 1090 | results[0] = node; | 1100 | results[0] = node; |
| 1091 | return 1; | 1101 | return 1; |
| 1092 | } | 1102 | } |
| 1093 | node = radix_tree_indirect_to_ptr(node); | 1103 | node = indirect_to_ptr(node); |
| 1094 | 1104 | ||
| 1095 | max_index = radix_tree_maxindex(node->height); | 1105 | max_index = radix_tree_maxindex(node->height); |
| 1096 | 1106 | ||
| @@ -1109,7 +1119,8 @@ radix_tree_gang_lookup_tag(struct radix_tree_root *root, void **results, | |||
| 1109 | slot = *(((void ***)results)[ret + i]); | 1119 | slot = *(((void ***)results)[ret + i]); |
| 1110 | if (!slot) | 1120 | if (!slot) |
| 1111 | continue; | 1121 | continue; |
| 1112 | results[ret + nr_found] = rcu_dereference_raw(slot); | 1122 | results[ret + nr_found] = |
| 1123 | indirect_to_ptr(rcu_dereference_raw(slot)); | ||
| 1113 | nr_found++; | 1124 | nr_found++; |
| 1114 | } | 1125 | } |
| 1115 | ret += nr_found; | 1126 | ret += nr_found; |
| @@ -1159,7 +1170,7 @@ radix_tree_gang_lookup_tag_slot(struct radix_tree_root *root, void ***results, | |||
| 1159 | results[0] = (void **)&root->rnode; | 1170 | results[0] = (void **)&root-> |
