Răsfoiți Sursa

rculist: Use WRITE_ONCE() when deleting from reader-visible list

The various RCU list-deletion macros (list_del_rcu(),
hlist_del_init_rcu(), hlist_del_rcu(), hlist_bl_del_init_rcu(),
hlist_bl_del_rcu(), hlist_nulls_del_init_rcu(), and hlist_nulls_del_rcu())
do plain stores into the ->next pointer of the preceding list elemment.
Unfortunately, the compiler is within its rights to (for example) use
byte-at-a-time writes to update the pointer, which would fatally confuse
concurrent readers.  This patch therefore adds the needed WRITE_ONCE()
macros.

KernelThreadSanitizer (KTSAN) reported the __hlist_del() issue, which
is a problem when __hlist_del() is invoked by hlist_del_rcu().

Reported-by: Dmitry Vyukov <dvyukov@google.com>
Signed-off-by: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
Reviewed-by: Josh Triplett <josh@joshtriplett.org>
Paul E. McKenney 10 ani în urmă
părinte
comite
7f5f873c6a
3 a modificat fișierele cu 8 adăugiri și 5 ștergeri
  1. 3 2
      include/linux/list.h
  2. 3 2
      include/linux/list_bl.h
  3. 2 1
      include/linux/list_nulls.h

+ 3 - 2
include/linux/list.h

@@ -87,7 +87,7 @@ static inline void list_add_tail(struct list_head *new, struct list_head *head)
 static inline void __list_del(struct list_head * prev, struct list_head * next)
 static inline void __list_del(struct list_head * prev, struct list_head * next)
 {
 {
 	next->prev = prev;
 	next->prev = prev;
-	prev->next = next;
+	WRITE_ONCE(prev->next, next);
 }
 }
 
 
 /**
 /**
@@ -615,7 +615,8 @@ static inline void __hlist_del(struct hlist_node *n)
 {
 {
 	struct hlist_node *next = n->next;
 	struct hlist_node *next = n->next;
 	struct hlist_node **pprev = n->pprev;
 	struct hlist_node **pprev = n->pprev;
-	*pprev = next;
+
+	WRITE_ONCE(*pprev, next);
 	if (next)
 	if (next)
 		next->pprev = pprev;
 		next->pprev = pprev;
 }
 }

+ 3 - 2
include/linux/list_bl.h

@@ -93,9 +93,10 @@ static inline void __hlist_bl_del(struct hlist_bl_node *n)
 	LIST_BL_BUG_ON((unsigned long)n & LIST_BL_LOCKMASK);
 	LIST_BL_BUG_ON((unsigned long)n & LIST_BL_LOCKMASK);
 
 
 	/* pprev may be `first`, so be careful not to lose the lock bit */
 	/* pprev may be `first`, so be careful not to lose the lock bit */
-	*pprev = (struct hlist_bl_node *)
+	WRITE_ONCE(*pprev,
+		   (struct hlist_bl_node *)
 			((unsigned long)next |
 			((unsigned long)next |
-			 ((unsigned long)*pprev & LIST_BL_LOCKMASK));
+			 ((unsigned long)*pprev & LIST_BL_LOCKMASK)));
 	if (next)
 	if (next)
 		next->pprev = pprev;
 		next->pprev = pprev;
 }
 }

+ 2 - 1
include/linux/list_nulls.h

@@ -76,7 +76,8 @@ static inline void __hlist_nulls_del(struct hlist_nulls_node *n)
 {
 {
 	struct hlist_nulls_node *next = n->next;
 	struct hlist_nulls_node *next = n->next;
 	struct hlist_nulls_node **pprev = n->pprev;
 	struct hlist_nulls_node **pprev = n->pprev;
-	*pprev = next;
+
+	WRITE_ONCE(*pprev, next);
 	if (!is_a_nulls(next))
 	if (!is_a_nulls(next))
 		next->pprev = pprev;
 		next->pprev = pprev;
 }
 }