cve-2017-6074 briefly analyze

Introduction

根据漏洞描述 cve-2017-6074 是存在于 dccp 数据报拥塞控制协议实现中的 double free 漏洞,漏洞需要内核编译的时候开启 CONFIG_IP_DCCP, 不过许多 linux 发行版都是默认开启的。影响范围可能对大于 2.6.18 的版本都有效,漏洞于2017年2月17日修复,patch 如下:

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=5edabca9d4cff7f1f2b68f0bac55ef99d9798ba4

目前还未找到该漏洞有完整的分析文章,但是作者放出了完整的利用 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)

利用漏洞CVE-2017-6074获取root权限