Linux内核AF_PACKET原生套接字误差(CVE-2020-14386)剖析

宣布时间 2020-09-22

误差配景


克日,Openwall社区上果真了一个Linux内核AF_PACKET原生套接字内存破损误差。凭证细节形貌,该误差泛起在net/packet/af_packet.c中,由整数溢出导致越界写,可以通过它举行权限提升。该误差危害评级为高,编号为CVE-2020-14386。


受影响产品缓和解步伐


1、受影响产品


该误差影响Linux刊行版高于4.6的内核版本,包括:

  • Ubuntu Bionic (18.04) and newer

  • Debian 9

  • Debian 10

  • CentOS 8/RHEL 8

2、缓解步伐


(1)修补系统

上游内核补丁如下:

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


(2)关闭CAP_NET_RAW功效

针对RHEL8,详细关闭办法如下:

# echo"user.max_user_namespaces=0" > /etc/sysctl.d/userns.conf

# sysctl -p/etc/sysctl.d/userns.conf


(3)针对一些受影响的容器产品,同样接纳关闭CAP_NET_RAW功效举行缓解

Kubernetes Pod清静战略:设置Pod清静战略以删除运行容器中的CAP_NET_RAW功效,参考链接:https://cloud.google.com/kubernetes-engine/docs/security-bulletins。


相关看法


1、AF_PACKET套接字


网络协议栈中,原始套接字是一个特殊的套接字类型,从实现上可以分为两类,一类为链路层原始套接字;另一类为网络层原始套接字。链路层原始套接字可直接用于吸收和发送链路层的MAC帧,在发送时需要挪用者自行结构和封装MAC首部。链路层原始套接字挪用socket()函数建设。第一个参数指定地址簇类型为AF_PACKET,第二个参数套接字类型为SOCK_RAW或SOCK_DGRAM,当类型指定为SOCK_RAW时,套接字吸收和发送的数据都是从MAC首部最先的。在发送时需要由挪用者从MAC首部最先结构和封装报文数据。


2、PACKET_MMAP


仅依赖AF_PACKET过滤数据包是很是低效的,内核又提供了PACKET_MMAP支持。PACKET_MMAP在内核空间中分派一块环形内核缓冲区,用户空间通过mmap将该内核缓冲区映射出来。收到的数据包拷贝到环形内核缓冲区中,用户层可以直接操作数据,通过内核空间和用户空间共享的缓冲区起到镌汰数据拷贝的作用,提高处置惩罚效率。


PACKET_MMAP实现历程


通过setsockopt()函数设置环形缓冲区,option参数设置为PACKET_RX_RING或PACKET_TX_RING。为了利便内核与用户层管理和交互环形缓冲区中的数据帧,内核界说了TPACKET_HEADER结构体,该结构体存储着一些元信息如套接字地址信息、时间戳以及环形缓冲区管理信息等。若是通过setsockopt()函数设置了PACKET_VNET_HDR选项,还需添加一个virtio_net_hdr结构体。一个数据帧包括两个部分,第一部分为TPACKET_HEADER,第二部分为Data,并且要包管页面临齐,如下图所示:

现在TPACKET_HEADER保存三个版本,每个版本长度略有差别。关于v1和v2,收发环形缓冲区用tpacket_req结构体管理,该结构体包括四个数据域:划分为内存块的巨细和数目、每个数据帧的巨细和数据帧总数。如下图所示:



捕获的frame被划分为多个block,每个block是一块物理上一连的内存区域,有tp_block_size/tp_frame_size个frame,block的总数是tp_block_nr。例如,tp_block_size = 4096,tp_frame_size = 2048,tp_block_nr = 4,tp_frame_nr = 8。获得的缓冲区结构如下图所示:



每个frame必需放在一个block中,每个block生涯整数个frame,也就是说一个frame不可跨越两个block。在用户层映射环形缓冲区可以直接使用mmap()函数。虽然环形缓冲区在内核中是由多个block组成的,可是映射后它们在用户空间中是一连的。


误差剖析


该误差详细泛起在tpacket_rcv()函数中,该函数是基于PACKET_MMAP的数据包吸收函数。详细功效实现如下代码所示:



行2226到行2228,若是sk_type为SOCK_DGRAM,体现不需要自行结构MAC首部,由内核填充,则macoff即是netoff,巨细为TPACKET_ALIGN(tp_hdr_len)+ 16 + tp_reserve。若是sk_type为SOCK_RAW,则进入行2230,体现需要自行结构MAC首部。行2231到行2233,首先盘算netoff,巨细为TPACKET_ALIGN(tp_hdrlen +(maclen < 16 ?16 : maclen)) + tp_reserve。行2234到行2237,若是设置了PACKET_VNET_HDR选项,还需加上一个virtio_net_hdr结构体的巨细,然后设置do_vnet为真。行2238,盘算macoff。


由于macoff、netoff以及maclen被界说为unsigned short类型,最大值为0xffff。而tp_reserve被界说为unsigned int类型,最大值为0xffffffff,并且巨细可以通过setsockopt()函数举行设置,如下代码所示:



因此,在盘算netoff时,可以通过控制tp_reserve造成整数溢出,进而盘算蜕化误的macoff。当执行到如下代码时:



行2287,挪用virtio_net_hdr_from_skb()函数从sk_buff中拷贝数据,该函数第二个参数为h.raw + macoff – sizeof(struct virtio_net_hdr),h.raw为tpacket_rcv_uhdr类型的指针,指向环形缓冲区的frame,由于macoff是可控的,可以让maoff小于sizeof(struct virtio_net_hdr),导致向前越界写,最多可写入sizeof(struct virtio_net_hdr)个字节。凭证提供的PoC,调试代码如下图所示:



rdx中存放着TPACKET_ALIGN(tp_hdrlen+(maclen < 16 ? 16 : maclen)),巨细为0x50。rbp+0x4e4处存放着po->tp_reserve,巨细为0x0000ffb4。相加后,整数上溢后,rdx为0x0004。当执行到越界会见时,详细如下:



R9存放着h.raw指针,rdx存放着macoff,virtio_net_hdr结构体巨细为0xa。如下图所示:



爆发内存会见过失,造成系统瓦解。


参考链接:


[1] https://blog.csdn.net/sinat_20184565/article/details/82788387

[2] https://www.openwall.com/lists/oss-security/2020/09/03/3

[3] https://elixir.bootlin.com/linux/v5.6/source/Documentation/networking/packet_mmap.txt

[4] https://sysdig.com/blog/cve-2020-14386-falco/

[5] https://bugzilla.redhat.com/show_bug.cgi?id=1875699#c9