From: Eric Dumazet <eduma...@google.com>

3.12-stable review patch.  If anyone has any objections, please let me know.

===============

[ Upstream commit ac6e780070e30e4c35bd395acfe9191e6268bdd3 ]

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 <eduma...@google.com>
Reported-by: Marco Grassi <marco....@gmail.com>
Reported-by: Vladis Dronov <vdro...@redhat.com>
Signed-off-by: David S. Miller <da...@davemloft.net>
Signed-off-by: Jiri Slaby <jsl...@suse.cz>
---
 include/linux/filter.h |  6 +++++-
 include/net/tcp.h      |  1 +
 net/core/filter.c      | 10 +++++-----
 net/ipv4/tcp_ipv4.c    | 19 ++++++++++++++++++-
 net/ipv6/tcp_ipv6.c    |  6 ++++--
 5 files changed, 33 insertions(+), 9 deletions(-)

diff --git a/include/linux/filter.h b/include/linux/filter.h
index ff4e40cd45b1..264c1a440240 100644
--- a/include/linux/filter.h
+++ b/include/linux/filter.h
@@ -41,7 +41,11 @@ static inline unsigned int sk_filter_size(unsigned int 
proglen)
                   offsetof(struct sk_filter, insns[proglen]));
 }
 
-extern int sk_filter(struct sock *sk, struct sk_buff *skb);
+int sk_filter_trim_cap(struct sock *sk, struct sk_buff *skb, unsigned int cap);
+static inline int sk_filter(struct sock *sk, struct sk_buff *skb)
+{
+       return sk_filter_trim_cap(sk, skb, 1);
+}
 extern unsigned int sk_run_filter(const struct sk_buff *skb,
                                  const struct sock_filter *filter);
 extern int sk_unattached_filter_create(struct sk_filter **pfp,
diff --git a/include/net/tcp.h b/include/net/tcp.h
index 035135b43820..83d03f86e914 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -1049,6 +1049,7 @@ static inline void tcp_prequeue_init(struct tcp_sock *tp)
 }
 
 extern bool tcp_prequeue(struct sock *sk, struct sk_buff *skb);
+int tcp_filter(struct sock *sk, struct sk_buff *skb);
 
 #undef STATE_TRACE
 
diff --git a/net/core/filter.c b/net/core/filter.c
index ebce437678fc..5903efc408da 100644
--- a/net/core/filter.c
+++ b/net/core/filter.c
@@ -67,9 +67,10 @@ static inline void *load_pointer(const struct sk_buff *skb, 
int k,
 }
 
 /**
- *     sk_filter - run a packet through a socket filter
+ *     sk_filter_trim_cap - run a packet through a socket filter
  *     @sk: sock associated with &sk_buff
  *     @skb: buffer to filter
+ *     @cap: limit on how short the eBPF program may trim the packet
  *
  * Run the filter code and then cut skb->data to correct size returned by
  * sk_run_filter. If pkt_len is 0 we toss packet. If skb->len is smaller
@@ -78,7 +79,7 @@ static inline void *load_pointer(const struct sk_buff *skb, 
int k,
  * be accepted or -EPERM if the packet should be tossed.
  *
  */
-int sk_filter(struct sock *sk, struct sk_buff *skb)
+int sk_filter_trim_cap(struct sock *sk, struct sk_buff *skb, unsigned int cap)
 {
        int err;
        struct sk_filter *filter;
@@ -99,14 +100,13 @@ int sk_filter(struct sock *sk, struct sk_buff *skb)
        filter = rcu_dereference(sk->sk_filter);
        if (filter) {
                unsigned int pkt_len = SK_RUN_FILTER(filter, skb);
-
-               err = pkt_len ? pskb_trim(skb, pkt_len) : -EPERM;
+               err = pkt_len ? pskb_trim(skb, max(cap, pkt_len)) : -EPERM;
        }
        rcu_read_unlock();
 
        return err;
 }
-EXPORT_SYMBOL(sk_filter);
+EXPORT_SYMBOL(sk_filter_trim_cap);
 
 /**
  *     sk_run_filter - run a filter on a socket
diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c
index 4b2040762733..57f5bad5650c 100644
--- a/net/ipv4/tcp_ipv4.c
+++ b/net/ipv4/tcp_ipv4.c
@@ -1941,6 +1941,21 @@ bool tcp_prequeue(struct sock *sk, struct sk_buff *skb)
 }
 EXPORT_SYMBOL(tcp_prequeue);
 
+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
  */
@@ -2003,8 +2018,10 @@ process:
                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);
 
        sk_mark_napi_id(sk, skb);
        skb->dev = NULL;
diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c
index 0812b615885d..e5bafd576a13 100644
--- a/net/ipv6/tcp_ipv6.c
+++ b/net/ipv6/tcp_ipv6.c
@@ -1339,7 +1339,7 @@ static int tcp_v6_do_rcv(struct sock *sk, struct sk_buff 
*skb)
                goto discard;
 #endif
 
-       if (sk_filter(sk, skb))
+       if (tcp_filter(sk, skb))
                goto discard;
 
        /*
@@ -1509,8 +1509,10 @@ process:
        if (!xfrm6_policy_check(sk, XFRM_POLICY_IN, 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);
 
        sk_mark_napi_id(sk, skb);
        skb->dev = NULL;
-- 
2.10.2

Reply via email to