Home Home > GIT Browse
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicolai Stange <nstange@suse.de>2018-08-09 13:13:21 +0200
committerMiroslav Benes <mbenes@suse.cz>2018-08-09 16:16:18 +0200
commite65054f129072731caa214057ae717cda908aca9 (patch)
tree53f351a99158511152206753892479c143069be2
parentbd113d8b6ae8a9d5bb463256923026051ee2697a (diff)
Fix for CVE-2018-5390 ('denial of service conditions with low rates of specially modified packets aka "SegmentSmack"')
Live patch for CVE-2018-5390. Upstream commits 72cd43ba64fc ("tcp: free batches of packets in tcp_prune_ofo_queue()") f4a3313d8e2c ("tcp: avoid collapses in tcp_prune_queue() if possible") 3d4bf93ac120 ("tcp: detect malicious patterns in tcp_collapse_ofo_queue()") 8541b21e781a ("tcp: call tcp_drop() from tcp_data_queue_ofo()") 58152ecbbcc6 ("tcp: add tcp_ooo_try_coalesce() helper") KLP: CVE-2018-5390 References: bsc#1102682 CVE-2018-5390 Signed-off-by: Nicolai Stange <nstange@suse.de> Signed-off-by: Miroslav Benes <mbenes@suse.cz>
-rw-r--r--bsc1102682/livepatch_bsc1102682.c908
-rw-r--r--bsc1102682/livepatch_bsc1102682.h15
-rw-r--r--bsc1102682/patched_funcs.csv2
3 files changed, 925 insertions, 0 deletions
diff --git a/bsc1102682/livepatch_bsc1102682.c b/bsc1102682/livepatch_bsc1102682.c
new file mode 100644
index 0000000..057d217
--- /dev/null
+++ b/bsc1102682/livepatch_bsc1102682.c
@@ -0,0 +1,908 @@
+/*
+ * livepatch_bsc1102682
+ *
+ * Fix for CVE-2018-5390, bsc#1102682
+ *
+ * Upstream commits:
+ * 72cd43ba64fc ("tcp: free batches of packets in tcp_prune_ofo_queue()")
+ * f4a3313d8e2c ("tcp: avoid collapses in tcp_prune_queue() if possible")
+ * 3d4bf93ac120 ("tcp: detect malicious patterns in tcp_collapse_ofo_queue()")
+ * 8541b21e781a ("tcp: call tcp_drop() from tcp_data_queue_ofo()")
+ * 58152ecbbcc6 ("tcp: add tcp_ooo_try_coalesce() helper")
+ *
+ * SLE12 commit:
+ * not affected
+ *
+ * SLE12-SP1 commit
+ * not affected
+ *
+ * SLE12-SP2 commit:
+ * not affected
+ *
+ * SLE12-SP3 commits:
+ * not affected
+ *
+ * SLE15 commits:
+ * ec3d22a4c081f5988fd20d088c68201f5196a5e7
+ * fe0ec2a50973fb4bf2c2cecd6f1efd90b5804739
+ * 8fc8cfda8b5c1af4e3e4b121737acb9f155d3b0c
+ * 6ee46ea3308017deb1f444dc2e1225c74e42af90
+ * ce20133201e35e2e1d3030112b8a257a51b0951f
+ * b01b19a87acb29b2e36b5a79dcbf140b7fb5c639
+ *
+ *
+ * Copyright (c) 2018 SUSE
+ * Author: Nicolai Stange <nstange@suse.de>
+ *
+ * Based on the original Linux kernel code. Other copyrights apply.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/kernel.h>
+#include <linux/kallsyms.h>
+#include <linux/tcp.h>
+#include <linux/snmp.h>
+#include <linux/rbtree.h>
+#include <net/sock.h>
+#include <net/tcp.h>
+#include "livepatch_bsc1102682.h"
+
+static int (*klp_sysctl_tcp_dsack);
+
+static void (*klp__tcp_ecn_check_ce)(struct tcp_sock *tp,
+ const struct sk_buff *skb);
+static void (*klp_tcp_drop)(struct sock *sk, struct sk_buff *skb);
+static void (*klp_tcp_dsack_extend)(struct sock *sk, u32 seq, u32 end_seq);
+static void (*klp_skb_condense)(struct sk_buff *skb);
+static void (*klp_sk_forced_mem_schedule)(struct sock *sk, int size);
+static int (*klp_tcp_queue_rcv)(struct sock *sk, struct sk_buff *skb,
+ int hdrlen, bool *fragstolen);
+static void (*klp_tcp_event_data_recv)(struct sock *sk, struct sk_buff *skb);
+static void (*klp_tcp_fin)(struct sock *sk);
+static bool (*klp_tcp_try_coalesce)(struct sock *sk, struct sk_buff *to,
+ struct sk_buff *from, bool *fragstolen);
+static void (*klp_tcp_collapse)(struct sock *sk, struct sk_buff_head *list,
+ struct rb_root *root, struct sk_buff *head,
+ struct sk_buff *tail, u32 start, u32 end);
+
+static struct {
+ char *name;
+ void **addr;
+} klp_funcs[] = {
+ { "sysctl_tcp_dsack", (void *)&klp_sysctl_tcp_dsack },
+ { "__tcp_ecn_check_ce", (void *)&klp__tcp_ecn_check_ce },
+ { "tcp_drop", (void *)&klp_tcp_drop },
+ { "tcp_dsack_extend", (void *)&klp_tcp_dsack_extend },
+ { "skb_condense", (void *)&klp_skb_condense },
+ { "sk_forced_mem_schedule", (void *)&klp_sk_forced_mem_schedule },
+ { "tcp_queue_rcv", (void *)&klp_tcp_queue_rcv },
+ { "tcp_event_data_recv", (void *)&klp_tcp_event_data_recv },
+ { "tcp_fin", (void *)&klp_tcp_fin },
+ { "tcp_try_coalesce", (void *)&klp_tcp_try_coalesce },
+ { "tcp_collapse", (void *)&klp_tcp_collapse },
+};
+
+
+/* from net/ipv4/tcp_input.c */
+/* inlined */
+static void klp_tcp_incr_quickack(struct sock *sk)
+{
+ struct inet_connection_sock *icsk = inet_csk(sk);
+ unsigned int quickacks = tcp_sk(sk)->rcv_wnd / (2 * icsk->icsk_ack.rcv_mss);
+
+ if (quickacks == 0)
+ quickacks = 2;
+ if (quickacks > icsk->icsk_ack.quick)
+ icsk->icsk_ack.quick = min(quickacks, TCP_MAX_QUICKACKS);
+}
+
+/* inlined */
+static void klp_tcp_enter_quickack_mode(struct sock *sk)
+{
+ struct inet_connection_sock *icsk = inet_csk(sk);
+ klp_tcp_incr_quickack(sk);
+ icsk->icsk_ack.pingpong = 0;
+ icsk->icsk_ack.ato = TCP_ATO_MIN;
+}
+
+/* inlined */
+static void klp_tcp_ecn_accept_cwr(struct tcp_sock *tp,
+ const struct sk_buff *skb)
+{
+ if (tcp_hdr(skb)->cwr)
+ tp->ecn_flags &= ~TCP_ECN_DEMAND_CWR;
+}
+
+/* inlined */
+static void klp_tcp_ecn_check_ce(struct tcp_sock *tp, const struct sk_buff *skb)
+{
+ if (tp->ecn_flags & TCP_ECN_OK)
+ klp__tcp_ecn_check_ce(tp, skb);
+}
+
+/* inlined */
+static int klp__tcp_grow_window(const struct sock *sk,
+ const struct sk_buff *skb)
+{
+ struct tcp_sock *tp = tcp_sk(sk);
+ /* Optimize this! */
+ int truesize = tcp_win_from_space(skb->truesize) >> 1;
+ int window = tcp_win_from_space(sysctl_tcp_rmem[2]) >> 1;
+
+ while (tp->rcv_ssthresh <= window) {
+ if (truesize <= skb->len)
+ return 2 * inet_csk(sk)->icsk_ack.rcv_mss;
+
+ truesize >>= 1;
+ window >>= 1;
+ }
+ return 0;
+}
+
+/* optimized */
+static void klp_tcp_grow_window(struct sock *sk, const struct sk_buff *skb)
+{
+ struct tcp_sock *tp = tcp_sk(sk);
+
+ /* Check #1 */
+ if (tp->rcv_ssthresh < tp->window_clamp &&
+ (int)tp->rcv_ssthresh < tcp_space(sk) &&
+ !tcp_under_memory_pressure(sk)) {
+ int incr;
+
+ /* Check #2. Increase window, if skb with such overhead
+ * will fit to rcvbuf in future.
+ */
+ if (tcp_win_from_space(skb->truesize) <= skb->len)
+ incr = 2 * tp->advmss;
+ else
+ incr = klp__tcp_grow_window(sk, skb);
+
+ if (incr) {
+ incr = max_t(int, incr, 2 * skb->len);
+ tp->rcv_ssthresh = min(tp->rcv_ssthresh + incr,
+ tp->window_clamp);
+ inet_csk(sk)->icsk_ack.quick |= 1;
+ }
+ }
+}
+
+/* inlined */
+static void klp_tcp_clamp_window(struct sock *sk)
+{
+ struct tcp_sock *tp = tcp_sk(sk);
+ struct inet_connection_sock *icsk = inet_csk(sk);
+
+ icsk->icsk_ack.quick = 0;
+
+ if (sk->sk_rcvbuf < sysctl_tcp_rmem[2] &&
+ !(sk->sk_userlocks & SOCK_RCVBUF_LOCK) &&
+ !tcp_under_memory_pressure(sk) &&
+ sk_memory_allocated(sk) < sk_prot_mem_limits(sk, 0)) {
+ sk->sk_rcvbuf = min(atomic_read(&sk->sk_rmem_alloc),
+ sysctl_tcp_rmem[2]);
+ }
+ if (atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf)
+ tp->rcv_ssthresh = min(tp->window_clamp, 2U * tp->advmss);
+}
+
+/* inlined */
+static void klp_tcp_rcv_nxt_update(struct tcp_sock *tp, u32 seq)
+{
+ u32 delta = seq - tp->rcv_nxt;
+
+ sock_owned_by_me((struct sock *)tp);
+ tp->bytes_received += delta;
+ tp->rcv_nxt = seq;
+}
+
+/* inlined */
+static inline bool klp_tcp_sack_extend(struct tcp_sack_block *sp, u32 seq,
+ u32 end_seq)
+{
+ if (!after(seq, sp->end_seq) && !after(sp->start_seq, end_seq)) {
+ if (before(seq, sp->start_seq))
+ sp->start_seq = seq;
+ if (after(end_seq, sp->end_seq))
+ sp->end_seq = end_seq;
+ return true;
+ }
+ return false;
+}
+
+/* partially inlined */
+static void klp_tcp_dsack_set(struct sock *sk, u32 seq, u32 end_seq)
+{
+ struct tcp_sock *tp = tcp_sk(sk);
+
+ if (tcp_is_sack(tp) && *klp_sysctl_tcp_dsack) {
+ int mib_idx;
+
+ if (before(seq, tp->rcv_nxt))
+ mib_idx = LINUX_MIB_TCPDSACKOLDSENT;
+ else
+ mib_idx = LINUX_MIB_TCPDSACKOFOSENT;
+
+ NET_INC_STATS(sock_net(sk), mib_idx);
+
+ tp->rx_opt.dsack = 1;
+ tp->duplicate_sack[0].start_seq = seq;
+ tp->duplicate_sack[0].end_seq = end_seq;
+ }
+}
+
+/* inlined */
+static void klp_tcp_sack_maybe_coalesce(struct tcp_sock *tp)
+{
+ int this_sack;
+ struct tcp_sack_block *sp = &tp->selective_acks[0];
+ struct tcp_sack_block *swalk = sp + 1;
+
+ /* See if the recent change to the first SACK eats into
+ * or hits the sequence space of other SACK blocks, if so coalesce.
+ */
+ for (this_sack = 1; this_sack < tp->rx_opt.num_sacks;) {
+ if (klp_tcp_sack_extend(sp, swalk->start_seq, swalk->end_seq)) {
+ int i;
+
+ /* Zap SWALK, by moving every further SACK up by one slot.
+ * Decrease num_sacks.
+ */
+ tp->rx_opt.num_sacks--;
+ for (i = this_sack; i < tp->rx_opt.num_sacks; i++)
+ sp[i] = sp[i + 1];
+ continue;
+ }
+ this_sack++, swalk++;
+ }
+}
+
+/* inlined */
+static void klp_tcp_sack_new_ofo_skb(struct sock *sk, u32 seq, u32 end_seq)
+{
+ struct tcp_sock *tp = tcp_sk(sk);
+ struct tcp_sack_block *sp = &tp->selective_acks[0];
+ int cur_sacks = tp->rx_opt.num_sacks;
+ int this_sack;
+
+ if (!cur_sacks)
+ goto new_sack;
+
+ for (this_sack = 0; this_sack < cur_sacks; this_sack++, sp++) {
+ if (klp_tcp_sack_extend(sp, seq, end_seq)) {
+ /* Rotate this_sack to the first one. */
+ for (; this_sack > 0; this_sack--, sp--)
+ swap(*sp, *(sp - 1));
+ if (cur_sacks > 1)
+ klp_tcp_sack_maybe_coalesce(tp);
+ return;
+ }
+ }
+
+ /* Could not find an adjacent existing SACK, build a new one,
+ * put it at the front, and shift everyone else down. We
+ * always know there is at least one SACK present already here.
+ *
+ * If the sack array is full, forget about the last one.
+ */
+ if (this_sack >= TCP_NUM_SACKS) {
+ this_sack--;
+ tp->rx_opt.num_sacks--;
+ sp--;
+ }
+ for (; this_sack > 0; this_sack--, sp--)
+ *sp = *(sp - 1);
+
+new_sack:
+ /* Build the new head SACK, and we're done. */
+ sp->start_seq = seq;
+ sp->end_seq = end_seq;
+ tp->rx_opt.num_sacks++;
+}
+
+/* inlined */
+static void klp_tcp_sack_remove(struct tcp_sock *tp)
+{
+ struct tcp_sack_block *sp = &tp->selective_acks[0];
+ int num_sacks = tp->rx_opt.num_sacks;
+ int this_sack;
+
+ /* Empty ofo queue, hence, all the SACKs are eaten. Clear. */
+ if (RB_EMPTY_ROOT(&tp->out_of_order_queue)) {
+ tp->rx_opt.num_sacks = 0;
+ return;
+ }
+
+ for (this_sack = 0; this_sack < num_sacks;) {
+ /* Check if the start of the sack is covered by RCV.NXT. */
+ if (!before(tp->rcv_nxt, sp->start_seq)) {
+ int i;
+
+ /* RCV.NXT must cover all the block! */
+ WARN_ON(before(tp->rcv_nxt, sp->end_seq));
+
+ /* Zap this SACK, by moving forward any other SACKS. */
+ for (i = this_sack+1; i < num_sacks; i++)
+ tp->selective_acks[i-1] = tp->selective_acks[i];
+ num_sacks--;
+ continue;
+ }
+ this_sack++;
+ sp++;
+ }
+ tp->rx_opt.num_sacks = num_sacks;
+}
+
+/* inlined */
+static void klp_tcp_ofo_queue(struct sock *sk)
+{
+ struct tcp_sock *tp = tcp_sk(sk);
+ __u32 dsack_high = tp->rcv_nxt;
+ bool fin, fragstolen, eaten;
+ struct sk_buff *skb, *tail;
+ struct rb_node *p;
+
+ p = rb_first(&tp->out_of_order_queue);
+ while (p) {
+ skb = rb_entry(p, struct sk_buff, rbnode);
+ if (after(TCP_SKB_CB(skb)->seq, tp->rcv_nxt))
+ break;
+
+ if (before(TCP_SKB_CB(skb)->seq, dsack_high)) {
+ __u32 dsack = dsack_high;
+ if (before(TCP_SKB_CB(skb)->end_seq, dsack_high))
+ dsack_high = TCP_SKB_CB(skb)->end_seq;
+ klp_tcp_dsack_extend(sk, TCP_SKB_CB(skb)->seq, dsack);
+ }
+ p = rb_next(p);
+ rb_erase(&skb->rbnode, &tp->out_of_order_queue);
+
+ if (unlikely(!after(TCP_SKB_CB(skb)->end_seq, tp->rcv_nxt))) {
+ SOCK_DEBUG(sk, "ofo packet was already received\n");
+ klp_tcp_drop(sk, skb);
+ continue;
+ }
+ SOCK_DEBUG(sk, "ofo requeuing : rcv_next %X seq %X - %X\n",
+ tp->rcv_nxt, TCP_SKB_CB(skb)->seq,
+ TCP_SKB_CB(skb)->end_seq);
+
+ tail = skb_peek_tail(&sk->sk_receive_queue);
+ eaten = tail && klp_tcp_try_coalesce(sk, tail, skb, &fragstolen);
+ klp_tcp_rcv_nxt_update(tp, TCP_SKB_CB(skb)->end_seq);
+ fin = TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN;
+ if (!eaten)
+ __skb_queue_tail(&sk->sk_receive_queue, skb);
+ else
+ kfree_skb_partial(skb, fragstolen);
+
+ if (unlikely(fin)) {
+ klp_tcp_fin(sk);
+ /* tcp_fin() purges tp->out_of_order_queue,
+ * so we must end this loop right now.
+ */
+ break;
+ }
+ }
+}
+
+/* inlined */
+static struct sk_buff *klp_tcp_skb_next(struct sk_buff *skb,
+ struct sk_buff_head *list)
+{
+ if (list)
+ return !skb_queue_is_last(list, skb) ? skb->next : NULL;
+
+ return rb_entry_safe(rb_next(&skb->rbnode), struct sk_buff, rbnode);
+}
+
+
+
+/* new */
+static bool klp_tcp_ooo_try_coalesce(struct sock *sk,
+ struct sk_buff *to,
+ struct sk_buff *from,
+ bool *fragstolen)
+{
+ bool res = klp_tcp_try_coalesce(sk, to, from, fragstolen);
+
+ /* In case tcp_drop() is called later, update to->gso_segs */
+ if (res) {
+ u32 gso_segs = max_t(u16, 1, skb_shinfo(to)->gso_segs) +
+ max_t(u16, 1, skb_shinfo(from)->gso_segs);
+
+ skb_shinfo(to)->gso_segs = min_t(u32, gso_segs, 0xFFFF);
+ }
+ return res;
+}
+
+static bool klp_tcp_prune_ofo_queue(struct sock *sk);
+static int klp_tcp_prune_queue(struct sock *sk);
+
+/* patched, calls tcp_prune_ofo_queue() and tcp_prune_queue() */
+int klp_tcp_try_rmem_schedule(struct sock *sk, struct sk_buff *skb,
+ unsigned int size)
+{
+ if (atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf ||
+ !sk_rmem_schedule(sk, skb, size)) {
+
+ if (klp_tcp_prune_queue(sk) < 0)
+ return -1;
+
+ while (!sk_rmem_schedule(sk, skb, size)) {
+ if (!klp_tcp_prune_ofo_queue(sk))
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/* patched, inlined */
+static void klp_tcp_data_queue_ofo(struct sock *sk, struct sk_buff *skb)
+{
+ struct tcp_sock *tp = tcp_sk(sk);
+ struct rb_node **p, *q, *parent;
+ struct sk_buff *skb1;
+ u32 seq, end_seq;
+ bool fragstolen;
+
+ klp_tcp_ecn_check_ce(tp, skb);
+
+ if (unlikely(klp_tcp_try_rmem_schedule(sk, skb, skb->truesize))) {
+ NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPOFODROP);
+ klp_tcp_drop(sk, skb);
+ return;
+ }
+
+ /* Disable header prediction. */
+ tp->pred_flags = 0;
+ inet_csk_schedule_ack(sk);
+
+ NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPOFOQUEUE);
+ seq = TCP_SKB_CB(skb)->seq;
+ end_seq = TCP_SKB_CB(skb)->end_seq;
+ SOCK_DEBUG(sk, "out of order segment: rcv_next %X seq %X - %X\n",
+ tp->rcv_nxt, seq, end_seq);
+
+ p = &tp->out_of_order_queue.rb_node;
+ if (RB_EMPTY_ROOT(&tp->out_of_order_queue)) {
+ /* Initial out of order segment, build 1 SACK. */
+ if (tcp_is_sack(tp)) {
+ tp->rx_opt.num_sacks = 1;
+ tp->selective_acks[0].start_seq = seq;
+ tp->selective_acks[0].end_seq = end_seq;
+ }
+ rb_link_node(&skb->rbnode, NULL, p);
+ rb_insert_color(&skb->rbnode, &tp->out_of_order_queue);
+ tp->ooo_last_skb = skb;
+ goto end;
+ }
+
+ /* In the typical case, we are adding an skb to the end of the list.
+ * Use of ooo_last_skb avoids the O(Log(N)) rbtree lookup.
+ */
+ /*
+ * Fix CVE-2018-5390
+ * -1 line, +2 lines
+ */
+ if (klp_tcp_ooo_try_coalesce(sk, tp->ooo_last_skb,
+ skb, &fragstolen)) {
+coalesce_done:
+ klp_tcp_grow_window(sk, skb);
+ kfree_skb_partial(skb, fragstolen);
+ skb = NULL;
+ goto add_sack;
+ }
+ /* Can avoid an rbtree lookup if we are adding skb after ooo_last_skb */
+ if (!before(seq, TCP_SKB_CB(tp->ooo_last_skb)->end_seq)) {
+ parent = &tp->ooo_last_skb->rbnode;
+ p = &parent->rb_right;
+ goto insert;
+ }
+
+ /* Find place to insert this segment. Handle overlaps on the way. */
+ parent = NULL;
+ while (*p) {
+ parent = *p;
+ skb1 = rb_entry(parent, struct sk_buff, rbnode);
+ if (before(seq, TCP_SKB_CB(skb1)->seq)) {
+ p = &parent->rb_left;
+ continue;
+ }
+ if (before(seq, TCP_SKB_CB(skb1)->end_seq)) {
+ if (!after(end_seq, TCP_SKB_CB(skb1)->end_seq)) {
+ /* All the bits are present. Drop. */
+ NET_INC_STATS(sock_net(sk),
+ LINUX_MIB_TCPOFOMERGE);
+ /*
+ * Fix CVE-2018-5390
+ * -1 line, +1 line
+ */
+ klp_tcp_drop(sk, skb);
+ skb = NULL;
+ klp_tcp_dsack_set(sk, seq, end_seq);
+ goto add_sack;
+ }
+ if (after(seq, TCP_SKB_CB(skb1)->seq)) {
+ /* Partial overlap. */
+ klp_tcp_dsack_set(sk, seq, TCP_SKB_CB(skb1)->end_seq);
+ } else {
+ /* skb's seq == skb1's seq and skb covers skb1.
+ * Replace skb1 with skb.
+ */
+ rb_replace_node(&skb1->rbnode, &skb->rbnode,
+ &tp->out_of_order_queue);
+ klp_tcp_dsack_extend(sk,
+ TCP_SKB_CB(skb1)->seq,
+ TCP_SKB_CB(skb1)->end_seq);
+ NET_INC_STATS(sock_net(sk),
+ LINUX_MIB_TCPOFOMERGE);
+ /*
+ * Fix CVE-2018-5390
+ * -1 line, +1 line
+ */
+ klp_tcp_drop(sk, skb1);
+ goto merge_right;
+ }
+ /*
+ * Fix CVE-2018-5390
+ * -1 line, +2 lines
+ */
+ } else if (klp_tcp_ooo_try_coalesce(sk, skb1,
+ skb, &fragstolen)) {
+ goto coalesce_done;
+ }
+ p = &parent->rb_right;
+ }
+insert:
+ /* Insert segment into RB tree. */
+ rb_link_node(&skb->rbnode, parent, p);
+ rb_insert_color(&skb->rbnode, &tp->out_of_order_queue);
+
+merge_right:
+ /* Remove other segments covered by skb. */
+ while ((q = rb_next(&skb->rbnode)) != NULL) {
+ skb1 = rb_entry(q, struct sk_buff, rbnode);
+
+ if (!after(end_seq, TCP_SKB_CB(skb1)->seq))
+ break;
+ if (before(end_seq, TCP_SKB_CB(skb1)->end_seq)) {
+ klp_tcp_dsack_extend(sk, TCP_SKB_CB(skb1)->seq,
+ end_seq);
+ break;
+ }
+ rb_erase(&skb1->rbnode, &tp->out_of_order_queue);
+ klp_tcp_dsack_extend(sk, TCP_SKB_CB(skb1)->seq,
+ TCP_SKB_CB(skb1)->end_seq);
+ NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPOFOMERGE);
+ klp_tcp_drop(sk, skb1);
+ }
+ /* If there is no skb after us, we are the last_skb ! */
+ if (!q)
+ tp->ooo_last_skb = skb;
+
+add_sack:
+ if (tcp_is_sack(tp))
+ klp_tcp_sack_new_ofo_skb(sk, seq, end_seq);
+end:
+ if (skb) {
+ klp_tcp_grow_window(sk, skb);
+ klp_skb_condense(skb);
+ skb_set_owner_r(skb, sk);
+ }
+}
+
+/* patched, calls tcp_data_queue_ofo() */
+void klp_tcp_data_queue(struct sock *sk, struct sk_buff *skb)
+{
+ struct tcp_sock *tp = tcp_sk(sk);
+ bool fragstolen;
+ int eaten;
+
+ if (TCP_SKB_CB(skb)->seq == TCP_SKB_CB(skb)->end_seq) {
+ __kfree_skb(skb);
+ return;
+ }
+ skb_dst_drop(skb);
+ __skb_pull(skb, tcp_hdr(skb)->doff * 4);
+
+ klp_tcp_ecn_accept_cwr(tp, skb);
+
+ tp->rx_opt.dsack = 0;
+
+ /* Queue data for delivery to the user.
+ * Packets in sequence go to the receive queue.
+ * Out of sequence packets to the out_of_order_queue.
+ */
+ if (TCP_SKB_CB(skb)->seq == tp->rcv_nxt) {
+ if (tcp_receive_window(tp) == 0)
+ goto out_of_window;
+
+ /* Ok. In sequence. In window. */
+queue_and_out:
+ if (skb_queue_len(&sk->sk_receive_queue) == 0)
+ klp_sk_forced_mem_schedule(sk, skb->truesize);
+ else if (klp_tcp_try_rmem_schedule(sk, skb, skb->truesize))
+ goto drop;
+
+ eaten = klp_tcp_queue_rcv(sk, skb, 0, &fragstolen);
+ klp_tcp_rcv_nxt_update(tp, TCP_SKB_CB(skb)->end_seq);
+ if (skb->len)
+ klp_tcp_event_data_recv(sk, skb);
+ if (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN)
+ klp_tcp_fin(sk);
+
+ if (!RB_EMPTY_ROOT(&tp->out_of_order_queue)) {
+ klp_tcp_ofo_queue(sk);
+
+ /* RFC2581. 4.2. SHOULD send immediate ACK, when
+ * gap in queue is filled.
+ */
+ if (RB_EMPTY_ROOT(&tp->out_of_order_queue))
+ inet_csk(sk)->icsk_ack.pingpong = 0;
+ }
+
+ if (tp->rx_opt.num_sacks)
+ klp_tcp_sack_remove(tp);
+
+ tcp_fast_path_check(sk);
+
+ if (eaten > 0)
+ kfree_skb_partial(skb, fragstolen);
+ if (!sock_flag(sk, SOCK_DEAD))
+ sk->sk_data_ready(sk);
+ return;
+ }
+
+ if (!after(TCP_SKB_CB(skb)->end_seq, tp->rcv_nxt)) {
+ /* A retransmit, 2nd most common case. Force an immediate ack. */
+ NET_INC_STATS(sock_net(sk), LINUX_MIB_DELAYEDACKLOST);
+ klp_tcp_dsack_set(sk, TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq);
+
+out_of_window:
+ klp_tcp_enter_quickack_mode(sk);
+ inet_csk_schedule_ack(sk);
+drop:
+ klp_tcp_drop(sk, skb);
+ return;
+ }
+
+ /* Out of window. F.e. zero window probe. */
+ if (!before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt + tcp_receive_window(tp)))
+ goto out_of_window;
+
+ klp_tcp_enter_quickack_mode(sk);
+
+ if (before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt)) {
+ /* Partial packet, seq < rcv_next < end_seq */
+ SOCK_DEBUG(sk, "partial packet: rcv_next %X seq %X - %X\n",
+ tp->rcv_nxt, TCP_SKB_CB(skb)->seq,
+ TCP_SKB_CB(skb)->end_seq);
+
+ klp_tcp_dsack_set(sk, TCP_SKB_CB(skb)->seq, tp->rcv_nxt);
+
+ /* If window is closed, drop tail of packet. But after
+ * remembering D-SACK for its head made in previous line.
+ */
+ if (!tcp_receive_window(tp))
+ goto out_of_window;
+ goto queue_and_out;
+ }
+
+ klp_tcp_data_queue_ofo(sk, skb);
+}
+
+/* patched, inlined, caller tcp_prune_queue() also patched */
+static void klp_tcp_collapse_ofo_queue(struct sock *sk)
+{
+ struct tcp_sock *tp = tcp_sk(sk);
+ /*
+ * Fix CVE-2018-5390
+ * +1 line
+ */
+ u32 range_truesize, sum_tiny = 0;
+ struct sk_buff *skb, *head;
+ struct rb_node *p;
+ u32 start, end;
+
+ p = rb_first(&tp->out_of_order_queue);
+ skb = rb_entry_safe(p, struct sk_buff, rbnode);
+new_range:
+ if (!skb) {
+ p = rb_last(&tp->out_of_order_queue);
+ /* Note: This is possible p is NULL here. We do not
+ * use rb_entry_safe(), as ooo_last_skb is valid only
+ * if rbtree is not empty.
+ */
+ tp->ooo_last_skb = rb_entry(p, struct sk_buff, rbnode);
+ return;
+ }
+ start = TCP_SKB_CB(skb)->seq;
+ end = TCP_SKB_CB(skb)->end_seq;
+ /*
+ * Fix CVE-2018-5390
+ * +1 line
+ */
+ range_truesize = skb->truesize;
+
+ for (head = skb;;) {
+ skb = klp_tcp_skb_next(skb, NULL);
+
+ /* Range is terminated when we see a gap or when
+ * we are at the queue end.
+ */
+ if (!skb ||
+ after(TCP_SKB_CB(skb)->seq, end) ||
+ before(TCP_SKB_CB(skb)->end_seq, start)) {
+ /*
+ * Fix CVE-2018-5390
+ * -2 lines, +10 lines
+ */
+ /* Do not attempt collapsing tiny skbs */
+ if (range_truesize != head->truesize ||
+ end - start >= SKB_WITH_OVERHEAD(SK_MEM_QUANTUM)) {
+ klp_tcp_collapse(sk, NULL, &tp->out_of_order_queue,
+ head, skb, start, end);
+ } else {
+ sum_tiny += range_truesize;
+ if (sum_tiny > sk->sk_rcvbuf >> 3)
+ return;
+ }
+ goto new_range;
+ }
+
+ /*
+ * Fix CVE-2018-5390
+ * +1 line
+ */
+ range_truesize += skb->truesize;
+ if (unlikely(before(TCP_SKB_CB(skb)->seq, start)))
+ start = TCP_SKB_CB(skb)->seq;
+ if (after(TCP_SKB_CB(skb)->end_seq, end))
+ end = TCP_SKB_CB(skb)->end_seq;
+ }
+}
+
+/* patched, optimized */
+static bool klp_tcp_prune_ofo_queue(struct sock *sk)
+{
+ struct tcp_sock *tp = tcp_sk(sk);
+ struct rb_node *node, *prev;
+ /*
+ * Fix CVE-2018-5390
+ * +1 line
+ */
+ int goal;
+
+ if (RB_EMPTY_ROOT(&tp->out_of_order_queue))
+ return false;
+
+ NET_INC_STATS(sock_net(sk), LINUX_MIB_OFOPRUNED);
+ /*
+ * Fix CVE-2018-5390
+ * +1 line
+ */
+ goal = sk->sk_rcvbuf >> 3;
+ node = &tp->ooo_last_skb->rbnode;
+ do {
+ prev = rb_prev(node);
+ rb_erase(node, &tp->out_of_order_queue);
+ /*
+ * Fix CVE-2018-5390
+ * +1 line
+ */
+ goal -= rb_entry(node, struct sk_buff, rbnode)->truesize;
+ klp_tcp_drop(sk, rb_entry(node, struct sk_buff, rbnode));
+ /*
+ * Fix CVE-2018-5390
+ * -4 lines, +7 lines
+ */
+ if (!prev || goal <= 0) {
+ sk_mem_reclaim(sk);
+ if (atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf &&
+ !tcp_under_memory_pressure(sk))
+ break;
+ goal = sk->sk_rcvbuf >> 3;
+ }
+ node = prev;
+ } while (node);
+ tp->ooo_last_skb = rb_entry(prev, struct sk_buff, rbnode);
+
+ /* Reset SACK state. A conforming SACK implementation will
+ * do the same at a timeout based retransmit. When a connection
+ * is in a sad state like this, we care only about integrity
+ * of the connection not performance.
+ */
+ if (tp->rx_opt.sack_ok)
+ tcp_sack_reset(&tp->rx_opt);
+ return true;
+}
+
+/* patched, inlined */
+static int klp_tcp_prune_queue(struct sock *sk)
+{
+ struct tcp_sock *tp = tcp_sk(sk);
+
+ SOCK_DEBUG(sk, "prune_queue: c=%x\n", tp->copied_seq);
+
+ NET_INC_STATS(sock_net(sk), LINUX_MIB_PRUNECALLED);
+
+ if (atomic_read(&sk->sk_rmem_alloc) >= sk->sk_rcvbuf)
+ klp_tcp_clamp_window(sk);
+ else if (tcp_under_memory_pressure(sk))
+ tp->rcv_ssthresh = min(tp->rcv_ssthresh, 4U * tp->advmss);
+
+ /*
+ * Fix CVE-2018-5390
+ * +3 lines
+ */
+ if (atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf)
+ return 0;
+
+ klp_tcp_collapse_ofo_queue(sk);
+ if (!skb_queue_empty(&sk->sk_receive_queue))
+ klp_tcp_collapse(sk, &sk->sk_receive_queue, NULL,
+ skb_peek(&sk->sk_receive_queue),
+ NULL,
+ tp->copied_seq, tp->rcv_nxt);
+ sk_mem_reclaim(sk);
+
+ if (atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf)
+ return 0;
+
+ /* Collapsing did not help, destructive actions follow.
+ * This must not ever occur. */
+
+ klp_tcp_prune_ofo_queue(sk);
+
+ if (atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf)
+ return 0;
+
+ /* If we are really being abused, tell the caller to silently
+ * drop receive data on the floor. It will get retransmitted
+ * and hopefully then we'll have sufficient space.
+ */
+ NET_INC_STATS(sock_net(sk), LINUX_MIB_RCVPRUNED);
+
+ /* Massive buffer overcommit. */
+ tp->pred_flags = 0;
+ return -1;
+}
+
+
+
+static int livepatch_bsc1102682_kallsyms(void)
+{
+ unsigned long addr;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(klp_funcs); i++) {
+ /* mod_find_symname would be nice, but it is not exported */
+ addr = kallsyms_lookup_name(klp_funcs[i].name);
+ if (!addr) {
+ pr_err("livepatch: symbol %s not resolved\n",
+ klp_funcs[i].name);
+ return -ENOENT;
+ }
+
+ *(klp_funcs[i].addr) = (void *)addr;
+ }
+
+ return 0;
+}
+
+int livepatch_bsc1102682_init(void)
+{
+ return livepatch_bsc1102682_kallsyms();
+}
diff --git a/bsc1102682/livepatch_bsc1102682.h b/bsc1102682/livepatch_bsc1102682.h
new file mode 100644
index 0000000..7834968
--- /dev/null
+++ b/bsc1102682/livepatch_bsc1102682.h
@@ -0,0 +1,15 @@
+#ifndef _LIVEPATCH_BSC1102682_H
+#define _LIVEPATCH_BSC1102682_H
+
+int livepatch_bsc1102682_init(void);
+static inline void livepatch_bsc1102682_cleanup(void) {}
+
+
+struct sock;
+struct sk_buff;
+
+int klp_tcp_try_rmem_schedule(struct sock *sk, struct sk_buff *skb,
+ unsigned int size);
+void klp_tcp_data_queue(struct sock *sk, struct sk_buff *skb);
+
+#endif /* _LIVEPATCH_BSC1102682_H */
diff --git a/bsc1102682/patched_funcs.csv b/bsc1102682/patched_funcs.csv
new file mode 100644
index 0000000..b4e5f92
--- /dev/null
+++ b/bsc1102682/patched_funcs.csv
@@ -0,0 +1,2 @@
+vmlinux tcp_try_rmem_schedule klp_tcp_try_rmem_schedule
+vmlinux tcp_data_queue klp_tcp_data_queue