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 月修复,主流发行版最新版均已包含补丁。无法更新的环境可通过以下方式缓解:
- 禁用非特权 user namespace:
echo 0 > /proc/sys/kernel/unprivileged_userns_clone(代价为 Rootless Docker 等无法使用) - 禁用 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 本身一直存在,但要产生可被利用的副作用,需同时满足:
- 精心设计的设备链,将
needed_headroom 精确调至某一特定值 - 发送
len == 0 的空包,使 skb->data 与 skb_shared_info 重合
在通常情况下,buffer overflow 只是写入 page 对齐后未使用的内存区域,不会引发任何可见的崩溃或异常。即使 KASAN 等内存检测工具也难以发现这种"无害"越界。研究人员实际上是通过 syzkaller 偶然触发的内核崩溃,再经深度根因分析才逆向定位到这一行代码。
总结
CVE-2026-43456 是一个教科书式的 type confusion 案例——一行简单的函数指针赋值,结合 19 年后才被精确构造的设备链 exploit,展示了内核漏洞从"存在"到"可被利用"之间的巨大鸿沟。研究团队还指出,虽然 AI 在根因分析(RCA)中给予了重要帮助(当时为 2025 年上半年,前沿模型对 Linux 内核的代码理解已相当出色),但完全依赖 AI 自动发现此类复杂漏洞仍为时过早。