1 ポイント 投稿者 GN⁺ 2024-03-27 | 1件のコメント | WhatsAppで共有
  • Linuxカーネル nf_tablesCVE-2024-1086 は、Netfilter verdict の入力検証不備により sk_buff の二重解放を引き起こし、条件がそろうとローカル権限昇格につながる可能性がある
  • 攻撃フローは、NF_DROP 処理中に解放された skb が NF_ACCEPT のようにそのまま処理継続されるようにして、同じオブジェクトが後続経路で再び解放される構造になっている
  • PoC は KernelCTF mitigation、Debian、Ubuntu、および vanilla カーネルで検証されており、少なくとも v5.14.21~v6.6.14 の範囲が kconfig によって影響を受けうる。修正は 2024年2月に stable ブランチへ配布された
  • 中核となるのは Dirty Pagedirectory で、PTE と PMD ページを同じ物理ページに重複割り当てし、ユーザー空間の読み書きだけで任意の物理アドレスへアクセスするデータ専用 KSMA 手法である
  • 物理 KASLR の探索、modprobe_path の回避、ファイルレス実行、名前空間脱出用ファイルディスクリプタの接続まで組み合わせられており、カーネルのメモリ管理とネットワークサブシステムを同時に狙う実戦的な LPE 事例となっている

脆弱性の条件と影響範囲

  • nf_tables のバグは CVE-2024-1086 として登録されており、Linux カーネル Netfilter の verdict 処理において 正の drop error が許可される入力検証不備が核心である
  • エクスプロイトには次の条件が必要
    • nf_tables が有効化されていること
    • 非特権ユーザー名前空間 が有効化されていること
    • Debian や Ubuntu のような主要ディストリビューションでこの設定がデフォルト有効の場合、それが攻撃の前提となる
  • テスト基準では stable ブランチ linux-5.15.ylinux-6.1.ylinux-6.6.y が影響範囲に含まれ、linux-6.7.1 も可能性がある
  • 2024年2月に stable ブランチへ バグ修正 が配布された
  • PoC のソースコードは CVE-2024-1086 PoC repository で公開されている

二重解放が生まれるコードフロー

  • Netfilter verdict は、パケットをドロップするか、受理するか、キューへ送るかなどを決定する値である
  • 脆弱なフローは、nft_verdict_init() がユーザー入力の verdict 値を十分に制限していなかったため、NF_DROP に見えつつも drop error が正の値である値を設定できたことから始まる
  • nf_hook_slow() は verdict の下位ビットを見て NF_DROP と判断すると、kfree_skb_reason() によって skb を先に解放する
  • その後 NF_DROP_GETERR() の結果が NF_ACCEPT に相当する値として返ると、呼び出し側はパケットが受理されたものとみなして処理を続行する
  • その結果、すでに解放済みの skb が後続経路で再度解放され、double-free primitive が生まれる

破損するオブジェクトとパケット処理方式

  • 二重解放は skbuff_head_cachestruct sk_buffsk_buff->head オブジェクトに影響する
  • sk_buff->head は実際のパケット内容を保持し、IPv4 パケットサイズに応じて kmalloc-256 から buddy allocator の order 4 ページ まで割り当てられうる
  • PoC は大きな IP パケットを使い、slab allocator ではなく buddy allocator の経路に入るよう設計されている
  • IPv4 fragmentation queue は、skb の 2回目の解放を遅延させたり、望むタイミングで誘発したりするために活用される
  • パケット経路で破損した skb フィールドが使われるとカーネルパニックが発生しうるため、TCP/UDP スタックを避けて特定の IP fragment エラーパスを利用する

テスト範囲と成功率

  • vanilla カーネル、KernelCTF、Debian、Ubuntu 環境で複数のカーネルバージョンと設定がテストされた
  • 成功例には次の環境が含まれる
    • Linux v5.14.21、v5.15.148、v5.16.20、v5.17.15、v5.18.19、v5.19.17、v6.0.19
    • KernelCTF Mitigation v3 の Linux v6.1.55
    • Debian Bookworm 6.1.0-17 の Linux v6.1.69
    • KernelCTF LTS の Linux v6.1.72
    • Ubuntu Jammy v6.2.0-37
    • Linux v6.2.16、v6.3.13
  • 失敗例には v5.4.270、v5.10.209、v6.4.16、Ubuntu Jammy v6.5.0-15、v6.5.13、v6.6.14、v6.7.1 などが含まれる
  • v6.4.0 以降の一部失敗は、CONFIG_INIT_ON_ALLOC_DEFAULT_ON=y による bad_page() 検知 と関連している
  • v6.4.16 環境では成功率が 99.4% で、場合によっては 93.0% まで下がり、サンプル数はそれぞれ n=1000 であった

