Commit 73ed5d25 authored by Hannes Frederic Sowa's avatar Hannes Frederic Sowa Committed by David S. Miller

af-unix: fix use-after-free with concurrent readers while splicing

During splicing an af-unix socket to a pipe we have to drop all
af-unix socket locks. While doing so we allow another reader to enter
unix_stream_read_generic which can read, copy and finally free another
skb. If exactly this skb is just in process of being spliced we get a
use-after-free report by kasan.

First, we must make sure to not have a free while the skb is used during
the splice operation. We simply increment its use counter before unlocking
the reader lock.

Stream sockets have the nice characteristic that we don't care about
zero length writes and they never reach the peer socket's queue. That
said, we can take the UNIXCB.consumed field as the indicator if the
skb was already freed from the socket's receive queue. If the skb was
fully consumed after we locked the reader side again we know it has been
dropped by a second reader. We indicate a short read to user space and
abort the current splice operation.

This bug has been found with syzkaller
(http://github.com/google/syzkaller) by Dmitry Vyukov.

Fixes: 2b514574 ("net: af_unix: implement splice for stream af_unix sockets")
Reported-by: default avatarDmitry Vyukov <dvyukov@google.com>
Cc: Dmitry Vyukov <dvyukov@google.com>
Cc: Eric Dumazet <eric.dumazet@gmail.com>
Acked-by: default avatarEric Dumazet <edumazet@google.com>
Signed-off-by: default avatarHannes Frederic Sowa <hannes@stressinduktion.org>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 49e4a229
...@@ -441,6 +441,7 @@ static void unix_release_sock(struct sock *sk, int embrion) ...@@ -441,6 +441,7 @@ static void unix_release_sock(struct sock *sk, int embrion)
if (state == TCP_LISTEN) if (state == TCP_LISTEN)
unix_release_sock(skb->sk, 1); unix_release_sock(skb->sk, 1);
/* passed fds are erased in the kfree_skb hook */ /* passed fds are erased in the kfree_skb hook */
UNIXCB(skb).consumed = skb->len;
kfree_skb(skb); kfree_skb(skb);
} }
...@@ -2072,6 +2073,7 @@ static int unix_stream_read_generic(struct unix_stream_read_state *state) ...@@ -2072,6 +2073,7 @@ static int unix_stream_read_generic(struct unix_stream_read_state *state)
do { do {
int chunk; int chunk;
bool drop_skb;
struct sk_buff *skb, *last; struct sk_buff *skb, *last;
unix_state_lock(sk); unix_state_lock(sk);
...@@ -2152,7 +2154,11 @@ static int unix_stream_read_generic(struct unix_stream_read_state *state) ...@@ -2152,7 +2154,11 @@ static int unix_stream_read_generic(struct unix_stream_read_state *state)
} }
chunk = min_t(unsigned int, unix_skb_len(skb) - skip, size); chunk = min_t(unsigned int, unix_skb_len(skb) - skip, size);
skb_get(skb);
chunk = state->recv_actor(skb, skip, chunk, state); chunk = state->recv_actor(skb, skip, chunk, state);
drop_skb = !unix_skb_len(skb);
/* skb is only safe to use if !drop_skb */
consume_skb(skb);
if (chunk < 0) { if (chunk < 0) {
if (copied == 0) if (copied == 0)
copied = -EFAULT; copied = -EFAULT;
...@@ -2161,6 +2167,18 @@ static int unix_stream_read_generic(struct unix_stream_read_state *state) ...@@ -2161,6 +2167,18 @@ static int unix_stream_read_generic(struct unix_stream_read_state *state)
copied += chunk; copied += chunk;
size -= chunk; size -= chunk;
if (drop_skb) {
/* the skb was touched by a concurrent reader;
* we should not expect anything from this skb
* anymore and assume it invalid - we can be
* sure it was dropped from the socket queue
*
* let's report a short read
*/
err = 0;
break;
}
/* Mark read part of skb as used */ /* Mark read part of skb as used */
if (!(flags & MSG_PEEK)) { if (!(flags & MSG_PEEK)) {
UNIXCB(skb).consumed += chunk; UNIXCB(skb).consumed += chunk;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment