flawopen.com/Teardowns/cve-2024-26642-linux-nftables-anonymous-set-timeout

● CVE-2024-26642 · CVSS 7.8 · 高危
安全研究 · FlawOpen

CVE-2024-26642:Linux nf_tables 带超时标志的匿名集合

CVE-2024-26642(NIST 评分 CVSS 7.8):nf_tables_newset() 接受同时带有超时标志的匿名集合,而任何用户态工具都不会创建这种组合;这让绑定在规则上的集合也要面对元素过期和垃圾回收。Linux 6.8 以 -EOPNOTSUPP 拒绝它。

💡 通俗易懂的原理解析 (ELI5)

衣帽间有两套制度。编号挂钩在一个晚上只属于一位客人,客人一走就清空。计时牌则由一名服务员每小时巡查一次,把过期的东西拿走。登记表允许为同一件外套同时勾选两项,于是这个挂钩既会在客人离开时清空,也会按服务员的时间表被清理。两个人都以为自己负责这个挂钩,却都不了解对方的时间表。修复很简单:登记表现在拒绝同时勾选这两项。

核心概念与专有名词

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(仅可用性)。

攻击执行流程分解

步骤 1

获取 CAP_NET_ADMIN

本地用户创建用户命名空间和网络命名空间(在允许非特权用户命名空间的系统上),从而对该命名空间的 nf_tables 状态拥有 CAP_NET_ADMIN。

步骤 2

请求非法集合

进程在一个 netlink 批次中发送 NFT_MSG_NEWSET,其 NFTA_SET_FLAGS = NFT_SET_ANONYMOUS | NFT_SET_TIMEOUT,并通过 lookup 表达式把集合绑定到一条新规则上。

步骤 3

内核予以接受

nf_tables_newset() 没有发现被禁止的标志组合,于是创建了一个绑定在规则上、元素带有过期时间的匿名集合。

步骤 4

未经测试的生命周期路径

此后提交、中止或删除该规则,会同时触发集合销毁和元素垃圾回收,而 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;
	}

工程与系统安全加固清单

参考来源