Linux内核TCP漏洞预警

发布时间 2018-08-08

漏洞编号和级别

 

CVE-2018-5390,高危,厂商自评:7.5,官方未评定

 

影响版本

 

漏洞影响Linux kernel 4.9及以上版本。因为Linux内核的广泛应用,漏洞会影响软、硬件厂商,包括亚马逊、AppleUbuntuZyXEL等。

 

更多受影响的网络设备厂商、PC和服务器厂商、手机厂商、操作系统厂商请参考

https://access.redhat.com/articles/3553061#affected-products-2

 

漏洞概述

 

研究人员发现Linux kernelTCP漏洞(SegmentSmackCVE-2018-5390),攻击者利用该漏洞可以以很小的流量远程发起DoS攻击。

 

研究人员发现,对每个进入的包,tcp_collapse_ofo_queue()tcp_prune_ofo_queue()的调用成本很高,会导致DoS攻击。

 

攻击者可以使用修改过是数据包来进行代价较大的调用,这会让带宽较小的网络中系统的CPU利用率达到饱和状态,导致DoS攻击。在最坏情况下,2k个包每秒的流量就可以导致系统拒绝服务。攻击会使系统CPU处于满负荷状态,同时网络包处理会有很大的延迟。

 

因为DoS攻击需要到开放、可达端口的双向TCP session,所以用伪造的IP地址不能发起此类攻击。

 

漏洞验证

 

目前没有poc发布。Redhat提供了使用4个流进行攻击的结果,4CPU内核的完全饱和,以及网络数据包处理的延迟:

 

$ top

%Cpu25 :  0.0 us,  0.0 sy,  0.0 ni,  1.4 id,  0.0 wa,  0.0 hi, 98.5 si,  0.0 st

%Cpu26 :  0.0 us,  0.0 sy,  0.0 ni,  1.4 id,  0.0 wa,  0.0 hi, 98.6 si,  0.0 st

%Cpu28 :  0.0 us,  0.3 sy,  0.0 ni,  0.7 id,  0.0 wa,  0.0 hi, 99.0 si,  0.0 st

%Cpu30 :  0.0 us,  0.0 sy,  0.0 ni,  1.4 id,  0.0 wa,  0.0 hi, 98.6 si,  0.0 st

   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND

   141 root      20   0       0      0      0 R  97.3  0.0   1:16.33 ksoftirqd/26

   151 root      20   0       0      0      0 R  97.3  0.0   1:16.68 ksoftirqd/28

   136 root      20   0       0      0      0 R  97.0  0.0   0:39.09 ksoftirqd/25

   161 root      20   0       0      0      0 R  97.0  0.0   1:16.48 ksoftirqd/30

 

修复建议:

 

截止目前,除了运行修复的内核外,还没有其他缓解的方法,也没有攻击PoC发布。

为了解决该漏洞,Linux kernel开发人员已经发布了补丁。

 

https://git.kernel.org/pub/scm/linux/kernel/git/davem/net.git/commit/?id=1a4f14bab1868b443f0dd3c55b689a478f82e72e

 

diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c

index 6bade06aaf72..3bcd30a2ba06 100644

--- a/net/ipv4/tcp_input.c

+++ b/net/ipv4/tcp_input.c

@@ -4358,6 +4358,23 @@ static bool tcp_try_coalesce(struct sock *sk,

return true;

}

+static bool tcp_ooo_try_coalesce(struct sock *sk,

+     struct sk_buff *to,

+     struct sk_buff *from,

+     bool *fragstolen)

