Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

[AF_UNIX]: Fix stream recvmsg() race.

A recv() on an AF_UNIX, SOCK_STREAM socket can race with a
send()+close() on the peer, causing recv() to return zero, even though
the sent data should be received.

This happens if the send() and the close() is performed between
skb_dequeue() and checking sk->sk_shutdown in unix_stream_recvmsg():

process A skb_dequeue() returns NULL, there's no data in the socket queue
process B new data is inserted onto the queue by unix_stream_sendmsg()
process B sk->sk_shutdown is set to SHUTDOWN_MASK by unix_release_sock()
process A sk->sk_shutdown is checked, unix_release_sock() returns zero

I'm surprised nobody noticed this, it's not hard to trigger. Maybe
it's just (un)luck with the timing.

It's possible to work around this bug in userspace, by retrying the
recv() once in case of a zero return value.

Signed-off-by: Miklos Szeredi <mszeredi@suse.cz>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Miklos Szeredi and committed by
David S. Miller
3c0d2f37 c764c9ad

+10 -3
+10 -3
net/unix/af_unix.c
··· 1744 1744 int chunk; 1745 1745 struct sk_buff *skb; 1746 1746 1747 + unix_state_lock(sk); 1747 1748 skb = skb_dequeue(&sk->sk_receive_queue); 1748 1749 if (skb==NULL) 1749 1750 { 1750 1751 if (copied >= target) 1751 - break; 1752 + goto unlock; 1752 1753 1753 1754 /* 1754 1755 * POSIX 1003.1g mandates this order. 1755 1756 */ 1756 1757 1757 1758 if ((err = sock_error(sk)) != 0) 1758 - break; 1759 + goto unlock; 1759 1760 if (sk->sk_shutdown & RCV_SHUTDOWN) 1760 - break; 1761 + goto unlock; 1762 + 1763 + unix_state_unlock(sk); 1761 1764 err = -EAGAIN; 1762 1765 if (!timeo) 1763 1766 break; ··· 1774 1771 } 1775 1772 mutex_lock(&u->readlock); 1776 1773 continue; 1774 + unlock: 1775 + unix_state_unlock(sk); 1776 + break; 1777 1777 } 1778 + unix_state_unlock(sk); 1778 1779 1779 1780 if (check_creds) { 1780 1781 /* Never glue messages from different writers */