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 で拒否します。
クロークには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(可用性のみ)と評価しています。
ステップ・バイ・ステップの攻撃フロー
CAP_NET_ADMIN を得る
ローカルユーザーが(非特権ユーザー名前空間が有効な環境で)ユーザー名前空間とネットワーク名前空間を作り、その名前空間の nf_tables の状態に対して CAP_NET_ADMIN を持ちます。
不正なセットを要求する
netlink のバッチ内で NFTA_SET_FLAGS = NFT_SET_ANONYMOUS | NFT_SET_TIMEOUT の NFT_MSG_NEWSET を送り、lookup 式でそのセットを新しいルールに結び付けます。
カーネルが受け入れる
nf_tables_newset() は禁止されたフラグの組を見つけられず、要素に有効期限を持つ、ルールに結び付いた匿名セットを作成します。
未検証の寿命管理経路
以後ルールのコミット、中止、削除のたびに、セットの破棄と要素のガベージコレクションが同時に動き、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;
}
エンジニアリング&システム堅牢化チェックリスト
- ✓コミット 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 インターフェースをファジングし、珍しい組み合わせがまずテストでオブジェクトの寿命管理コードに届くようにする。