CVE-2016-8645: Improper Access Control (CWE-284)
value: The TCP stack in the Linux kernel before 4.8.10 mishandles skb truncation, which allows local users to cause a denial of service (system crash) via a crafted application that makes sendto system calls, related to net/ipv4/tcp_ipv4.c and net/ipv6/tcp_ipv6.c.
CWE description: The product does not restrict or incorrectly restricts access to a resource from an unauthorized actor.
CVSS v3 base score: 5.5
Published: 2016-11-28T03:59Z
Repository: https://github.com/torvalds/linux
Language: C
Files changed: include/net/tcp.h, net/ipv4/tcp_ipv4.c, net/ipv6/tcp_ipv6.c
Commit date: 2016-11-13 12:30:02 -0500
Commit message: tcp: take care of truncations done by sk_filter()

With syzkaller help, Marco Grassi found a bug in TCP stack,
crashing in tcp_collapse()

Root cause is that sk_filter() can truncate the incoming skb,
but TCP stack was not really expecting this to happen.
It probably was expecting a simple DROP or ACCEPT behavior.

We first need to make sure no part of TCP header could be removed.
Then we need to adjust TCP_SKB_CB(skb)->end_seq

Many thanks to syzkaller team and Marco for giving us a reproducer.

Signed-off-by: Eric Dumazet <edumazet@google.com>
Reported-by: Marco Grassi <marco.gra@gmail.com>
Reported-by: Vladis Dronov <vdronov@redhat.com>
Signed-off-by: David S. Miller <davem@davemloft.net>

Diff: diff --git a/include/net/tcp.h b/include/net/tcp.h
index 304a8e17bc87b2..123979fe12bf78 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -1218,10 +1218,11 @@ static inline void tcp_prequeue_init(struct tcp_sock *tp)
 	skb_queue_head_init(&tp->ucopy.prequeue);
 }
 
 bool tcp_prequeue(struct sock *sk, struct sk_buff *skb);
 bool tcp_add_backlog(struct sock *sk, struct sk_buff *skb);
+int tcp_filter(struct sock *sk, struct sk_buff *skb);
 
 #undef STATE_TRACE
 
 #ifdef STATE_TRACE
 static const char *statename[]={
diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c
index 61b7be303eeca3..2259114c7242c7 100644
--- a/net/ipv4/tcp_ipv4.c
+++ b/net/ipv4/tcp_ipv4.c
@@ -1562,10 +1562,25 @@ bool tcp_add_backlog(struct sock *sk, struct sk_buff *skb)
 	}
 	return false;
 }
 EXPORT_SYMBOL(tcp_add_backlog);
 
+int tcp_filter(struct sock *sk, struct sk_buff *skb)
+{
+	struct tcphdr *th = (struct tcphdr *)skb->data;
+	unsigned int eaten = skb->len;
+	int err;
+
+	err = sk_filter_trim_cap(sk, skb, th->doff * 4);
+	if (!err) {
+		eaten -= skb->len;
+		TCP_SKB_CB(skb)->end_seq -= eaten;
+	}
+	return err;
+}
+EXPORT_SYMBOL(tcp_filter);
+
 /*
  *	From tcp_input.c
  */
 
 int tcp_v4_rcv(struct sk_buff *skb)
@@ -1674,12 +1689,14 @@ int tcp_v4_rcv(struct sk_buff *skb)
 	if (tcp_v4_inbound_md5_hash(sk, skb))
 		goto discard_and_relse;
 
 	nf_reset(skb);
 
-	if (sk_filter(sk, skb))
+	if (tcp_filter(sk, skb))
 		goto discard_and_relse;
+	th = (const struct tcphdr *)skb->data;
+	iph = ip_hdr(skb);
 
 	skb->dev = NULL;
 
 	if (sk->sk_state == TCP_LISTEN) {
 		ret = tcp_v4_do_rcv(sk, skb);
diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c
index 6ca23c2e76f7b7..b9f1fee9a8862a 100644
--- a/net/ipv6/tcp_ipv6.c
+++ b/net/ipv6/tcp_ipv6.c
@@ -1227,11 +1227,11 @@ static int tcp_v6_do_rcv(struct sock *sk, struct sk_buff *skb)
 	 */
 
 	if (skb->protocol == htons(ETH_P_IP))
 		return tcp_v4_do_rcv(sk, skb);
 
-	if (sk_filter(sk, skb))
+	if (tcp_filter(sk, skb))
 		goto discard;
 
 	/*
 	 *	socket locking is here for SMP purposes as backlog rcv
 	 *	is currently called with bh processing disabled.
@@ -1455,12 +1455,14 @@ static int tcp_v6_rcv(struct sk_buff *skb)
 	tcp_v6_fill_cb(skb, hdr, th);
 
 	if (tcp_v6_inbound_md5_hash(sk, skb))
 		goto discard_and_relse;
 
-	if (sk_filter(sk, skb))
+	if (tcp_filter(sk, skb))
 		goto discard_and_relse;
+	th = (const struct tcphdr *)skb->data;
+	hdr = ipv6_hdr(skb);
 
 	skb->dev = NULL;
 
 	if (sk->sk_state == TCP_LISTEN) {
 		ret = tcp_v6_do_rcv(sk, skb);

Vulnerable code: 	if (sk_filter(sk, skb))
	if (sk_filter(sk, skb))
	if (sk_filter(sk, skb))
Fixed code: int tcp_filter(struct sock *sk, struct sk_buff *skb);
int tcp_filter(struct sock *sk, struct sk_buff *skb)
{
	struct tcphdr *th = (struct tcphdr *)skb->data;
	unsigned int eaten = skb->len;
	int err;

	err = sk_filter_trim_cap(sk, skb, th->doff * 4);
	if (!err) {
		eaten -= skb->len;
		TCP_SKB_CB(skb)->end_seq -= eaten;
	}
	return err;
}
EXPORT_SYMBOL(tcp_filter);

	if (tcp_filter(sk, skb))
	th = (const struct tcphdr *)skb->data;
	iph = ip_hdr(skb);
	if (tcp_filter(sk, skb))
	if (tcp_filter(sk, skb))
	th = (const struct tcphdr *)skb->data;
	hdr = ipv6_hdr(skb);