poc运行
在github找了一个poc
在target里
1 | git clone https://github.com/beraphin/CVE-2017-8890.git |
target里
然后回到host发现已经断下了(panic)
1 | gef➤ c |
通过打印堆栈和查看源码/分析汇编,我们知道kernel panic的原因是NULL pointer dereference at 0000000000000006。
关键代码
1 | if (!psf) { |
1 | static int ip_mc_leave_src(struct sock *sk, struct ip_mc_socklist *iml, |
1 | struct ip_sf_socklist { |
漏洞成因
patch
看资料和patch
https://bugzilla.redhat.com/show_bug.cgi?id=CVE-2017-8890
1 | The inet_csk_clone_lock function in net/ipv4/inet_connection_sock.c in the Linux kernel allows attackers to cause a denial of service (double free) or possibly have unspecified other impact by leveraging use of the accept system call. |
linux内核的net/ipv4/inet_connection_sock.c文件中的inet_csk_clone_lock函数允许攻击者利用accept system call去触发double free,造成拒绝服务攻击或者其他可能的影响。
一个没有特权的本地用户能够使用这个缺陷去触发系统内核内存损坏,导致系统崩溃。
patch
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=657831ffc38e30092a2d5f03d385d710eb88b09a
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=8b485ce69876c65db12ed390e7f9c0d2a64eff2c
1 | dccp/tcp: do not inherit mc_list from parent |
patch前的源码
https://elixir.bootlin.com/linux/v4.11.3/source/net/ipv4/inet_connection_sock.c
dccp/tcp: do not inherit mc_list from parent
根据patch推测,在parent对象free了之后,由于child对象直接继承parent对象的值,于是又得到了mc_list的地址,在后面再次被free。
使用understand阅读源码
使用understand
我导入的源码是kernel4.10
然后等它建立好索引后,搜索想看的函数,右键选择call by就可以自动绘制被调用图。
double free
mc_list对象创建
mc_list结构的定义如下,大小为0x30
1 | struct ip_mc_socklist { |
1 | gef➤ b ip_mc_join_group |
然后单步
mc_list对象释放
1 | sock_close -> sock_release() -> inet_release() -> ip_mc_drop_socket() |
1 | /* |
ip_mc_drop_socket 这个函数导致释放操作,该函数获取到mc_list对象后,最后调用kfree_rcu,该函数并不是真正的释放该对象,而是调用call_rcu将要删除的对象保存起来,并标记或者开始一个宽限期,等到cpu宽限期结束,会触发一个RCU软中断,再进行释放,如果有回调函数func,则进行回调函数处理流程,整个函数调用逻辑为:kfree_rcu -> … -> call_rcu -> … -> invoke_rcu_core -> RCU_SOFTIRQ -> rcu_process_callbacks -> … __rcu_reclaim
1 | b /build/linux-hwe-edge-gyUj63/linux-hwe-edge-4.10.0/net/ipv4/igmp.c:2612 |
accept
1 | /** |
在accecpt的时候,创建一个新的socket的,parent的所有field(除了ref_cnt)被拷贝给新生成的socket对象,包括mc_list指针的值,于是就有了多个指针指向同一块内存,从而在后面造成double free。
poc分析
1 | sockfd = socket(AF_INET, xx, IPPROTO_TCP); |
int socket(int af, int type, int protocol);
1) af 为地址族(Address Family),也就是 IP 地址类型,常用的有 AF_INET 和 AF_INET6。AF 是“Address Family”的简写,INET是“Inetnet”的简写。AF_INET 表示 IPv4 地址,例如 127.0.0.1;AF_INET6 表示 IPv6 地址,例如 1030::C9B4:FF12:48AA:1A2B。
你也可以使用PF前缀,PF是“Protocol Family”的简写,它和AF是一样的。例如,PF_INET 等价于 AF_INET,PF_INET6 等价于 AF_INET6。
2) type 为数据传输方式,常用的有 SOCK_STREAM 和 SOCK_DGRAM,
3) protocol 表示传输协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议。int setsockopt(int sock, int level, int optname, void *optval, socklen_t *optlen);
参数:
sock:将要被设置或者获取选项的套接字。
level:选项所在的协议层。
optname:需要访问的选项名。
optval:对于getsockopt(),指向返回选项值的缓冲。 对于setsockopt(),指向包含新选项值的缓冲。
optlen:对于getsockopt(),作为入口参数时,选项值的最大长度。 作为出口参数时,选项值的实际长度。 对于setsockopt(),现选项的长度。int bind(int sock, struct sockaddr *addr, socklen_t addrlen); //Linux
sock 为 socket 文件描述符,addr 为 sockaddr 结构体变量的指针,addrlen 为 addr 变量的大小,可由 sizeof() 计算得出。
http://c.biancheng.net/cpp/html/3033.htmlint listen(int sock, int backlog); //Linux
sock 为需要进入监听状态的套接字,backlog 为请求队列的最大长度。
所谓被动监听,是指当没有客户端请求时,套接字处于“睡眠”状态,只有当接收到客户端请求时,套接字才会被“唤醒”来响应请求。
注意:listen() 只是让套接字处于监听状态,并没有接收请求。接收请求需要使用 accept() 函数。int accept(int sock, struct sockaddr *addr, socklen_t *addrlen); //Linux
它的参数与 listen() 和 connect() 是相同的:sock 为服务器端套接字,addr 为 sockaddr_in 结构体变量,addrlen 为参数 addr 的长度,可由 sizeof() 求得。
accept() 返回一个新的套接字来和客户端通信,addr 保存了客户端的IP地址和端口号,而 sock 是服务器端的套接字,大家注意区分。后面和客户端通信时,要使用这个新生成的套接字,而不是原来服务器端的套接字。
漏洞利用
1 | + mc_list 在内核中以链表形式存在,通过第一个成员next_rcu指向下一个mc_list |
漏洞模型
commit
事实上类似的模式早就被commit过patch
https://github.com/torvalds/linux/commit/8b485ce69876c65db12ed390e7f9c0d2a64eff2c
所以多看commit很重要,可以学习挖洞的模式。
可以看一下说明,提到了double free,触发地点也很接近。
tcp: do not inherit fastopen_req from parent
1 | tcp: do not inherit fastopen_req from parent |
patch
1 | newicsk->icsk_ack.last_seg_size = skb->len - newtp->tcp_header_len; |
CVE-2017-9075
do not inherit ipv6_{mc|ac|fl}_list from parent
1 | sctp: do not inherit ipv6_{mc|ac|fl}_list from parent |
patch
1 | +++ b/net/sctp/ipv6.c |
CVE-2017-9076/CVE-2017-9077
ipv6/dccp: do not inherit ipv6_mc_list from parent
1 | Like commit 657831ffc38e ("dccp/tcp: do not inherit mc_list from parent") |
patch
1 | diff --git a/net/dccp/ipv6.c b/net/dccp/ipv6.c |
参考资料
https://2freeman.github.io/2018/01/06/CVE-2017-8890-internals.html
https://bbs.pediy.com/thread-226057.htm
http://www.freebuf.com/articles/terminal/160041.html