| From foo@baz Wed 06 Nov 2019 04:08:37 PM CET |
| From: Eric Dumazet <edumazet@google.com> |
| Date: Wed, 23 Oct 2019 22:44:48 -0700 |
| Subject: net: add skb_queue_empty_lockless() |
| |
| From: Eric Dumazet <edumazet@google.com> |
| |
| [ Upstream commit d7d16a89350ab263484c0aa2b523dd3a234e4a80 ] |
| |
| Some paths call skb_queue_empty() without holding |
| the queue lock. We must use a barrier in order |
| to not let the compiler do strange things, and avoid |
| KCSAN splats. |
| |
| Adding a barrier in skb_queue_empty() might be overkill, |
| I prefer adding a new helper to clearly identify |
| points where the callers might be lockless. This might |
| help us finding real bugs. |
| |
| The corresponding WRITE_ONCE() should add zero cost |
| for current compilers. |
| |
| Signed-off-by: Eric Dumazet <edumazet@google.com> |
| Signed-off-by: David S. Miller <davem@davemloft.net> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| include/linux/skbuff.h | 33 ++++++++++++++++++++++++--------- |
| 1 file changed, 24 insertions(+), 9 deletions(-) |
| |
| --- a/include/linux/skbuff.h |
| +++ b/include/linux/skbuff.h |
| @@ -1346,6 +1346,19 @@ static inline int skb_queue_empty(const |
| } |
| |
| /** |
| + * skb_queue_empty_lockless - check if a queue is empty |
| + * @list: queue head |
| + * |
| + * Returns true if the queue is empty, false otherwise. |
| + * This variant can be used in lockless contexts. |
| + */ |
| +static inline bool skb_queue_empty_lockless(const struct sk_buff_head *list) |
| +{ |
| + return READ_ONCE(list->next) == (const struct sk_buff *) list; |
| +} |
| + |
| + |
| +/** |
| * skb_queue_is_last - check if skb is the last entry in the queue |
| * @list: queue head |
| * @skb: buffer |
| @@ -1709,9 +1722,11 @@ static inline void __skb_insert(struct s |
| struct sk_buff *prev, struct sk_buff *next, |
| struct sk_buff_head *list) |
| { |
| - newsk->next = next; |
| - newsk->prev = prev; |
| - next->prev = prev->next = newsk; |
| + /* see skb_queue_empty_lockless() for the opposite READ_ONCE() */ |
| + WRITE_ONCE(newsk->next, next); |
| + WRITE_ONCE(newsk->prev, prev); |
| + WRITE_ONCE(next->prev, newsk); |
| + WRITE_ONCE(prev->next, newsk); |
| list->qlen++; |
| } |
| |
| @@ -1722,11 +1737,11 @@ static inline void __skb_queue_splice(co |
| struct sk_buff *first = list->next; |
| struct sk_buff *last = list->prev; |
| |
| - first->prev = prev; |
| - prev->next = first; |
| + WRITE_ONCE(first->prev, prev); |
| + WRITE_ONCE(prev->next, first); |
| |
| - last->next = next; |
| - next->prev = last; |
| + WRITE_ONCE(last->next, next); |
| + WRITE_ONCE(next->prev, last); |
| } |
| |
| /** |
| @@ -1867,8 +1882,8 @@ static inline void __skb_unlink(struct s |
| next = skb->next; |
| prev = skb->prev; |
| skb->next = skb->prev = NULL; |
| - next->prev = prev; |
| - prev->next = next; |
| + WRITE_ONCE(next->prev, prev); |
| + WRITE_ONCE(prev->next, next); |
| } |
| |
| /** |