CVE-2020-14386:Linux内核AF_PACKET权限提升漏洞分析
字数 1444 2025-08-20 18:18:05

Linux内核AF_PACKET权限提升漏洞(CVE-2020-14386)分析报告

漏洞概述

CVE-2020-14386是Linux内核中AF_PACKET套接字实现的一个权限提升漏洞,存在于tpacket_rcv函数中。该漏洞允许本地攻击者通过构造特殊的网络包导致内核内存越界写入,可能实现权限提升。漏洞影响范围广泛,需要CAP_NET_RAW权限才能利用。

环境搭建

调试环境准备

  1. 下载调试镜像
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
  1. 编译内核
    参考:快速搭建一个Linux内核调试环境

  2. 编译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
  1. 设置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
  1. 调试设置
    设置断点: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. [1]处netoffunsigned short类型(范围[0, 0xffff]),而po->tp_reserveunsigned int类型(范围[0, 0xffffffff])。在赋值过程中进行类型转换,导致高两个字节被截断。

  2. [2]处:攻击者可以控制netoff,使得计算得到的macoff小于sizeof(struct virtio_net_hdr)

  3. [3]处macoff - sizeof(struct virtio_net_hdr)可能为负值,相当于往&h.raw地址前面写入数据,造成向上越界写漏洞。

漏洞限制

  1. 需要CAP_NET_RAW权限
  2. 只能向上越界写1~10个字节
  3. 因为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;
};

利用步骤:

  1. 在ring buffer前分配sctp_shared_key结构(通过kmalloc-32)
  2. 利用漏洞覆盖refcount字段
  3. 由于refcount减少,对象被提前释放
  4. 利用UAF条件实现权限提升

利用限制

  1. 由于结构对齐,key_iddeactivated字段各占4字节
  2. 最多只能上溢refcount的1~2个字节
  3. 需要精确的内存布局控制

补丁分析

补丁将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;
+       }

参考链接

  1. Containing a Real Vulnerability
  2. OSS-Security邮件列表讨论
  3. 360 CERT公告
  4. PACKET_MMAP官方文档
  5. 补丁提交
  6. Palo Alto Networks分析
Linux内核AF_ PACKET权限提升漏洞(CVE-2020-14386)分析报告 漏洞概述 CVE-2020-14386是Linux内核中AF_ PACKET套接字实现的一个权限提升漏洞,存在于 tpacket_rcv 函数中。该漏洞允许本地攻击者通过构造特殊的网络包导致内核内存越界写入,可能实现权限提升。漏洞影响范围广泛,需要CAP_ NET_ RAW权限才能利用。 环境搭建 调试环境准备 下载调试镜像 : 编译内核 : 参考: 快速搭建一个Linux内核调试环境 编译PoC : 设置CAP_ NET_ RAW权限 : 调试设置 : 设置断点: b tpacket_rcv 基础知识 Linux内核内存布局 PACKET_ MMAP机制 PACKET_ MMAP是Linux内核中PF_ PACKET套接口的一种高效数据包处理机制,允许用户空间通过mmap直接访问内核中的网络数据包缓冲区。 TPACKET_ HEADER版本 : 环形缓冲区配置结构 : 缓冲区结构示例: tp_ block_ size= 4096 tp_ frame_ size= 2048 tp_ block_ nr = 4 tp_ frame_ nr = 8 内存布局: 漏洞分析 漏洞位于 tpacket_rcv 函数中: 漏洞成因 [ 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漏洞。 目标结构 : 利用步骤: 在ring buffer前分配 sctp_shared_key 结构(通过kmalloc-32) 利用漏洞覆盖refcount字段 由于refcount减少,对象被提前释放 利用UAF条件实现权限提升 利用限制 由于结构对齐, key_id 和 deactivated 字段各占4字节 最多只能上溢refcount的1~2个字节 需要精确的内存布局控制 补丁分析 补丁将 netoff 类型改为 unsigned int ,确保赋值两边类型相同,避免整数溢出。同时增加了对 netoff 大小的检查: 参考链接 Containing a Real Vulnerability OSS-Security邮件列表讨论 360 CERT公告 PACKET_ MMAP官方文档 补丁提交 Palo Alto Networks分析