Skip to content

Commit 86b18aa

Browse files
Qian Caidavem330
authored andcommitted
skbuff: fix a data race in skb_queue_len()
sk_buff.qlen can be accessed concurrently as noticed by KCSAN, BUG: KCSAN: data-race in __skb_try_recv_from_queue / unix_dgram_sendmsg read to 0xffff8a1b1d8a81c0 of 4 bytes by task 5371 on cpu 96: unix_dgram_sendmsg+0x9a9/0xb70 include/linux/skbuff.h:1821 net/unix/af_unix.c:1761 ____sys_sendmsg+0x33e/0x370 ___sys_sendmsg+0xa6/0xf0 __sys_sendmsg+0x69/0xf0 __x64_sys_sendmsg+0x51/0x70 do_syscall_64+0x91/0xb47 entry_SYSCALL_64_after_hwframe+0x49/0xbe write to 0xffff8a1b1d8a81c0 of 4 bytes by task 1 on cpu 99: __skb_try_recv_from_queue+0x327/0x410 include/linux/skbuff.h:2029 __skb_try_recv_datagram+0xbe/0x220 unix_dgram_recvmsg+0xee/0x850 ____sys_recvmsg+0x1fb/0x210 ___sys_recvmsg+0xa2/0xf0 __sys_recvmsg+0x66/0xf0 __x64_sys_recvmsg+0x51/0x70 do_syscall_64+0x91/0xb47 entry_SYSCALL_64_after_hwframe+0x49/0xbe Since only the read is operating as lockless, it could introduce a logic bug in unix_recvq_full() due to the load tearing. Fix it by adding a lockless variant of skb_queue_len() and unix_recvq_full() where READ_ONCE() is on the read while WRITE_ONCE() is on the write similar to the commit d7d16a8 ("net: add skb_queue_empty_lockless()"). Signed-off-by: Qian Cai <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent c35947b commit 86b18aa

File tree

2 files changed

+22
-3
lines changed

2 files changed

+22
-3
lines changed

include/linux/skbuff.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1821,6 +1821,18 @@ static inline __u32 skb_queue_len(const struct sk_buff_head *list_)
18211821
return list_->qlen;
18221822
}
18231823

1824+
/**
1825+
* skb_queue_len_lockless - get queue length
1826+
* @list_: list to measure
1827+
*
1828+
* Return the length of an &sk_buff queue.
1829+
* This variant can be used in lockless contexts.
1830+
*/
1831+
static inline __u32 skb_queue_len_lockless(const struct sk_buff_head *list_)
1832+
{
1833+
return READ_ONCE(list_->qlen);
1834+
}
1835+
18241836
/**
18251837
* __skb_queue_head_init - initialize non-spinlock portions of sk_buff_head
18261838
* @list: queue to initialize
@@ -2026,7 +2038,7 @@ static inline void __skb_unlink(struct sk_buff *skb, struct sk_buff_head *list)
20262038
{
20272039
struct sk_buff *next, *prev;
20282040

2029-
list->qlen--;
2041+
WRITE_ONCE(list->qlen, list->qlen - 1);
20302042
next = skb->next;
20312043
prev = skb->prev;
20322044
skb->next = skb->prev = NULL;

net/unix/af_unix.c

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,11 +189,17 @@ static inline int unix_may_send(struct sock *sk, struct sock *osk)
189189
return unix_peer(osk) == NULL || unix_our_peer(sk, osk);
190190
}
191191

192-
static inline int unix_recvq_full(struct sock const *sk)
192+
static inline int unix_recvq_full(const struct sock *sk)
193193
{
194194
return skb_queue_len(&sk->sk_receive_queue) > sk->sk_max_ack_backlog;
195195
}
196196

197+
static inline int unix_recvq_full_lockless(const struct sock *sk)
198+
{
199+
return skb_queue_len_lockless(&sk->sk_receive_queue) >
200+
READ_ONCE(sk->sk_max_ack_backlog);
201+
}
202+
197203
struct sock *unix_peer_get(struct sock *s)
198204
{
199205
struct sock *peer;
@@ -1758,7 +1764,8 @@ static int unix_dgram_sendmsg(struct socket *sock, struct msghdr *msg,
17581764
* - unix_peer(sk) == sk by time of get but disconnected before lock
17591765
*/
17601766
if (other != sk &&
1761-
unlikely(unix_peer(other) != sk && unix_recvq_full(other))) {
1767+
unlikely(unix_peer(other) != sk &&
1768+
unix_recvq_full_lockless(other))) {
17621769
if (timeo) {
17631770
timeo = unix_wait_for_peer(other, timeo);
17641771

0 commit comments

Comments
 (0)