当前位置:首页>Linux>19 年未被发现的 Linux 内核零日漏洞:CVE-2026-43456 深度分析

19 年未被发现的 Linux 内核零日漏洞:CVE-2026-43456 深度分析

  • 2026-10-11 06:41:06
19 年未被发现的 Linux 内核零日漏洞:CVE-2026-43456 深度分析

GMO 网络安全团队近日披露了一枚潜伏长达 19 年的 Linux 内核漏洞 CVE-2026-43456。该漏洞的根因代码于 2007 年随 commit 1284cd3a2b740d0118458d2ea470a1e5bc19b187 合入内核,直至 2026 年才被发现并修复(fix commit: 950803f7254721c1c15858fbbfae3deaaeeecb11)。

研究团队利用该漏洞构建的 exploit 实现了 超过 99% 的成功率,且可在 1 秒内稳定完成权限提升。该 exploit 已提交至 Google kernelCTF 项目,获得 8 万美元以上奖金。

影响范围与条件

  • 影响版本:Linux 2.6.24 ~ 6.12.77
  • 子系统:net/bonding
  • 根因类型:type confusion(类型混淆)
  • 触发条件:需要 CAP_NET_ADMIN 权限(非特权用户可通过 user namespace 获得)

修复与缓解措施

漏洞已于 2026 年 3 月修复,主流发行版最新版均已包含补丁。无法更新的环境可通过以下方式缓解:

  1. 禁用非特权 user namespace:echo 0 > /proc/sys/kernel/unprivileged_userns_clone(代价为 Rootless Docker 等无法使用)
  2. 禁用 bonding 模块:
    echo "install bonding /bin/false" > /etc/modprobe.d/disable-bonding.conf rmmod bonding 2>/dev/null

Bonding 在大多数发行版中为模块形式,默认并未加载,因此大部分用户不受影响。

漏洞根因:一行代码引发的 Type Confusion

漏洞的起源位于 bond_setup_by_slave() 函数中,仅一行赋值操作:

static void bond_setup_by_slave(struct net_device *bond_dev,
                struct net_device *slave_dev)
{
    bool was_up = !!(bond_dev->flags & IFF_UP);
    dev_close(bond_dev);

    bond_dev->header_ops = slave_dev->header_ops;  // ★ 漏洞行

