| From ben@decadent.org.uk Tue Aug 13 20:28:54 2019 |
| From: Ben Hutchings <ben@decadent.org.uk> |
| Date: Tue, 13 Aug 2019 12:53:17 +0100 |
| Subject: tcp: Clear sk_send_head after purging the write queue |
| To: Greg Kroah-Hartman <gregkh@linuxfoundation.org>, Sasha Levin <sashal@kernel.org> |
| Cc: stable@vger.kernel.org, Denis Andzakovic <denis.andzakovic@pulsesecurity.co.nz>, Salvatore Bonaccorso <carnil@debian.org>, Eric Dumazet <edumazet@google.com> |
| Message-ID: <20190813115317.6cgml2mckd3c6u7z@decadent.org.uk> |
| Content-Disposition: inline |
| |
| From: Ben Hutchings <ben@decadent.org.uk> |
| |
| Denis Andzakovic discovered a potential use-after-free in older kernel |
| versions, using syzkaller. tcp_write_queue_purge() frees all skbs in |
| the TCP write queue and can leave sk->sk_send_head pointing to freed |
| memory. tcp_disconnect() clears that pointer after calling |
| tcp_write_queue_purge(), but tcp_connect() does not. It is |
| (surprisingly) possible to add to the write queue between |
| disconnection and reconnection, so this needs to be done in both |
| places. |
| |
| This bug was introduced by backports of commit 7f582b248d0a ("tcp: |
| purge write queue in tcp_connect_init()") and does not exist upstream |
| because of earlier changes in commit 75c119afe14f ("tcp: implement |
| rb-tree based retransmit queue"). The latter is a major change that's |
| not suitable for stable. |
| |
| Reported-by: Denis Andzakovic <denis.andzakovic@pulsesecurity.co.nz> |
| Bisected-by: Salvatore Bonaccorso <carnil@debian.org> |
| Fixes: 7f582b248d0a ("tcp: purge write queue in tcp_connect_init()") |
| Cc: <stable@vger.kernel.org> # before 4.15 |
| Cc: Eric Dumazet <edumazet@google.com> |
| Signed-off-by: Ben Hutchings <ben@decadent.org.uk> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| include/net/tcp.h | 3 +++ |
| 1 file changed, 3 insertions(+) |
| |
| --- a/include/net/tcp.h |
| +++ b/include/net/tcp.h |
| @@ -1613,6 +1613,8 @@ static inline void tcp_init_send_head(st |
| sk->sk_send_head = NULL; |
| } |
| |
| +static inline void tcp_init_send_head(struct sock *sk); |
| + |
| /* write queue abstraction */ |
| static inline void tcp_write_queue_purge(struct sock *sk) |
| { |
| @@ -1621,6 +1623,7 @@ static inline void tcp_write_queue_purge |
| tcp_chrono_stop(sk, TCP_CHRONO_BUSY); |
| while ((skb = __skb_dequeue(&sk->sk_write_queue)) != NULL) |
| sk_wmem_free_skb(sk, skb); |
| + tcp_init_send_head(sk); |
| sk_mem_reclaim(sk); |
| tcp_clear_all_retrans_hints(tcp_sk(sk)); |
| tcp_init_send_head(sk); |