History

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 d7d16a89350a ("net: add skb_queue_empty_lockless()").

Qian Cai committed 6 years ago

Commit 86b18aa1 parent c35947b2 files · +22 −3Browse files

Unified · Split

2 files
  1. include/linux/skbuff.h +13 −1
  2. net/unix/af_unix.c +9 −2
include/linux/skbuff.h+13−1
@@ -1821,6 +1821,18 @@ static inline __u32 skb_queue_len(const struct sk_buff_head *list_) · 1820 unmodified lines
1821 return list_->qlen;1821 return list_->qlen;
1822}1822}
1823 1823
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 */
1831static inline __u32 skb_queue_len_lockless(const struct sk_buff_head *list_)
1832{
1833 return READ_ONCE(list_->qlen);
1834}
1835
1824/**1836/**
1825 * __skb_queue_head_init - initialize non-spinlock portions of sk_buff_head1837 * __skb_queue_head_init - initialize non-spinlock portions of sk_buff_head
1826 * @list: queue to initialize1838 * @list: queue to initialize
@@ -2026,7 +2038,7 @@ static inline void __skb_unlink(struct sk_buff *skb, struct sk_buff_head *list) · 199 unmodified lines
2026{2038{
2027 struct sk_buff *next, *prev;2039 struct sk_buff *next, *prev;
2028 2040
2029 list->qlen--;2041 WRITE_ONCE(list->qlen, list->qlen - 1);
2030 next = skb->next;2042 next = skb->next;
2031 prev = skb->prev;2043 prev = skb->prev;
2032 skb->next = skb->prev = NULL;2044 skb->next = skb->prev = NULL;
net/unix/af_unix.c+9−2
@@ -189,11 +189,17 @@ static inline int unix_may_send(struct sock *sk, struct sock *osk) · 188 unmodified lines
189 return unix_peer(osk) == NULL || unix_our_peer(sk, osk);189 return unix_peer(osk) == NULL || unix_our_peer(sk, osk);
190}190}
191 191
192static inline int unix_recvq_full(struct sock const *sk)192static inline int unix_recvq_full(const struct sock *sk)
193{193{
194 return skb_queue_len(&sk->sk_receive_queue) > sk->sk_max_ack_backlog;194 return skb_queue_len(&sk->sk_receive_queue) > sk->sk_max_ack_backlog;
195}195}
196 196
197static 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
197struct sock *unix_peer_get(struct sock *s)203struct sock *unix_peer_get(struct sock *s)
198{204{
199 struct sock *peer;205 struct sock *peer;
@@ -1758,7 +1764,8 @@ restart_locked: · 1558 unmodified lines
1758 * - unix_peer(sk) == sk by time of get but disconnected before lock1764 * - unix_peer(sk) == sk by time of get but disconnected before lock
1759 */1765 */
1760 if (other != sk &&1766 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))) {
1762 if (timeo) {1769 if (timeo) {
1763 timeo = unix_wait_for_peer(other, timeo);1770 timeo = unix_wait_for_peer(other, timeo);
1764 1771