Introduction
根据漏洞描述 cve-2017-6074
是存在于 dccp
数据报拥塞控制协议实现中的 double free
漏洞,漏洞需要内核编译的时候开启 CONFIG_IP_DCCP
,
不过许多 linux 发行版都是默认开启的。影响范围可能对大于 2.6.18 的版本都有效,漏洞于2017年2月17日修复,patch 如下:
目前还未找到该漏洞有完整的分析文章,但是作者放出了完整的利用 poc.c 其中包含 smep & smap 的绕过,以及 sendmmsg 堆喷技巧,后面将根据作者给出的利用对漏洞进行分析,并学习其中的利用方式。
Root Cause
diff --git a/net/dccp/input.c b/net/dccp/input.c
index ba347184bda9..8fedc2d49770 100644
--- a/net/dccp/input.c
+++ b/net/dccp/input.c
@@ -606,7 +606,8 @@ int dccp_rcv_state_process(struct sock *sk, struct sk_buff *skb,
if (inet_csk(sk)->icsk_af_ops->conn_request(sk,
skb) < 0)
return 1;
- goto discard;
+ consume_skb(skb);
+ return 0;
}
if (dh->dccph_type == DCCP_PKT_RESET)
goto discard;
从漏洞细节描述结合漏洞修补代码,可以知道漏洞产生的原因是 socket_buff 对象被多次引用,没有正确释放造成的,漏洞补丁对 skb->user
占用,
而没有跳转到 discard 然后调用 __kfree_skb
。
net/core/skbuff.c#729
/**
* consume_skb - free an skbuff
* @skb: buffer to free
*
* Drop a ref to the buffer and free it if the usage count has hit zero
* Functions identically to kfree_skb, but kfree_skb assumes that the frame
* is being dropped after a failure and notes that
*/
void consume_skb(struct sk_buff *skb)
{
if (unlikely(!skb))
return;
if (likely(atomic_read(&skb->users) == 1))
smp_rmb();
else if (likely(!atomic_dec_and_test(&skb->users)))
return;
trace_consume_skb(skb);
__kfree_skb(skb);
}
那么 skb
对象在哪里产生的呢?光用源码阅读工具无法回溯出完整的函数调用链,经过一番搜索后,发现了用 ftrace 追踪
kernel 函数调用的方法,从下图可以看到 alloc_skb() 函数是由 dccp_connect() 调用,
后者最终是由 sys_connect 调用,那么整个调用链就如下所示:
用户态:connect()
-----------------------------------------------------------------------------------------------------------
内核态:SYSC_connect() --> inet_stream_conect() --> dccp_v6_connect() --> dccp_connect() --> __alloc_skb()
知道了对象如何产生,再来看看对象如何释放。修补的函数是 DCCP 协议栈 socket 状态为 DCCP_LISTEN 时对 DCCP_PKT_REQUEST
类型的请求包进行处理的函数。其中的 conn_reqeust
函数指针指向 dccp_v6_conn_request() 函数,根据漏洞公告的信息,
在该函数的处理中如果在 socket 上设置 IPV6_RECVPKTINFO,则 skb 地址会被保存在 ireq->pktopts
,那么其引用次数会增加一次,
若该函数返回成功,则会在之后的处理中 goto discard
被释放掉,这多余的释放会造成一个 double free。
net/dccp/ipv6.c#298
static int dccp_v6_conn_request(struct sock *sk, struct sk_buff *skb)
{
...
if (ipv6_opt_accepted(sk, skb, IP6CB(skb)) ||
np->rxopt.bits.rxinfo || np->rxopt.bits.rxoinfo ||
np->rxopt.bits.rxhlim || np->rxopt.bits.rxohlim) {
atomic_inc(&skb->users);
ireq->pktopts = skb;
}
...
Breif
漏洞的利用比较复杂,也可能是水平不够,不能完整的分析出每一步的作用,太菜了( 。。 抛去前面 sandbox 的设置, 单看后面的过程可以分为两步,第一步是绕过 smap & smep,作者提到这里用的方法是出自 Philip Pettersson 的 CVE-2016-8655 的利用。
TODO ..
第二步则是执行 commit_cred()
函数进行提权,这也是大多数提权程序最终生效的入口函数,不同的是,这里用的堆喷对象
是 sk_buff,也就是被多次引用造成漏洞的对象。而释放 skb 时调用的 skb_release_data 函数中存在调用回调函数的代码
static void skb_release_data(struct sk_buff *skb)
{
struct skb_shared_info *shinfo = skb_shinfo(skb);
...
if (shinfo->tx_flags & SKBTX_DEV_ZEROCOPY) {
struct ubuf_info *uarg;
uarg = shinfo->destructor_arg;
if (uarg->callback)
uarg->callback(uarg, true);
}
...
所以可以覆盖到 destructor_arg
变量触发回调函数时劫持控制流。
另外调试的时候遇到一个坑,我调试的内核版本为 4.4.0-62-generic,从官网下载了带符号的文件然后双机调试, 但是下断点的时候与 dccp 相关的函数都没有符号
这时可以直接在 target 里本地查看相关符号因为关闭的 kalsr
整体思路大概是这样,但具体怎么操作的细节、timer buffer
的作用都还没弄清楚。。希望后续再磨练一下可以分析出来。
Reference
ftrace: trace your kernel functions!
Linux kernel: CVE-2017-6074: DCCP double-free vulnerability (local root)