この記事の要点
1984年にKen Thompson(UNIXの生みの親のひとり)が示した「信頼を信頼する(trusting trust)」攻撃は、長らく「汚染されたコンパイラ」に固有の脅威だと考えられてきた。ソースコードをいくらレビューしても見つけられない裏口を、コンパイラ自身が自分の再ビルドのたびに埋め込み直す、という有名な思考実験である。
2026年7月に公開された論文 Trusting-Trust Attack against an Entire Linux Distribution through Binary Manipulation(Julien Malka、Aman Sharma、Martin Monperrus、Stefano Zacchiroli、Théo Zimmermann)は、この前提を正面から覆した。コンパイラではなく、ソースコードを一切読まず生成もしない、ただの後処理ユーティリティ strip を使って、NixOSというLinuxディストリビューション全体のビルド成果物ほぼすべてに裏口を仕込めることを、実際のnixpkgsリビジョン上で実証している。
日本のエンジニアにとってこれは他人事ではない。私たちの誰もが、監査したことのない既製バイナリ(Dockerのベースイメージ、apt/dnfで降ってくるツールチェーン、ベンダー提供のSDK)を毎日ビルドの土台にしているからだ。本記事は、この攻撃がなぜ再現可能ビルドでも検知できないのか、そして現場で今日から効く防御は何かを、一次ソースを統合して再構成する。
なお前回扱ったRustクレートarrayrefの供給網攻撃は「悪意あるソースを引き込ませる」攻撃だった。今回は正反対で、ソースは最後までクリーンなまま、ツールチェーンの土台バイナリだけが汚染される。攻撃の層が一段深く、防御の考え方もまったく異なる。
何が起きたのか
論文の主張を一言でまとめると、こうなる。トラスティングトラスト攻撃はコンパイラに限定されない。ELFバイナリ(Linuxの実行ファイル形式、Executable and Linkable Format)を触るだけの strip でも、完全な自己増殖する裏口を作れる。
strip は、コンパイル済みバイナリからデバッグ情報などを削り落としてサイズを小さくする、どのビルドでも当たり前に走る道具である。ソースは見ない。コードも生成しない。だからこそ誰も警戒しない。ここが突かれた。
攻撃者に与えられる能力は、驚くほど小さい。論文の脅威モデル(想定する攻撃者の前提)はこうだ。
- 攻撃者はディストロのブートストラップseed(後述する、最初の信頼された既製バイナリ群)のうち、実行ファイルをたった1個だけ差し替えられる
- 攻撃者はディストロのレシピやパッケージのソースは一切書き換えられない
つまりソースツリーは完全にクリーンなまま、strip バイナリを1個すり替えるだけでよい。それだけで、最終的に出来上がるグラフィカルインストーラの、あらゆる言語・ツールチェーン由来のバイナリほぼすべてに裏口が入り、しかもビルドは1個も失敗しない。論文はこれを実在のnixpkgsで動かして見せた。
Hacker Newsの議論スレッドでは、この一般性を突いたコメントが目を引いた。裏口を仕込めるのは strip だけではない。strings、objdump、nm、ldd など、ELFバイナリを触る道具はどれも同じ運び屋になりうる、という指摘である。攻撃の本質は strip そのものではなく、「ソースを見ない信頼された後処理ツール」という空白地帯にある。
技術的な核心 ― なぜ「土台のバイナリ」が急所なのか
ブートストラップseedという盲点
まずブートストラップ(bootstrap)という言葉を噛み砕く。ディストロは「ソースからすべてをビルドする」と言うが、最初のコンパイラをコンパイルするコンパイラはどこから来るのか、という鶏と卵の問題がある。この最初の一歩のために、多くのディストロはあらかじめビルド済みのバイナリ一式を外部からダウンロードして使う。これがバイナリseedである。
seedは定義上、ソースから検証されていない。ここに1個でも汚染バイナリが紛れれば、そこから作られるすべてが汚染されうる。論文が突いたのはまさにこの一点だ。攻撃者はseedの中の strip を、後述の細工を施したものに差し替えるだけでよい。
自己増殖の仕組み ― 「stripでstripをstripする」
トラスティングトラストが恐ろしいのは、汚染ソースを消しても攻撃が生き残る点にある。論文はThompsonのオリジナルと自分たちの手法を、ひとつの表で対応づけている。
| 役割 | Thompsonのコンパイラ | 本論文のstrip |
|---|---|---|
| 変換 | ソースをコンパイル | ELFをstrip |
| 認識 | ソースのパターンで判定 | 実行中の名前(basename)で判定 |
| 埋め込み | コード生成で仕込む | バイナリ書き換えで仕込む |
| 後継エッジ | コンパイラをコンパイル | stripでstripをstripする |
一番下の「後継エッジ(successor edge)」が肝である。汚染された strip は、ビルドの過程で次世代の strip バイナリを strip する。そのとき、ただ縮めるだけでなく、自分と同じ裏口を相手に注入する。こうして世代から世代へ、ソースはクリーンなまま裏口だけが受け継がれていく。
NixOSのパッケージビルドは、クリーンなソースからコンパイルするビルド段階のあと、インストールされたバイナリに strip をかけるfixup段階を必ず持つ。攻撃はこのfixup段階に相乗りする。だから特別なパッケージを狙う必要すらなく、あらゆるパッケージが素通りで汚染される。
ELFに寄生する3つの書き換え
では汚染 strip は、どうやって成果物のELFに裏口を埋め込むのか。論文は3つの単純なELF書き換えだけで実現している。噛み砕くと以下のとおりだ。
- Append(追記)。ファイル末尾に、裏口の実体(ペイロード)を新しい領域として付け足す
- Repurpose(転用)。使われていない
PT_NOTE(注釈用の、実行されないメタデータ領域)のプログラムヘッダを、実行可能なPT_LOADセグメントに書き換える - Redirect(付け替え)。ELFヘッダの
e_entry(プログラムの開始アドレス)とe_shoff(セクション表の位置)を、追記したペイロードの側に向け直す
擬似コードで流れを示すとこうなる。
# 汚染stripが出力ELFに対して行うこと(概念)
def infect(elf):
payload_off = append(elf, PAYLOAD) # 末尾にペイロードを追記
seg = find_spare(elf, type="PT_NOTE") # 未使用のNOTE領域を探す
seg.type = "PT_LOAD" # 実行可能セグメントに転用
seg.flags = "R+X"
seg.vaddr = payload_off
elf.header.e_entry = payload_off # 起動時にまずペイロードへ
# ペイロードは処理後、本来のエントリポイントへ制御を返す → 挙動は変わらない
論文は2つの実装方式を比較している。ひとつは strip を丸ごと別プログラムに置き換え、その中に本物の strip を埋め込むwrapper方式。もうひとつが実際に採用されたparasite(寄生体)方式で、本物の strip はそのまま残し、小さなエントリポイント用のコードだけを付け足す。parasiteのほうが、感染後も見た目が本物の strip のままで、本来の挙動もそっくり引き継ぐため、ビルドを壊しにくく発見されにくい。
攻撃者から見た「うまみ」
Thompsonのコンパイラは、自分がコンパイルする言語で書かれたプログラムしか汚染できなかった。ところが strip はELFバイナリを書き換えるので、C、Rust、Go、あらゆる言語・ツールチェーンの出力に等しく手を出せる。運び屋が1個で、ディストロ全体の言語エコシステムを横断できてしまう。これが今回の攻撃の到達範囲の広さである。
なぜ再現可能ビルドでも検知できないのか
ここが本記事で最も強調したい点だ。多くのエンジニアは「再現可能ビルド(reproducible builds、同じソースから誰がビルドしてもビット単位で同一のバイナリが出る性質)を徹底すれば、こういう仕込みは弾ける」と考える。だが、この攻撃はそのすぐ外側を通る。
論文の関連研究(7.2節)が、防御側の2本柱をこう位置づけている。
- Diverse Double-Compiling(DDC、David A. Wheelerが提唱した多様な二重コンパイル)。独立した第2のコンパイラで対象コンパイラを再ビルドし、結果が一致するか照合する。片方のツールチェーンにだけ潜む裏口は不一致として露見する
- 再現可能ビルド。ビルドがビット単位で同一の出力を生むようにし、独立した再ビルド者がバイナリとソースの対応を確認する
問題は、この2つがどちらもソースからバイナリへの経路、すなわちコンパイラの正しさを守るものであって、コンパイラが仕事を終えたあとに走る後処理ユーティリティを検証しない点にある。攻撃はまさにその隙間に住んでいる。汚染 strip は、最初のビルドでも独立した再ビルドでも同じように信頼され、同じように呼ばれる。DDCが多様化するのはコンパイラであって strip ではないので、両系統が同じ汚染 strip を通れば、出力はビット単位で一致してしまう。再現できることが、逆に「正しい」という誤ったお墨付きになる。
論文はこの新規性を、2025年に話題になったnpmの自己増殖ワームShai-Huludと対比して際立たせている。Shai-Huludは、メンテナの資格情報を盗んで、その人が持つ他のnpmパッケージにトロイの木馬入りの版を再公開して横に広がる。しかしそれは横方向かつソースレベルの伝播であり、感染パッケージには依然としてレビュー可能な悪意あるソースが載っていて、クリーンな再ビルドで消える。今回の攻撃はそうではない。ビルドそのものを通じて自己増殖し、悪意あるソースをどこにも持たず、汚染seedが依存グラフから外れたあとも生き残る。ソースレビューでも、ビルド手順が正直に実行されたことの証明(per-artifact attestation)でも捕まえられない。
日本の実務への示唆 ― 監査しない「最初のバイナリ」を洗い出す
この攻撃は研究上の話に見えるかもしれないが、写像すると身近な急所が浮かび上がる。私たちのビルドにも、必ず「一度も中身を監査していない最初のバイナリ」が存在する。
- Dockerの
FROM node:22やFROM ubuntu:24.04といったベースイメージ。中のツールチェーンは既製バイナリのかたまりである - CIランナーにプリインストールされたコンパイラ、リンカ、
stripを含むbinutils - ベンダーや社内配布のプリビルドSDK、クロスコンパイル用ツールチェーン
- 長期間キャッシュされ、誰も再検証しないビルドキャッシュ層
これらはいずれも、今回の脅威モデルでいうseedに相当する。差し替えられても、ソースレビューやテストでは見えない。では現場で何ができるか。方向性は論文とNixコミュニティの動きが示している。
第一に、信頼するバイナリseedを可能な限り小さくすることだ。信頼の起点が小さいほど、監査も検証も現実的になる。実際、NixコミュニティはnixpkgsのPR #479322で、x86_64などのブートストラップseedを、束ねられた既製バイナリ群からわずか181バイトまで縮める作業を進めている。この方向を突き詰めたのがBootstrappable Buildsや、最小限のバイナリから全ソースを積み上げるfull-source bootstrapの取り組みである。土台を小さくする、という発想を自分たちのビルドにも持ち込みたい。
第二に、Reproducible Buildsを導入しつつ、その限界を正しく理解することだ。再現可能ビルドは価値が高いが、今回のようにコンパイラの後段を通る攻撃は素通りする。ビット単位一致は「ソースからバイナリへの経路が同じ」ことしか保証しない。検証の網を、コンパイラだけでなく strip や objdump といった後処理ツールにも広げる必要がある、というのが論文の突きつけた宿題だ。
第三に、ビルドの出所を機械可読に残すことだ。どのツールチェーンで、どのseedから、どの手順でビルドしたかをSLSAのようなprovenance(来歴)フレームワークで記録しておけば、汚染seedが判明したときに影響範囲を追跡できる。attestationは今回の攻撃を検知はできないが、事後の封じ込めには効く。
実務のチェックリストとして、次を回したい。
□ 自社ビルドの「信頼の起点」を書き出す
(ベースイメージ / プリビルドツールチェーン / CIキャッシュ)
□ その起点のハッシュを固定し、出所(誰がいつ何から作ったか)を確認する
□ ベースイメージは可能な限り小さく、再現可能なものを選ぶ
□ 重要なプロダクトは reproducible builds で独立再ビルドと照合する
□ ただし「再現できた=安全」と早合点しない。stripなど後処理ツールは網の外
□ SLSA provenance を残し、seed汚染が判明したときの影響追跡経路を用意する
なお国内の脅威動向としても、サプライチェーンの侵害はIPA 情報セキュリティ10大脅威 2026で組織向けの上位に居座り続けている。インシデントに直面した際の相談窓口としてJPCERT/CCも押さえておきたい。
まとめ ― エンジニアへのアクションアイテム
今回の論文が崩したのは、「自分はコンパイラを自作していないから、トラスティングトラストは関係ない」という広く共有された油断である。ソースを一切見ない strip ひとつで、ディストロ全体が自己増殖する裏口に落ちうる。しかもそれは、私たちが最良の防御と信じている再現可能ビルドの、ちょうど外側を通っていく。
行動に落とすなら、三点だ。ひとつ、自分のビルドの「信頼の起点=最初の既製バイナリ」を具体的に書き出し、それが差し替えられたら誰も気づけない現実を直視すること。ふたつ、信頼するバイナリの塊を小さくし、出所を固定・記録すること。みっつ、再現可能ビルドやattestationの守備範囲を過信せず、コンパイラの後段のツールまで検証の網を広げる議論を、社内で始めることだ。
Thompsonが1984年に投げかけた問い、すなわち「自分が全部書いていないコードを、どこまで信頼できるのか」は、42年を経てなお私たちの足元に突き刺さっている。今回はその刃が、コンパイラの外にまで及ぶことが示された。
参考ソース
- Trusting-Trust Attack against an Entire Linux Distribution through Binary Manipulation(arXiv abstract)(本記事の一次ソース。strip寄生・3つのELF書き換え・NixOSブートストラップでの実証)
- 同論文 PDF
- Hacker News 議論スレッド(id=49575515)(strings/objdump等への一般化・Nixの181バイトseed・DDCへの言及)
- nixpkgs PR #479322(ブートストラップseedを181バイトへ縮小)(信頼の起点最小化の実例)
- David A. Wheeler「Countering Trusting Trust through Diverse Double-Compiling」(DDCの一次資料)
- Reflections on Trusting Trust(Ken Thompson, 1984, CMUホスト版PDF)(古典の原典)
- Bootstrappable Builds(バイナリseed最小化・full-source bootstrapの取り組み)
- Reproducible Builds(ビット単位再現の一次情報)
- SLSA(Supply-chain Levels for Software Artifacts)(ビルド来歴・attestationのフレームワーク)
- NixOS 公式(対象ディストロ)
- IPA 情報セキュリティ10大脅威 2026(国内脅威動向)
- JPCERT/CC(国内インシデント相談窓口)