+{

+ bool res = 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 void tcp_drop(struct sock *sk, struct sk_buff *skb)

{

sk_drops_add(sk, skb);

@@ -4481,8 +4498,8 @@ static void tcp_data_queue_ofo(struct sock *sk, struct sk_buff *skb)

/* 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.

*/

- if (tcp_try_coalesce(sk, tp->ooo_last_skb,

-     skb, &fragstolen)) {

+ if (tcp_ooo_try_coalesce(sk, tp->ooo_last_skb,

+ skb, &fragstolen)) {

coalesce_done:

tcp_grow_window(sk, skb);

kfree_skb_partial(skb, fragstolen);

@@ -4510,7 +4527,7 @@ coalesce_done:

/* All the bits are present. Drop. */

NET_INC_STATS(sock_net(sk),

LINUX_MIB_TCPOFOMERGE);

- __kfree_skb(skb);

+ tcp_drop(sk, skb);

skb = NULL;

tcp_dsack_set(sk, seq, end_seq);

goto add_sack;

@@ -4529,11 +4546,11 @@ coalesce_done:

TCP_SKB_CB(skb1)->end_seq);

NET_INC_STATS(sock_net(sk),

LINUX_MIB_TCPOFOMERGE);

- __kfree_skb(skb1);

+ tcp_drop(sk, skb1);

goto merge_right;

}

- } else if (tcp_try_coalesce(sk, skb1,

-    skb, &fragstolen)) {

+ } else if (tcp_ooo_try_coalesce(sk, skb1,

+ skb, &fragstolen)) {

goto coalesce_done;

}

p = &parent->rb_right;

@@ -4902,6 +4919,7 @@ end:

static void tcp_collapse_ofo_queue(struct sock *sk)

{

struct tcp_sock *tp = tcp_sk(sk);

+ u32 range_truesize, sum_tiny = 0;

struct sk_buff *skb, *head;

u32 start, end;

@@ -4913,6 +4931,7 @@ new_range:

}

start = TCP_SKB_CB(skb)->seq;

end = TCP_SKB_CB(skb)->end_seq;

+ range_truesize = skb->truesize;

for (head = skb;;) {

skb = skb_rb_next(skb);

@@ -4923,11 +4942,20 @@ new_range:

if (!skb ||

after(TCP_SKB_CB(skb)->seq, end) ||

before(TCP_SKB_CB(skb)->end_seq, start)) {

- tcp_collapse(sk, NULL, &tp->out_of_order_queue,

-     head, skb, start, end);

+ /* Do not attempt collapsing tiny skbs */

+ if (range_truesize != head->truesize ||

+    end - start >= SKB_WITH_OVERHEAD(SK_MEM_QUANTUM)) {

+ 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;

}

+ 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))

@@ -4942,6 +4970,7 @@ new_range:

* 2) not add too big latencies if thousands of packets sit there.

*    (But if application shrinks SO_RCVBUF, we could still end up

*     freeing whole queue here)

+ * 3) Drop at least 12.5 % of sk_rcvbuf to avoid malicious attacks.

*

* Return true if queue has shrunk.

*/

@@ -4949,20 +4978,26 @@ static bool tcp_prune_ofo_queue(struct sock *sk)

{

struct tcp_sock *tp = tcp_sk(sk);

struct rb_node *node, *prev;

+ int goal;

if (RB_EMPTY_ROOT(&tp->out_of_order_queue))

return false;

NET_INC_STATS(sock_net(sk), LINUX_MIB_OFOPRUNED);

+ goal = sk->sk_rcvbuf >> 3;

node = &tp->ooo_last_skb->rbnode;

do {

prev = rb_prev(node);

rb_erase(node, &tp->out_of_order_queue);

+ goal -= rb_to_skb(node)->truesize;

tcp_drop(sk, rb_to_skb(node));

- sk_mem_reclaim(sk);

- if (atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf &&

-    !tcp_under_memory_pressure(sk))

- break;

+ 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_to_skb(prev);

@@ -4997,6 +5032,9 @@ static int tcp_prune_queue(struct sock *sk)

else if (tcp_under_memory_pressure(sk))

tp->rcv_ssthresh = min(tp->rcv_ssthresh, 4U * tp->advmss);

+ if (atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf)

+ return 0;

+

tcp_collapse_ofo_queue(sk);

if (!skb_queue_empty(&sk->sk_receive_queue))

tcp_collapse(sk, &sk->sk_receive_queue, NULL,

 

linux厂商:

 

Redhat

https://access.redhat.com/security/cve/cve-2018-5390

Ubuntu

https://usn.ubuntu.com/3732-1/

https://usn.ubuntu.com/3732-2/

Debian

https://www.debian.org/security/2018/dsa-4266

 

参考链接:

 

www.kb.cert.org/vuls/id/962459

https://www.securityfocus.com/bid/104976/info

https://www.spinics.net/lists/netdev/msg514742.html

https://git.kernel.org/pub/scm/linux/kernel/git/davem/net.git/commit/?id=1a4f14bab1868b443f0dd3c55b689a478f82e72e