aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Howells <dhowells@redhat.com>2010-04-06 17:36:20 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2010-04-09 13:12:03 -0400
commitce82653d6cfcc95ba88c25908664878459fb1b8d (patch)
treeab80dd0055bcb4b9296c28c241f1d1fba229be1f
parentd3e06e2b15590b70ea73733fc4612e4741ff46e0 (diff)
radix_tree_tag_get() is not as safe as the docs make out [ver #2]
radix_tree_tag_get() is not safe to use concurrently with radix_tree_tag_set() or radix_tree_tag_clear(). The problem is that the double tag_get() in radix_tree_tag_get(): if (!tag_get(node, tag, offset)) saw_unset_tag = 1; if (height == 1) { int ret = tag_get(node, tag, offset); may see the value change due to the action of set/clear. RCU is no protection against this as no pointers are being changed, no nodes are being replaced according to a COW protocol - set/clear alter the node directly. The documentation in linux/radix-tree.h, however, says that radix_tree_tag_get() is an exception to the rule that "any function modifying the tree or tags (...) must exclude other modifications, and exclude any functions reading the tree". The problem is that the next statement in radix_tree_tag_get() checks that the tag doesn't vary over time: BUG_ON(ret && saw_unset_tag); This has been seen happening in FS-Cache: https://www.redhat.com/archives/linux-cachefs/2010-April/msg00013.html To this end, remove the BUG_ON() from radix_tree_tag_get() and note in various comments that the value of the tag may change whilst the RCU read lock is held, and thus that the return value of radix_tree_tag_get() may not be relied upon unless radix_tree_tag_set/clear() and radix_tree_delete() are excluded from running concurrently with it. Reported-by: Romain DEGEZ <romain.degez@smartjog.com> Signed-off-by: David Howells <dhowells@redhat.com> Acked-by: Nick Piggin <npiggin@suse.de> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r--include/linux/radix-tree.h7
-rw-r--r--lib/radix-tree.c12
2 files changed, 13 insertions, 6 deletions
diff --git a/include/linux/radix-tree.h b/include/linux/radix-tree.h
index c5da74918096..55ca73cf25e5 100644
--- a/include/linux/radix-tree.h
+++ b/include/linux/radix-tree.h
@@ -121,6 +121,13 @@ do { \
121 * (Note, rcu_assign_pointer and rcu_dereference are not needed to control 121 * (Note, rcu_assign_pointer and rcu_dereference are not needed to control
122 * access to data items when inserting into or looking up from the radix tree) 122 * access to data items when inserting into or looking up from the radix tree)
123 * 123 *
124 * Note that the value returned by radix_tree_tag_get() may not be relied upon
125 * if only the RCU read lock is held. Functions to set/clear tags and to
126 * delete nodes running concurrently with it may affect its result such that
127 * two consecutive reads in the same locked section may return different
128 * values. If reliability is required, modification functions must also be
129 * excluded from concurrency.
130 *
124 * radix_tree_tagged is able to be called without locking or RCU. 131 * radix_tree_tagged is able to be called without locking or RCU.
125 */ 132 */
126 133
diff --git a/lib/radix-tree.c b/lib/radix-tree.c
index 0871582aa29d..2a087e0f9863 100644
--- a/lib/radix-tree.c
+++ b/lib/radix-tree.c
@@ -555,6 +555,10 @@ EXPORT_SYMBOL(radix_tree_tag_clear);
555 * 555 *
556 * 0: tag not present or not set 556 * 0: tag not present or not set
557 * 1: tag set 557 * 1: tag set
558 *
559 * Note that the return value of this function may not be relied on, even if
560 * the RCU lock is held, unless tag modification and node deletion are excluded
561 * from concurrency.
558 */ 562 */
559int radix_tree_tag_get(struct radix_tree_root *root, 563int radix_tree_tag_get(struct radix_tree_root *root,
560 unsigned long index, unsigned int tag) 564 unsigned long index, unsigned int tag)
@@ -595,12 +599,8 @@ int radix_tree_tag_get(struct radix_tree_root *root,
595 */ 599 */
596 if (!tag_get(node, tag, offset)) 600 if (!tag_get(node, tag, offset))
597 saw_unset_tag = 1; 601 saw_unset_tag = 1;
598 if (height == 1) { 602 if (height == 1)
599 int ret = tag_get(node, tag, offset); 603 return !!tag_get(node, tag, offset);
600
601 BUG_ON(ret && saw_unset_tag);
602 return !!ret;
603 }
604 node = rcu_dereference_raw(node->slots[offset]); 604 node = rcu_dereference_raw(node->slots[offset]);
605 shift -= RADIX_TREE_MAP_SHIFT; 605 shift -= RADIX_TREE_MAP_SHIFT;
606 height--; 606 height--;