Linux内核AF_PACKET权限提升漏洞(CVE-2020-14386)分析报告
漏洞概述
CVE-2020-14386是Linux内核中AF_PACKET套接字实现的一个权限提升漏洞,存在于tpacket_rcv函数中。该漏洞允许本地攻击者通过构造特殊的网络包导致内核内存越界写入,可能实现权限提升。漏洞影响范围广泛,需要CAP_NET_RAW权限才能利用。
环境搭建
调试环境准备
- 下载调试镜像:
sudo apt-get install debootstrap
wget https://raw.githubusercontent.com/google/syzkaller/master/tools/create-image.sh -O create-image.sh
chmod +x create-image.sh
./create-image.sh
-
编译内核:
参考:快速搭建一个Linux内核调试环境 -
编译PoC:
wget https://raw.githubusercontent.com/cgwalters/cve-2020-14386/master/cve-cap-net-raw.c
gcc cve-cap-net-raw.c -o poc -static
- 设置CAP_NET_RAW权限:
# 设置cap_net_raw权限
setcap cap_net_raw+ep ./poc
# 查看程序的cap权限
getcap ./poc
# 删除cap_net_raw权限
setcap cap_net_raw-ep ./poc
# 仅做调试使用可以直接运行:
sudo ./poc skip-unshare
- 调试设置:
设置断点:b tpacket_rcv
基础知识
Linux内核内存布局
0xffffffffffffffff ---+-----------+-----------------------------------------------+-------------+
| | |+++++++++++++|
8M | | unused hole |+++++++++++++|
| | |+++++++++++++|
0xffffffffff7ff000 ---|-----------+------------| FIXADDR_TOP |--------------------|+++++++++++++|
1M | | |+++++++++++++|
0xffffffffff600000 ---+-----------+------------| VSYSCALL_ADDR |------------------|+++++++++++++|
548K | | vsyscalls |+++++++++++++|
0xffffffffff577000 ---+-----------+------------| FIXADDR_START |------------------|+++++++++++++|
5M | | hole |+++++++++++++|
0xffffffffff000000 ---+-----------+------------| MODULES_END |--------------------|+++++++++++++|
| | |+++++++++++++|
1520M | | module mapping space (MODULES_LEN) |+++++++++++++|
| | |+++++++++++++|
0xffffffffa0000000 ---+-----------+------------| MODULES_VADDR |------------------|+++++++++++++|
| | |+++++++++++++|
512M | | kernel text mapping, from phys 0 |+++++++++++++|
| | |+++++++++++++|
0xffffffff80000000 ---+-----------+------------| __START_KERNEL_map |-------------|+++++++++++++|
2G | | hole |+++++++++++++|
0xffffffff00000000 ---+-----------+-----------------------------------------------|+++++++++++++|
64G | | EFI region mapping space |+++++++++++++|
0xffffffef00000000 ---+-----------+-----------------------------------------------|+++++++++++++|
444G | | hole |+++++++++++++|
0xffffff8000000000 ---+-----------+-----------------------------------------------|+++++++++++++|
16T | | %esp fixup stacks |+++++++++++++|
0xffffff0000000000 ---+-----------+-----------------------------------------------|+++++++++++++|
3T | | hole |+++++++++++++|
0xfffffc0000000000 ---+-----------+-----------------------------------------------|+++++++++++++|
16T | | kasan shadow memory (16TB) |+++++++++++++|
0xffffec0000000000 ---+-----------+-----------------------------------------------|+++++++++++++|
1T | | hole |+++++++++++++|
0xffffeb0000000000 ---+-----------+-----------------------------------------------| kernel space|
1T | | virtual memory map for all of struct pages |+++++++++++++|
0xffffea0000000000 ---+-----------+------------| VMEMMAP_START |------------------|+++++++++++++|
1T | | hole |+++++++++++++|
0xffffe90000000000 ---+-----------+------------| VMALLOC_END |------------------|+++++++++++++|
32T | | vmalloc/ioremap (1 << VMALLOC_SIZE_TB) |+++++++++++++|
0xffffc90000000000 ---+-----------+------------| VMALLOC_START |------------------|+++++++++++++|
1T | | hole |+++++++++++++|
0xffffc80000000000 ---+-----------+-----------------------------------------------|+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
64T | | direct mapping of all phys. memory |+++++++++++++|
| | (1 << MAX_PHYSMEM_BITS) |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
0xffff880000000000 ----+-----------+-----------| __PAGE_OFFSET_BASE | -------------|+++++++++++++|
| | |+++++++++++++|
8T | | guard hole, reserved for hypervisor |+++++++++++++|
| | |+++++++++++++|
0xffff800000000000 ----+-----------+-----------------------------------------------+-------------+
|-----------| |-------------|
|-----------| hole caused by [48:63] sign extension |-------------|
|-----------| |-------------|
0x0000800000000000 ----+-----------+-----------------------------------------------+-------------+
PAGE_SIZE | | guard page |xxxxxxxxxxxxx|
0x00007ffffffff000 ----+-----------+--------------| TASK_SIZE_MAX | ---------------|xxxxxxxxxxxxx|
| | | user space |
PACKET_MMAP机制
PACKET_MMAP是Linux内核中PF_PACKET套接口的一种高效数据包处理机制,允许用户空间通过mmap直接访问内核中的网络数据包缓冲区。
TPACKET_HEADER版本:
enum tpacket_versions {
TPACKET_V1,
TPACKET_V2,
TPACKET_V3
};
环形缓冲区配置结构:
struct tpacket_req {
unsigned int tp_block_size; /* 连续块的最小大小 */
unsigned int tp_block_nr; /* 块数量 */
unsigned int tp_frame_size; /* 帧大小 */
unsigned int tp_frame_nr; /* 帧总数 */
};
缓冲区结构示例:
- tp_block_size= 4096
- tp_frame_size= 2048
- tp_block_nr = 4
- tp_frame_nr = 8
内存布局:
block #1 block #2
+---------+---------+ +---------+---------+
| frame 1 | frame 2 | | frame 3 | frame 4 |
+---------+---------+ +---------+---------+
block #3 block #4
+---------+---------+ +---------+---------+
| frame 5 | frame 6 | | frame 7 | frame 8 |
+---------+---------+ +---------+---------+
漏洞分析
漏洞位于tpacket_rcv函数中:
static int tpacket_rcv(struct sk_buff *skb, struct net_device *dev,
struct packet_type *pt, struct net_device *orig_dev)
{
// ...
if (sk->sk_type == SOCK_DGRAM) {
macoff = netoff = TPACKET_ALIGN(po->tp_hdrlen) + 16 +
po->tp_reserve;
} else {
unsigned int maclen = skb_network_offset(skb);
// tp_reserve是unsigned int,netoff是unsigned short。加法可能使netoff溢出
netoff = TPACKET_ALIGN(po->tp_hdrlen +
(maclen < 16 ? 16 : maclen)) +
po->tp_reserve; // [1]
if (po->has_vnet_hdr) {
netoff += sizeof(struct virtio_net_hdr);
do_vnet = true;
}
// 攻击者可以控制netoff,使macoff小于sizeof(struct virtio_net_hdr)
macoff = netoff - maclen; // [2]
}
// ...
// "macoff - sizeof(struct virtio_net_hdr)"可能为负数,导致指针指向h.raw之前
if (do_vnet &&
virtio_net_hdr_from_skb(skb, h.raw + macoff -
sizeof(struct virtio_net_hdr),
vio_le(), true, 0)) { // [3]
// ...
漏洞成因
-
[1]处:
netoff是unsigned short类型(范围[0, 0xffff]),而po->tp_reserve是unsigned int类型(范围[0, 0xffffffff])。在赋值过程中进行类型转换,导致高两个字节被截断。 -
[2]处:攻击者可以控制
netoff,使得计算得到的macoff小于sizeof(struct virtio_net_hdr)。 -
[3]处:
macoff - sizeof(struct virtio_net_hdr)可能为负值,相当于往&h.raw地址前面写入数据,造成向上越界写漏洞。
漏洞限制
- 需要CAP_NET_RAW权限
- 只能向上越界写1~10个字节
- 因为
virtio_net_hdr结构大小为0xa,所以最多能覆盖前10个字节
漏洞利用
利用思路
作者提出的利用方法是在ring buffer前放置一个包含refcount的结构,通过上溢减少refcount的值。由于packet_socket_send中的memset会赋零,refcount减少可能导致对象被错误地认为已释放,从而造成UAF漏洞。
目标结构:
struct sctp_shared_key {
struct list_head key_list;
struct sctp_auth_bytes *key;
refcount_t refcnt;
__u16 key_id;
__u8 deactivated;
};
利用步骤:
- 在ring buffer前分配
sctp_shared_key结构(通过kmalloc-32) - 利用漏洞覆盖refcount字段
- 由于refcount减少,对象被提前释放
- 利用UAF条件实现权限提升
利用限制
- 由于结构对齐,
key_id和deactivated字段各占4字节 - 最多只能上溢refcount的1~2个字节
- 需要精确的内存布局控制
补丁分析
补丁将netoff类型改为unsigned int,确保赋值两边类型相同,避免整数溢出。同时增加了对netoff大小的检查:
netoff = TPACKET_ALIGN(po->tp_hdrlen + (maclen < 16 ? 16 : maclen)) + po->tp_reserve;
+ if (netoff > USHRT_MAX) {
+ atomic_inc(&po->tp_drops);
+ goto drop_n_restore;
+ }