    bond_dev->type          = slave_dev->type;
    bond_dev->hard_header_len   = slave_dev->hard_header_len;
    bond_dev->needed_headroom   = slave_dev->needed_headroom;
    bond_dev->addr_len      = slave_dev->addr_len;

header_ops 是一个函数指针表,用于处理特定协议的数据包头部。Bonding 设备为透明转发底层 slave 设备的流量,会直接复用 slave 的 header_ops。问题在于:这些 header 函数内部会通过 netdev_priv(dev) 访问设备私有存储区(Private Data),而 bond 设备与 slave 设备的私有数据结构完全不同。

Bond 设备分配的是 sizeof(struct bonding) 字节的私有区域:

struct rtnl_link_ops bond_link_ops __read_mostly = {
    .kind           = "bond",
    .priv_size      = sizeof(struct bonding),
    .setup          = bond_setup,
    ...
};

而 exploit 利用的 GRE 协议则将其解释为 struct ip_tunnel:

struct ip_tunnel *t = netdev_priv(dev);

两个结构体完全不兼容:

struct bonding {
    struct   net_device *dev; /* first - useful for panic debug */
    struct   slave __rcu *curr_active_slave;
    struct   slave __rcu *current_arp_slave;
    struct   slave __rcu *primary_slave;
    struct   bond_up_slave __rcu *usable_slaves;
    struct   bond_up_slave __rcu *all_slaves;
    // ...
};

struct ip_tunnel {
    struct ip_tunnel __rcu  *next;
    struct hlist_node hash_node;
    struct net_device   *dev;
    netdevice_tracker   dev_tracker;
    struct net      *net;
    unsigned long   err_time;
    // ...
};

将 GRE 设备作为 slave 挂载到 bond 后,对 bond 设备执行 GRE 的头部处理就会引发内存破坏。

Exploit 链详解

Step 1:KASLR 绕过(信息泄露)

利用 IP6GRE 协议实现类型混淆读取内核地址。

从 bond 侧看,struct bonding 的 recv_probe 字段位于偏移 0x38,这是一个函数指针,指向 bond_rcv_validate:

if (bond->params.arp_interval) {
    queue_delayed_work(bond->wq, &bond->arp_work, 0);
    bond->recv_probe = bond_rcv_validate;
}

从 IP6GRE 侧看,struct ip6_tnl 的 parms.laddr 也位于偏移 0x38,这是一个 IPv6 源地址字段(16 字节 struct in6_addr),接收到的数据包会包含该字段的值。

/* offset   | size */  type = struct ip6_tnl {
/* 0x0038   | 0x0010 */    struct in6_addr laddr;  // ← 作为 IPv6 地址发送出去

/* offset   | size */  type = struct bonding {
/* 0x0038   | 0x0008 */    int (*recv_probe)(...); // ← 实际存储 bond_rcv_validate 的地址

通过 type confusion,bond 的 recv_probe(bond_rcv_validate 的函数地址)被 IP6GRE 作为源地址发出。接收方解析该 IPv6 地址即可还原内核基址,突破 KASLR。

Step 2:任意代码执行

Step 2.1:伪造 ZeroCopy 标志

利用 IPv4 GRE 的 ipgre_header() 函数,通过写入 greh->flags 来篡改 skb_shared_info->flags。

关键代码路径:

static inline struct ubuf_info *skb_zcopy(struct sk_buff *skb)
{
    bool is_zcopy = skb && skb_shinfo(skb)->flags & SKBFL_ZEROCOPY_ENABLE;
    return is_zcopy ? skb_uarg(skb) : NULL;
}

static inline void skb_zcopy_clear(struct sk_buff *skb, bool success)
{
    struct ubuf_info *uarg = skb_zcopy(skb);
    if (uarg)  // 非 NULL 时调用 callback
        uarg->callback(skb, uarg, success);
}

如果能在非 zerocopy 的 skb 上错误地置起 SKBFL_ZEROCOPY_ENABLE 标志,skb 末尾的 ubuf_info 就会被当作有效对象处理,其 callback 指针可以被劫持。

ipgre_header() 的操作如下:

iph = skb_push(skb, t->hlen + sizeof(*iph));
greh = (struct gre_base_hdr *)(iph + 1);
greh->flags = gre_tnl_flags_to_gre_flags(t->parms.o_flags);

在 type confusion 生效时,t 实际指向 struct bonding,其 t->hlen == 0。因此 skb_push() 只回退 sizeof(struct iphdr) 而非 sizeof(struct iphdr) + sizeof(struct gre_base_hdr),导致 greh 指针落在原 skb->data 位置——即发生了 buffer overflow。

gre_base_hdr 与 skb_shared_info 的头部结构对齐:

struct gre_base_hdr {
    __be16 flags;  // 2 字节
    // ...
};

struct skb_shared_info {
    __u8 flags;    // 1 字节
    // ...
};

当 skb->data 恰好与 skb_shared_info 起始地址重合时,写入 greh->flags(2 字节)就会覆盖 skb_shared_info->flags(1 字节)。写入的值由 gre_tnl_flags_to_gre_flags(t->parms.o_flags) 决定。在 type confusion 下,t->parms.o_flags 读取的是 struct bonding 偏移 0x6e 的值——即 bond_list.next 的 6–7 字节,内核指针的这两个字节恒为 0xff 0xff。转换后得到:

gre_tnl_flags_to_gre_flags(0xffff) = 0x07ff

其中 BIT(0) 被置位,对应 SKBFL_ZEROCOPY_ENABLE。非 zerocopy 的 skb 由此被伪装成 zerocopy skb。

Step 2.2:精确对齐 skb->data

为了让 skb->data 与 skb_shared_info 的起始地址重合,需要将 LL_RESERVED_SPACE(dev) 精确控制到 0x3ec0。

skb 缓冲区按页大小对齐分配,skb_shared_info 固定在页面末尾:

skb_shinfo(skb) = skb->head + (0x4000 - sizeof(struct skb_shared_info))
                = skb->head + 0x3ec0

当发送 len == 0 的空数据包时(无实际包数据),skb->data 也会落在 0x3ec0 偏移,与 skb_shared_info 重叠。

LL_RESERVED_SPACE 的计算:

#define LL_RESERVED_SPACE(dev) \
    ((((dev)->hard_header_len + READ_ONCE((dev)->needed_headroom)) \
      & ~(HH_DATA_MOD - 1)) + HH_DATA_MOD)

研究团队采用 GRE 设备链(chaining) 来精确调整 needed_headroom。GRE 协议支持设备链,当前 GRE 设备的 needed_headroom 基于下层设备的头长度逐层累加。他们构建了 329 个 GRE 设备的链条:

if0 <- if1 <- if2 <- ... <- if328
  • 前 8 个为 FOU encapsulation GRE(tunnel->hlen = 0xc,t_hlen = 0x20)
  • 后续 321 个为 plain GRE(tunnel->hlen = 0x4,t_hlen = 0x18)

ip_tunnel_bind_dev() 的计算逻辑:

int hlen = LL_MAX_HEADER;
int t_hlen = tunnel->hlen + sizeof(struct iphdr);
if (tdev)
    hlen = tdev->hard_header_len + tdev->needed_headroom;
dev->needed_headroom = t_hlen + hlen;

前 8 个 FOU GRE 使 needed_headroom 增长到 0x298,之后 320 个 plain GRE 使最终值增长到 0x3e98。将最后一个 GRE enslave 到 bond 后,bond 复制该值:

bond->needed_headroom = 0x3e98
bond->hard_header_len = 0x18
LL_RESERVED_SPACE = align_down(0x3eb0, 0x10) + 0x10 = 0x3ec0  // √ 命中目标

为何隐藏了 19 年

这恰是漏洞难以发现的原因——buffer overflow 本身一直存在,但要产生可被利用的副作用,需同时满足:

  1. 精心设计的设备链,将 needed_headroom 精确调至某一特定值
  2. 发送 len == 0 的空包,使 skb->data 与 skb_shared_info 重合

在通常情况下,buffer overflow 只是写入 page 对齐后未使用的内存区域,不会引发任何可见的崩溃或异常。即使 KASAN 等内存检测工具也难以发现这种"无害"越界。研究人员实际上是通过 syzkaller 偶然触发的内核崩溃,再经深度根因分析才逆向定位到这一行代码。

总结

CVE-2026-43456 是一个教科书式的 type confusion 案例——一行简单的函数指针赋值,结合 19 年后才被精确构造的设备链 exploit,展示了内核漏洞从"存在"到"可被利用"之间的巨大鸿沟。研究团队还指出,虽然 AI 在根因分析(RCA)中给予了重要帮助(当时为 2025 年上半年,前沿模型对 Linux 内核的代码理解已相当出色),但完全依赖 AI 自动发现此类复杂漏洞仍为时过早。

最新文章

随机文章