修正方法

  • 当初提案された修正は、Netfilter stack の途中で breaking change を生む可能性があった
  • Netfilter maintainer による修正は、ユーザー入力から入る verdict を API 段階でより厳格に制限するものだった
  • パッチは userland 入力に対して DROP/QUEUE verdict parameter を拒否する方式である
  • CVE の説明によれば、nft_verdict_init() は hook verdict 内の drop error に正の値を許しており、nf_hook_slow()NF_DROPNF_ACCEPT が重なる状況で double free を引き起こしえた
  • 修正パッチは [PATCH nf] netfilter: nf_tables: reject QUEUE/DROP verdict parameters. で確認できる

Dirty Pagedirectory と任意物理メモリアクセス

  • PoC の中心手法は Dirty Pagedirectory であり、既存の Dirty Pagetable 手法の派生である
  • 核心となるアイデアは、PTE ページと PMD ページを同じ物理ページに重複割り当てすることにある
  • ある仮想アドレス領域に PTE 値のように見える値を書き込むと、別の仮想アドレス領域がそれをページテーブルエントリとして解釈し、指定した物理ページをマッピングする
  • この方式は、ユーザー空間アドレスの読み書きだけで任意の物理アドレスと権限フラグを設定してアクセスする kernel-space mirroring attack として機能する
  • virtual KASLR、KPTI、SMAP、SMEP、CONFIG_STATIC_USERMODEHELPER のような緩和策の回避に使われる

ページアロケータ操作

  • カーネルのページ割り当てには slab allocator、buddy allocator、PCP allocator が関与する
  • skb head は大きいサイズでは buddy allocator の order 4 ページを使うが、PTE/PMD ページは order 0 ページであり、直接はかみ合わない
  • これを解決するため、2つの page conversion 手法が使われる
    • PCP list draining: order 4 ページを buddy freelist に入れ、PCP order 0 freelist を空にして buddy allocator から order 0 ページとして再補充させる
    • race condition: 2回目の free 中の競合を利用して order 4 ページを order 0 freelist に入れる方式
  • PCP list draining はより単純で安定しており高速な方法である
  • race condition 方式は KernelCTF 初期エクスプロイトで使われたが、QEMU VM のような serial TTY 遅延が大きい環境に依存しており、obsolete とされている

KernelCTF mitigation の回避

  • KernelCTF mitigation 環境で積極的に回避する必要があった緩和策は sk_buff freelist corruption check だった
  • skbuff_head_cache->offset == 0x70 のため、freelist の next pointer が skb->len と重なっている
  • 1回目の skb 解放後、skb->len が freelist pointer の一部で上書きされ、その後のパケット解析中にこの値が変化すると corruption check に引っかかる可能性がある
  • 破損した skb の上に正常な skb を追加で解放して freelist head を上書きする方法により、検知を回避する
  • KernelCTF 開発者は、free 時点でも freelist head next pointer を検査すればこの回避を緩和できると見ている

TLB flush と物理 KASLR 探索

  • Dirty Pagedirectory によってページテーブルを想定外の方法で変更すると、CPU の TLB キャッシュに古い変換情報が残ることがある
  • ユーザー空間で fork() 後、子プロセスが munmap() を実行してスリープする方式で TLB を flush する
  • この方法は AMD CPU と QEMU VM で 100% 動作したことが確認されている
  • 物理 KASLR は、カーネル物理ベースアドレスが CONFIG_PHYSICAL_START または CONFIG_PHYSICAL_ALIGN に整列される点を利用して探索範囲を縮める
  • 8GiB の物理メモリと 16MiB アラインメントを仮定すると候補は 512 個で、get-sig スクリプトでカーネル base signature を生成して識別する

modprobe_path と root shell の取得

  • 任意の物理メモリ読み書きを得た後、PoC はカーネル base 以降およそ 80MiB の範囲をスキャンして modprobe_path を探す
  • 一般的な設定では "/sbin/modprobe" と null padding パターンを探し、/proc/sys/kernel/modprobe への反映有無で実際の変数かを検証する
  • CONFIG_STATIC_USERMODEHELPER が有効な場合は、"/sbin/usermode-helper" 文字列を対象とする
  • root shell を得るために、modprobe_path または static usermode helper 文字列を /proc/<pid>/fd/<fd> 形式の memfd パスで上書きする
  • privilege escalation スクリプトは、exploit のファイルディスクリプタを shell の stdin/stdout に接続し、ローカル端末と reverse shell の両方で動作するよう構成されている

