aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHannes Frederic Sowa <hannes@stressinduktion.org>2015-11-10 10:23:15 -0500
committerDavid S. Miller <davem@davemloft.net>2015-11-15 13:16:34 -0500
commit73ed5d25dce0354ea381d6dc93005c3085fae03d (patch)
tree38dbb1ef110ad0c1edfa88e331051d328adc6197
parent49e4a2293035b420e807e739999d59c8ec1488e9 (diff)
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: 2b514574f7e8 ("net: af_unix: implement splice for stream af_unix sockets") Reported-by: Dmitry Vyukov <dvyukov@google.com> Cc: Dmitry Vyukov <dvyukov@google.com> Cc: Eric Dumazet <eric.dumazet@gmail.com> Acked-by: Eric Dumazet <edumazet@google.com> Signed-off-by: Hannes Frederic Sowa <hannes@stressinduktion.org> Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r--net/unix/af_unix.c18
1 files changed, 18 insertions, 0 deletions
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index aaa0b58d6aba..12b886f07982 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -441,6 +441,7 @@ static void unix_release_sock(struct sock *sk, int embrion)
441 if (state == TCP_LISTEN) 441 if (state == TCP_LISTEN)
442 unix_release_sock(skb->sk, 1); 442 unix_release_sock(skb->sk, 1);
443 /* passed fds are erased in the kfree_skb hook */ 443 /* passed fds are erased in the kfree_skb hook */
444 UNIXCB(skb).consumed = skb->len;
444 kfree_skb(skb); 445 kfree_skb(skb);
445 } 446 }
446 447
@@ -2072,6 +2073,7 @@ static int unix_stream_read_generic(struct unix_stream_read_state *state)
2072 2073
2073 do { 2074 do {
2074 int chunk; 2075 int chunk;
2076 bool drop_skb;
2075 struct sk_buff *skb, *last; 2077 struct sk_buff *skb, *last;
2076 2078
2077 unix_state_lock(sk); 2079 unix_state_lock(sk);
@@ -2152,7 +2154,11 @@ unlock:
2152 } 2154 }
2153 2155
2154 chunk = min_t(unsigned int, unix_skb_len(skb) - skip, size); 2156 chunk = min_t(unsigned int, unix_skb_len(skb) - skip, size);
2157 skb_get(skb);
2155 chunk = state->recv_actor(skb, skip, chunk, state); 2158 chunk = state->recv_actor(skb, skip, chunk, state);
2159 drop_skb = !unix_skb_len(skb);
2160 /* skb is only safe to use if !drop_skb */
2161 consume_skb(skb);
2156 if (chunk < 0) { 2162 if (chunk < 0) {
2157 if (copied == 0) 2163 if (copied == 0)
2158 copied = -EFAULT; 2164 copied = -EFAULT;
@@ -2161,6 +2167,18 @@ unlock:
2161 copied += chunk; 2167 copied += chunk;
2162 size -= chunk; 2168 size -= chunk;
2163 2169
2170 if (drop_skb) {
2171 /* the skb was touched by a concurrent reader;
2172 * we should not expect anything from this skb
2173 * anymore and assume it invalid - we can be
2174 * sure it was dropped from the socket queue
2175 *
2176 * let's report a short read
2177 */
2178 err = 0;
2179 break;
2180 }
2181
2164 /* Mark read part of skb as used */ 2182 /* Mark read part of skb as used */
2165 if (!(flags & MSG_PEEK)) { 2183 if (!(flags & MSG_PEEK)) {
2166 UNIXCB(skb).consumed += chunk; 2184 UNIXCB(skb).consumed += chunk;