Unit42研究人员在Linux内核源代码中发现内存损坏漏洞,漏洞CVE编号为CVE-2020-14386。攻击者可以利用此漏洞将权限从特权用户提升到Linux系统的根用户。技术细节该漏洞来源于net/packet/af_packet.c文件中的tpacket_rcv函数,由算法问题引起。此漏洞于2008年7月引入(提交8913336),自2016年2月(提交58d19b19cd99)以来已触发内存损坏。许多开发人员已尝试修复此漏洞,但建议的补丁均不足以防止内存损坏。要触发此漏洞,需要创建一个原始数据包(AF_PACKET域,SOCK_RAW类型),并将TPACKET_V2环形缓存和PACKET_RESERVE作为特定值。净空是用户指定大小的缓冲区,在环形缓冲区接收每个数据包的实际数据之前分配。这个值可以通过setsockopt系统调用在用户空间中设置:图1.Setsockopt设置–PACKET_RESERVE如图1所示,它会检查该值是否小于INT_MAX。该值是patch(https://lore.kernel.org/patchwork/patch/784412/)中新增的,防止packet_set_ring中最小帧长计算溢出。然后它验证页面是否分配给传入或传出环形缓存。这样做的目的是为了防止tp_reserve字段和环形缓冲区之间的不连续性。设置tp_reserve值后,可以通过包含PACKET_RX_RING的setsockopt系统调用触发环形缓冲区的分配:图2.来自手动数据包-PACKET_RX_RING选项。这是在packet_set_ring函数中实现的。在环形缓冲区分配之前,会对从用户空间接收到的tpacket_req结构进行多次检查:图3.packet_set_ring函数中的安全检查从图3可以看出,首先计算最小帧大小,然后与帧进行比较size从用户空间接收到的值进行比较和验证。检查以确保tpacket标头结构中的每个帧与tp_reserve字节数之间有空间。全部检查完成后,ringcache本身会通过alloc_pg_vec调用进行分配:图4.在packet_set_ring函数中调用ringcache分配函数如上图所示,块大小(blocksize)由用户空间。alloc_pg_vec函数会分配pg_vec数组,然后通过alloc_one_pg_vec_page函数分配给各个区块链:图5.alloc_pg_vec实现alloc_one_pg_vec_page函数会使用__get_free_pages分配块页:图6.alloc_one_pg_vec_page实现块分配后,pg_vec数组会被存储在packet_sock结构中嵌入的packet_ring_buffer结构中。当接口收到数据包时,绑定到tpacket_rcv函数的套接字、数据包数据和TPACKET元数据将被写入环形缓存。漏洞图7是tpacket_rcv函数的实现。首先调用skb_network_offset将接收到的数据包的网络头的偏移值提取到malen中。在此示例中,大小为14个字节,这是以太网标头的大小。之后,根据TPACKET标头、malen和tp_reserve值计算netoff。但是计算过程可能会溢出,因为tp_reserve的类型是unsignedint,netoff的类型是unsignedshort,tp_reserve的值唯一的限制是小于INT_MAX。图7.tpacket_rcv中的算术计算如图7所示,如果packet中设置了PACKET_vnet_HDR,则添加sizeof(structvirtio_net_hdr)。最后计算出以太网头的偏移量,保存在macoff中。如图8所示,virtio_net_hdr结构将使用virtio_net_hdr_from_skb函数加载到环形缓存中。h.raw指向环形缓冲区中当前空闲的帧。图8.在tpacket_rcv中调用virtio_net_hdr_from_skb函数研究人员设想可以利用这个溢出将netoff改成更小的值,这样macoff就可以接收到大于块大小的值并尝试写入缓存.但是有下面的检查,所以无法实现:图9.tpacket_rcv函数中的另一个检查但是这个检查不足以防止内存损坏,因为macoff仍然可以通过溢出netoff减小到更小的值。比如把macoff改成小于10字节的sizeof(structvirtio_net_hdr),然后用virtio_net_hdr_from_skb写入缓存边界。原语通过控制macoff的值,virtio_net_hdr结构可以在一个受控的偏移量处被初始化。virtio_net_hdr_from_skb函数会先将整个struct置零,然后根据skb结构体初始化结构体中的所有字段。图10.virtio_net_hdr_from_skb函数的实现,但可以将skb设置为仅将零写入结构。因此,可以在__get_free_pages分配中清零1-10个字节。在没有任何堆操作技巧的情况下立即使内核崩溃。触发该漏洞的PoC代码可参考:https://www.openwall.com/lists/oss-security/2020/09/03/3漏洞利用漏洞利用过程可参考:https:///unit42.paloaltonetworks.com/cve-2020-14386/patch研究人员提出的补丁可以在:https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=acf69c946233259ab4d64f8869d4037a198c7f06图11.Researchers提出的patch的思路是,如果netoff的类型从unsignedshort改为unsignedint,可以检查是否会超过USHRT_MAX,如果超过了,丢弃包,以防止进一步使用。综上所述,研究人员其实很惊讶这样一个简单的算术安全问题仍然存在于Linux内核中,而且之前没有被发现。同时,非特权用户空间也为本地提权暴露了巨大的攻击面。