ファイルレス実行と PoC 構成

  • PoC はディスクにファイルを書かない fileless execution をサポートする
  • 対象に Perl があれば、memfd_create() を使って exploit バイナリをメモリ上に載せ、/proc/$$/fd/<fd> から実行できる
  • コンパイル依存関係は libnftnl-devlibmnl-dev である
  • KernelCTF 向けの静的ビルドでは musl-gcc が使われており、glibc の静的リンクや QEMU の AVX512 opcode 問題を避けるための選択である
  • exploit ソースは複数ファイルに分かれており、スタンドアロン実行バイナリを目的としているため、エラー発生時は error code を返すより crash/exit を選ぶ設計になっている

安定性と限界

  • exploit プロセスの pagetable 状態が不安定になることがあるため、成功後または失敗後も子プロセスを終了させずスリープ状態のまま残し、カーネルの不安定化を抑える
  • ネットワーク活動があると skb freelist にノイズが入り、安定性に影響することがある
  • SSH や reverse shell 環境では、double-free のタイミング付近で stdout 出力を減らし、ネットワーク起因の skb 割り当て/解放を最小化する
  • 一部ハードウェアテストでは数秒後にシステムがクラッシュしており、WiFi フレームも skb を使うため、WiFi 活動が影響した可能性がある
  • WiFi アダプタを BIOS で無効化すると、その環境では exploit が正常動作する

研究過程で得られた結論

  • PoC は広い互換性、高い安定性、ステルス実行を目標に磨き込まれた
  • 開発期間 2か月に加え、安定性と互換性の改善にさらに 2か月が費やされた
  • exploit 自体は slab allocator の挙動に大きく依存せず、IPv4 subsystem と virtual memory のような広く有効化されている機能を中心に構成されている
  • 初期バグは unprivileged user namespace と nftables を必要とするが、Dirty Pagedirectory や PCP draining のような手法は他の実用的な exploit にも活用可能である
  • この作業は、Linux カーネルの networking subsystem と memory management subsystem を同時に深く掘り下げた事例として残る

