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)

クロークには2つの仕組みがあります。番号付きのフックは一晩だけ1人の客のもので、その客が帰った瞬間に片付けられます。時間付きの札は係員が1時間ごとに見回り、期限切れのものを下げていきます。申込書では同じコートに両方のチェックを付けられるので、そのフックは客が帰ったときにも、係員の巡回でも片付けられることになります。2人がそれぞれ同じフックの担当だと思っていて、どちらも相手の予定を知りません。修正は単純で、申込書がこの組み合わせを受け付けなくなりました。

主要な概念と専門用語

nf_tables set
nftables のルールが照合するアドレスやポートなどのキーを保持するカーネルのデータ構造。netlink メッセージ NFT_MSG_NEWSET で作成され、nf_tables_newset() が処理します。
匿名セット(NFT_SET_ANONYMOUS)
ルールの中に直接書く { 22, 80 } のような名前のないセット。その1つのルールに結び付き、同じトランザクションでルールと一緒に破棄されます。
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 は受け入れられたため、寿命が1つのルールと1つのトランザクションに結び付いたセットが、期限切れになりガベージコレクションで回収される要素まで持てました。ユーザー空間はこの組み合わせを作らず、寿命管理のコードもこれを想定していません。上流の修正は、NFT_SET_EVAL も付いていない限りこれを -EOPNOTSUPP で拒否します。結果として起きるメモリエラーの詳細は公開情報に書かれていません。NIST は 7.8(機密性・完全性・可用性すべて高)、カーネルの CNA は 5.5(可用性のみ)と評価しています。

ステップ・バイ・ステップの攻撃フロー

ステップ 1

CAP_NET_ADMIN を得る

ローカルユーザーが(非特権ユーザー名前空間が有効な環境で)ユーザー名前空間とネットワーク名前空間を作り、その名前空間の nf_tables の状態に対して CAP_NET_ADMIN を持ちます。

ステップ 2

不正なセットを要求する

netlink のバッチ内で NFTA_SET_FLAGS = NFT_SET_ANONYMOUS | NFT_SET_TIMEOUT の NFT_MSG_NEWSET を送り、lookup 式でそのセットを新しいルールに結び付けます。

ステップ 3

カーネルが受け入れる

nf_tables_newset() は禁止されたフラグの組を見つけられず、要素に有効期限を持つ、ルールに結び付いた匿名セットを作成します。

ステップ 4

未検証の寿命管理経路

以後ルールのコミット、中止、削除のたびに、セットの破棄と要素のガベージコレクションが同時に動き、nf_tables のトランザクション処理が安全に扱えない経路を通ります。

ソースコード比較:脆弱 vs 堅牢化

脆弱な実装
/* 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;
	}

エンジニアリング&システム堅牢化チェックリスト

参考資料