flawopen.com/Teardowns/cve-2024-26642-linux-nftables-anonymous-set-timeout
CVE-2024-26642:Linux nf_tables 带超时标志的匿名集合
CVE-2024-26642(NIST 评分 CVSS 7.8):nf_tables_newset() 接受同时带有超时标志的匿名集合,而任何用户态工具都不会创建这种组合;这让绑定在规则上的集合也要面对元素过期和垃圾回收。Linux 6.8 以 -EOPNOTSUPP 拒绝它。
衣帽间有两套制度。编号挂钩在一个晚上只属于一位客人,客人一走就清空。计时牌则由一名服务员每小时巡查一次,把过期的东西拿走。登记表允许为同一件外套同时勾选两项,于是这个挂钩既会在客人离开时清空,也会按服务员的时间表被清理。两个人都以为自己负责这个挂钩,却都不了解对方的时间表。修复很简单:登记表现在拒绝同时勾选这两项。
核心概念与专有名词
nf_tables set- 内核中存放地址、端口或其他键的数据结构,供 nftables 规则匹配;通过 netlink 消息
NFT_MSG_NEWSET创建,由nf_tables_newset()处理。 匿名集合(NFT_SET_ANONYMOUS)- 没有名字的集合,例如直接写在规则里的
{ 22, 80 }。它只绑定这一条规则,并在同一事务中随规则一起销毁。 NFT_SET_TIMEOUT- 为集合元素设置过期时间的标志。过期元素由集合的垃圾回收机制移除。
NFT_SET_EVAL- 用于从数据包路径更新的集合的标志,旧式
meter语句会用到。修复对它们仍允许“匿名 + 超时 + eval”的组合。 用户命名空间中的 CAP_NET_ADMIN- nf_tables 接受在自身网络命名空间中持有 CAP_NET_ADMIN 的任何进程的配置;在发行版允许的情况下,普通用户创建一个用户命名空间即可获得该能力。
根本原因剖析 (Root Cause)
net/netfilter/nf_tables_api.c 中的 nf_tables_newset() 只检查集合标志中的未知位,以及 MAP+OBJECT、EVAL+OBJECT 这两组冲突。它接受 NFT_SET_ANONYMOUS | NFT_SET_TIMEOUT,于是一个生命周期绑定在单条规则和单个事务上的集合,还可能拥有会过期、会被垃圾回收的元素。用户态从不产生这种组合,生命周期代码也不是为它设计的。上游修复以 -EOPNOTSUPP 拒绝该组合,除非同时设置了 NFT_SET_EVAL。公开资料没有详细描述由此产生的内存错误;NIST 评分 7.8(机密性、完整性、可用性均为高),内核 CNA 评分 5.5(仅可用性)。
攻击执行流程分解
获取 CAP_NET_ADMIN
本地用户创建用户命名空间和网络命名空间(在允许非特权用户命名空间的系统上),从而对该命名空间的 nf_tables 状态拥有 CAP_NET_ADMIN。
请求非法集合
进程在一个 netlink 批次中发送 NFT_MSG_NEWSET,其 NFTA_SET_FLAGS = NFT_SET_ANONYMOUS | NFT_SET_TIMEOUT,并通过 lookup 表达式把集合绑定到一条新规则上。
内核予以接受
nf_tables_newset() 没有发现被禁止的标志组合,于是创建了一个绑定在规则上、元素带有过期时间的匿名集合。
未经测试的生命周期路径
此后提交、中止或删除该规则,会同时触发集合销毁和元素垃圾回收,而 nf_tables 的事务代码在这些路径上并不能安全处理。
源代码对比:漏洞与安全实现
/* net/netfilter/nf_tables_api.c: nf_tables_newset(), before the fix */
if (nla[NFTA_SET_FLAGS] != NULL) {
flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS]));
if (flags & ~(NFT_SET_ANONYMOUS | NFT_SET_CONSTANT |
NFT_SET_INTERVAL | NFT_SET_TIMEOUT |
NFT_SET_MAP | NFT_SET_EVAL |
NFT_SET_OBJECT | NFT_SET_CONCAT | NFT_SET_EXPR))
return -EOPNOTSUPP;
/* Only one of these operations is supported */
if ((flags & (NFT_SET_MAP | NFT_SET_OBJECT)) ==
(NFT_SET_MAP | NFT_SET_OBJECT))
return -EOPNOTSUPP;
if ((flags & (NFT_SET_EVAL | NFT_SET_OBJECT)) ==
(NFT_SET_EVAL | NFT_SET_OBJECT))
return -EOPNOTSUPP;
/* BUG: NFT_SET_ANONYMOUS | NFT_SET_TIMEOUT is accepted. A rule-bound
* anonymous set now also gets per-element expiry and garbage
* collection, a combination nft never creates and the set
* lifetime code was not written for. */
}
/* net/netfilter/nf_tables_api.c: nf_tables_newset(), commit 16603605b667 */
if (nla[NFTA_SET_FLAGS] != NULL) {
flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS]));
if (flags & ~(NFT_SET_ANONYMOUS | NFT_SET_CONSTANT |
NFT_SET_INTERVAL | NFT_SET_TIMEOUT |
NFT_SET_MAP | NFT_SET_EVAL |
NFT_SET_OBJECT | NFT_SET_CONCAT | NFT_SET_EXPR))
return -EOPNOTSUPP;
/* Only one of these operations is supported */
if ((flags & (NFT_SET_MAP | NFT_SET_OBJECT)) ==
(NFT_SET_MAP | NFT_SET_OBJECT))
return -EOPNOTSUPP;
if ((flags & (NFT_SET_EVAL | NFT_SET_OBJECT)) ==
(NFT_SET_EVAL | NFT_SET_OBJECT))
return -EOPNOTSUPP;
/* FIX: anonymous sets are never used with timeouts from userspace,
* so reject the combination before the set exists. NFT_SET_EVAL
* stays allowed so legacy meters keep working. */
if ((flags & (NFT_SET_ANONYMOUS | NFT_SET_TIMEOUT | NFT_SET_EVAL)) ==
(NFT_SET_ANONYMOUS | NFT_SET_TIMEOUT))
return -EOPNOTSUPP;
}
工程与系统安全加固清单
- ✓运行包含提交 16603605b667 的内核(Linux 6.8,或变更日志中列出
CVE-2024-26642的稳定版或发行版内核)。 - ✓在工作负载不需要时禁用非特权用户命名空间(Debian 和 Ubuntu 上的
kernel.、unprivileged_userns_clone=0user.,或 Ubuntu 的 AppArmor userns 限制),让本地用户无法触及 nf_tables。max_user_namespaces=0 - ✓在不使用 nftables 的主机上,禁止加载
nf_tables模块。 - ✓在内核和 API 代码中,用显式允许列表描述可接受的标志组合,拒绝任何受支持客户端都不会产生的组合,而不是只拒绝已知的坏组合。
- ✓用覆盖每个标志位的 syzkaller 描述对 netlink 接口做模糊测试,让罕见组合先在测试中触及对象生命周期代码。