1件のコメント

 
GN⁺ 2024-03-27
Hacker Newsのコメント
  • 本日、CVE-2024-1086 の概念実証エクスプロイトを公開した。Debian や Ubuntu などで動作する。
    影響を受けるバージョンは Linux カーネル v5.14〜v6.6 で、v6.4〜v6.6 の対応可否は CONFIG_INIT_ON_ALLOC_DEFAULT_ON カーネル設定に依存する。
    このバグは 2024 年 2 月に修正済みなので、Linux マシンは更新する必要がある。

    • ソースも良いし仕事ぶりも素晴らしい。どれほど大きな反響になるのか気になる。
      https://github.com/Notselwyn/CVE-2024-1086/blob/main/src/mai...
    • エクスプロイト後の段階で KSMA に似たプリミティブ を得ていたなら、なぜ依然として modprobe_path 手法を使い、しかもファイルレス方式のために pid 総当たりまで入れたのか気になる。
      たとえばカーネル .text を短いシェルコードでパッチして root を取り、名前空間を脱出する方式をなぜ選ばなかったのかと聞いてみたい。
    • この脆弱性で想定される 攻撃経路と影響 が何なのか気になる。
    • 本文では影響を受けるエクスプロイト対象バージョンが Linux カーネル v5.14 から v6.4 までと書かれているが、リンク先のページでは v5.14 から v6.6 までとなっている。
      ただし、修正済みブランチ v5.15.149>, v6.1.76>, v6.6.15> は除くと書かれている。
  • パッチにこんな一文がある: “This reverts commit e0abdadcc6e1. [...] Its not clear to me why this commit was made.”
    このコミットの 経緯 を調べた人がいるのか気になる。
    [1] https://lore.kernel.org/all/20240120215012.129529-1-fw@strle...

    • 元のコミットはもう 10 年以上前のコミット なので、時の流れに埋もれてしまった可能性が高い。
      昔の netdev リストを探してみたが何も見つからず、コミッターの Pablo Neira Ayuso に直接送られたパッチだった可能性もある。
      元の作者は今では敬遠される人物となった Patrick McHardy で、Pablo が覚えているかメールから見つけ出さない限り、正確なユースケースを基本的な調査だけで突き止めるのは難しそうだ。
    • 関連コミットはこれ: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/lin...
      netfilter: nf_tables: accept QUEUE/DROP verdict parameters で、ユーザー空間が QUEUEDROP の判定にキュー番号や errno コードを指定できるようにする内容だ。
  • この記事は セキュリティ文書の書き方 という観点でも非常に印象的だ。
    セキュリティブログを書くとき、「どれだけ多くの背景知識や前提知識を仮定するか」は常に悩みどころだが、アクセスしやすさと書き切れる範囲のバランスを取るのは難しい。
    想定読者を先に定め、十分な背景説明を用意している点が素晴らしく、毎年会う研究者志望の人たちへの案内資料として渡せるようにブックマークした。

  • このエクスプロイトは 非特権ユーザー名前空間 へのアクセスに依存する: sysctl kernel.unprivileged_userns_clone = 1
    Debian/Ubuntu と Arch Linux カーネルの既定値であり、sudo なしで Docker コマンドを実行するような必要がないなら無効にしておくほうがよい。

    • この設定は Docker や Pacman だけのものではない。
      Electron アプリや 1Password などで使われる Chrome サンドボックス にも関係し、サンドボックス補助バイナリは setuid プログラムとしても動作できる。
      Proton も今後ユーザー名前空間を使い始めても不思議ではないので、一般的なデスクトップ Linux では無効にしないほうがよいかもしれない。
      サーバーや特別に強化した Linux では、無効にするのが悪くないことも多い。
    • この設定は root 権限なしでコンテナのようなものを実行できるようにする点では良いが、結局は root 権限昇格の脆弱性 につながってきた前歴があるのが本当に残念だ。
    • 本当の問題はその設定自体ではなく、名前空間は本来権限を落とすための仕組みだという点にある。
      問題は、コンテナを「とにかく動かす」にはネットワーク周りで数多くのハックが必要で、Docker の中核的な売りもそうした危険なハックを肩代わりしてくれることに近い。
      結局どんなコンテナ解決策もそうしたハックを受け入れることになり、正当な名前空間機能がアクセスを減らしても、カーネルがネットワークのハックによって扉を開けてしまう構図になる。
    • 6.1.65 ではそのオプションが見当たらないが、名前が変わったのだろうか。
  • なぜ 非特権ユーザー名前空間 がデフォルトで有効なのか分からない。
    「非特権」名前空間内で実行されるとはいえ、デフォルトでユーザーに iptables や mount などを実行する能力を与える理由が何なのか疑問だ。

    • 非特権ユーザー名前空間は、権限のないプログラムでも サンドボックス を構築できるようにする。
      たとえば Chrome はプロセスサンドボックスを実装するために名前空間を使うが、非特権名前空間がなくても済むように setuid-root バイナリをインストールしている。
      setuid-root バイナリ自体がセキュリティリスクなので、長期的には Chrome がそうしたバイナリをインストールしなくて済む方向が望ましい。
      ただしそのためには非特権ユーザー名前空間が広く提供される必要があり、こうしたバグはその未来を遅らせることになる。
      また Chrome のように名前空間ベースのサンドボックスを構成するプログラムは seccomp も併用し、サンドボックス内のコードが名前空間のような特殊なカーネル機能を使えないようにしていることが多い。
      単一ユーザーのデスクトップでは、ユーザーと root の分離を強く適用する利点はそれほど大きくなく、面白いものの大半は普通のユーザーアカウントからもアクセスできる。
      一方で Chrome のようなサンドボックスはデスクトップの安全性に不可欠なので、単一ユーザーのデスクトップでは非特権ユーザー名前空間を有効にしておくほうが全体のセキュリティは高まると考える。
      マルチユーザーシステムは当然別の話だ。
  • このようなバグさえ繰り返し存在しなければ、非特権ユーザー名前空間は優れたセキュリティ機能だったはず
    たとえば、Flatpak の実行でアプリ分離のためにホストの setuid バイナリを必要としないなら望ましかったはず

    • 問題は**「とにかく動く」哲学**にある
      ほとんどのデフォルト設定は安全ではなく、技術的知識とハードニング作業が必要になる
  • 問題を導入したコミット: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/lin...
    ネストされた switch/case のデフォルト経路に return があるのに、その後の fall-through を期待する構造になっていて奇妙に見える

    • 同じ人物なのか気になる
      https://lwn.net/Articles/882397/
    • リンクを GitHub 形式にすると次のとおり: https://github.com/torvalds/linux/commit/f342de4e2f33e0e3916...
      https://bugs.launchpad.net/bugs/cve/2024-1086
      Linux カーネルの netfilter: nf_tables コンポーネントにあるuse-after-free脆弱性により、ローカル権限昇格が可能
      nft_verdict_init() 関数が hook 判定内の drop error として正の値を許容し、その結果 nf_hook_slow()NF_ACCEPT のように見える drop error を伴う NF_DROP を処理して、double free 脆弱性が生じうる
      f342de4e2f33e0e39165d8639387aa6c19dff660 以降へアップグレードすることを推奨
  • ASLR のような現代的な緩和策があるのに、こうしたエクスプロイトがどうやって可能になるのか気になる
    大学の授業で、特定の Ubuntu バージョンで実行する複数のバイナリを渡され、use-after-free やバッファオーバーフローのようなバグを見つけてエクスプロイトする課題をやったが、本当に難しかった
    欠陥を見つけるのも難しいし、その欠陥で有用なことをする正確なシェルコードを書くのはさらに難しかった
    より難しい段階では ASLR、スタックカナリアのような緩和策も有効になっていて、管理された学生向け環境ですらほとんど不可能に感じられた
    結局、1) エクスプロイト可能な欠陥を見つけ、2) プログラムをただクラッシュさせるのではなく有用なことをする正確なバイナリペイロードを見つけなければならないが、現実世界ではもっと難しいはずで、どうして可能なのか疑問
    [0] https://web.stanford.edu/class/archive/cs/cs107/cs107.1194/a...

    • その授業を受けていた人は経験の浅い初心者で、10〜15週間のコースの中で複数のバグを見つけてエクスプロイトしなければならなかった
      他の授業と並行しながら、1つのバグに使える時間は数時間から数日程度だったはず
      Zerodium は一般的な Linux ローカル権限昇格に5万ドルを支払い、熟練したエクスプロイト開発者のコスト基準ではおおよそ 200 人時を買える
      つまり専門家は初心者の学生より 10〜100 倍多くの時間を使える計算になる
      初めて木工所に行った人と大工、陶芸初心者と職人、新人画家とプロの芸術家の違いを思い浮かべればよい
      そこに時間まで 10〜100 倍上乗せされるようなもの
      [1] https://zerodium.com/program.html
    • 現代的な緩和策によってエクスプロイトははるかに難しくなったが、研究者たちはそれらを回避する方法を見つけ続けている
      最新の保護機構の多くを回避する「古典的」な手法があり、それがなければ新しい攻撃や回避法が登場することもある
      たとえばヒープ保護の回避については how2heap を参照でき[0]、KASLR 回避のエクスプロイト例もあり[1]、今回のエクスプロイトはdirty pagetable手法を使っているように見える[2]
      緩和策が追加され、研究者がそれを回避するいたちごっこが続く構図になっている
      [0] https://github.com/shellphish/how2heap
      [1] https://www.willsroot.io/2022/12/entrybleed.html
      [2] https://pwning.tech/nftables/#452-the-technique
    • 短く言えば、上手い人もいれば、運のいい人もいて、上手くて運もいい人もいる
      こうしたものを見つけるには最後のタイプが 1 人いれば十分
      今では本当に難しく、緩和策を無効にしても脆弱性を見つけてエクスプロイトを書くのは依然として簡単ではない
      ただし、こうした脆弱性を見つける多くの人はチームで作業し、ファジングを並列化し、知識や別のエクスプロイトを組み合わせたりチェーンしたりできる
      一部の研究者の専門性と才能は驚くほど高く、この分野では数年から数十年の経験が非常に大きな価値を持つ
    • 似たような授業で、バイナリを渡されてバグを探す課題をやったことがあるが、やはり難しかった
      しかし、今見つかる主要なバグが十分な資金を持つチームや国家支援のアクターによって見つけられていると考えると、人員と資源を大量投入して既存の緩和策を回避できるという点はより理解しやすい
    • リポジトリにリンクされているブログ記事には、KASLR に関する別セクションがある
  • Ubuntu によれば、すべてのLTS リリースが影響を受け、現在のパッチ済みカーネルでは修正されている: https://ubuntu.com/security/CVE-2024-1086
    Focal は 5.4.0-174.193、Jammy は 5.15.0-101.111、Mantic は 6.5.0-26.26 で修正済み
    拡張サポートを使っている場合は Xenial と Bionic も含まれる

  • 脆弱な Debian システムで実行してみたところ、権限昇格はできなかったが、2回目の実行でシステム全体が停止した
    1回目の実行は単に失敗しただけなので、それでも時間をかけてパッチを当てる価値は十分にある

  • 現在のカーネル設定は /boot/config/proc/config.gz のようなファイルで確認できる