
2026/07/02 7:39
不正な手法に関する C プログラムコンテスト
RSS: https://news.ycombinator.com/rss
要約▶
日本語訳:
Nuclear Threat Initiative (NTI) が主催した 2015 年の Underhanded C コンテストは、微妙なコードの欠陥が高リスクである核検証システムを侵害し、通常の動作を停止させずに損傷させることができることを浮き彫りにしました。核査察官の視点から 40 以上の応募作品を審査したこのコンテストでは、エンジニアリング可能な条件下で、秘密裏に偽陽性結果を返す
match() 関数を作成することに焦点が当てられました。優勝したのは Linus Åkesson 氏であり、彼は不整合な typedef が原因で 8 バイトの double が 4 バイトの float のペアとして誤って解釈される「型混同」バグを利用しました。これはスペクトラムデータを効果的に「圧縮」することを可能にしました。このアプローチは外部からの改ざん(例えば、システムクロックの変更やファイルパーミッションの変更)を必要とせず、標準的な前処理フィルタには見えないため、特に現実的であると評価されました。他の注目に値する応募作品には、Poisson 分布の計算による NaN 投毒、ロギングコード内のバッファオーバーフローへの攻撃、関数名のタイプオプグラ (sinf と isinf)、浮動小数点精度の操作、x86 オPCODE として機能する Unicode バイト、負の時間微分の悪用などが含まれました。このコンテストは、高度な検証ソフトウェアが明白な攻撃ではなく、微小な論理的誤りに脆弱であることを示し、正確なデータ処理に依存する業界に対して、単純な入力検証だけではエンジニアリングされたエッジケースに対抗できず、重要インフラにおいて静かなシステム故障につながる可能性があるという警告を発しました。本文
2015 年度「ずる賢い C コード(Underhanded C)」コンテスト結果発表
投稿者: XcottCraver
公開日時: 午前 9:55
すべての応募作品を検査し、2015 年度の優勝者と上位入選者を発表いたします。今年は 40 件以上の高品質な応募があり、候補者リストが長引いた結果となりました。詳しくはアンカーリンクをご覧ください:
背景と提携
今年のコンテストは、核脅威対策イニシアチブ(NTI) と提携・協力の上で開催されました。NTI は、核兵器や生物兵器の脅威を軽減するための非営利かつ政党中立な組織です。 安全なソフトウェア開発において、単なる慎重さだけでなく、新たな研究が必要なことが再認識されています。
また、来週の水曜日(2 月 9 日午後 1 時) に、テープレコーダーやコモドール・PET CBM を愛用する皆様との Live AMA(Ask Me Anything)を開催します。ご質問をどうぞ!
チャレンジ問題の概要
- 目的: 二つの国が互いに核弾頭を解体していることを保証するために使用される「情報バリアー付き検出器」を悪意のあるコードにするコンテスト。
- 入力:
match(double * test, double * reference, int bins, double threshold)
: テスト用弾頭のガンマ線スペクトルデータtest
: 参考弾頭のスペクトルデータ(信頼可能)reference- 二つの配列が十分に類似している場合は
、それ以外は1
を返す。0
- 条件:
- ホスト国によって特定の状況(入力操作や環境改ざんなど)でトリガーされる必要がある。
- 通常の状態では正常に機能し、構造的に不自然であるべき。
「NaN バグ」応募作品への概観
多くの応募作品(全体の約 3 分の一)が、浮動小数点計算で NaN(Not a Number)を発生させるトリックを使用していました。
NaN の特性と攻撃手法
やsqrt(-1.0)
のように未定義の計算は NaN を返す。0/0- 重要:
として評価される(多くの言語の仕様)。NaN との比較演算は常に偽(false)
「NaN 投毒(Poisoning)」攻撃の手順:
- プログラムに奇妙な入力が与えられた場合、NaN を導入する。
- その NaN を最終的な比較統計量まで伝播させる。
- 閾値と比較して、偽の比較(NaN であるはずが真となる)を返すようにする。
リアルな攻撃シナリオとの兼ね合い
多くの応募は「ホスト国が入力を弄り込む」という単純な前提で NaN を侵入させられていた。一方で、テスト統計量の計算(割り算、平方根など)に依存して特殊な入力で NaN を発生させる手法も存在した。
Peter Eastman 氏の例: ピアソン相関を計算する際、
test[i] <= baseline[i] とすれば分母が 0/0 となり NaN が発生する。ただし、ゼロ除算に慎重になるコーディング慣習があるため、この手法は露骨である可能性がある。
Sean Egan 氏の例:
sscanf() でテキストファイルから数値を解析し、解析できない項目が入ると memset で埋め尽くした領域が NaN として扱われるようにする手法。
その他の「NaN トリック」作品
Philipp Klenze(可能性比率検定)
- ポアソン分布の下でバケット値の確率を計算することで NaN が発生するように設計。
- バケット値が 2000 に近づいた場合にトリガーされる現実的な攻撃。
- 単に
を計算するのではなく、物理的な意味を持つ数値(短寿命核種など)を利用して NaN へ誘導。0/0
Ghislain Lemaur(ログ処理コード内への侵入)
- バグ:
の間に余計なスペースがあり、ログ文字列のバッファオーバーフローを引き起こす。snprintf - これにより変数
(指数値 2.0)に書き込みが発生。glob.diff_exp - リトルエンディアン表現のため、指数の最初の 2 バイトを上書きすると微細な変化が生じ、
関数が負の数を指数として扱い NaN を返す。pow()
Josh Lospinoso(単純なタイプミス)
の呼び出しからisinf()
が欠落し、代わりにサイン関数'i'
が呼ばれる。sinf(referenceLength)- これによりテストと参照の両方が無限大の場合に誤った判定を行うが、極端な値(桁上がり)が必要でデータ誘発型攻撃としては難しい。
Stephen Dolan(最大アウトライアの除外ロジック)
- ユークリッド距離計算において、最大の平方距離を合計から除外するコード。
- バグ: 浮動小数点演算の性質により、最初の極端な値が合計と等しくなり、それを除外した結果として合計は
となる。0
Matt Bierner(ユニコードアルファベットによる誤作動)
- エラーハンドラを呼び出す際、文字列
を関数として渡している誤り。error_message - 文字列の初期バイトが有効な x86 コマンドとなり、スタックフレームをポップして常に真を返すように巧妙に操作。
Gregory Stewart(データ構造と goto の弄り)
- メモリプールの使い切れ後に、新規割り当て失敗時に初期化されていないノードがリストに残るバグ。
- これにより
とtest
が全て 0 で一致する判定が行われるようになる。reference - 条件: プールを使い切るまで通常の動作をし、その後でトリガーされる(複数のテストを繰り返すことで発生)。
Stephen Oman(負の時間のトリック)
- スペクトルデータの正規化処理で、時間差分
を除算するコードがある。t_seconds - 環境攻撃: ホスト国のシステムクロックを改ざんし、終了時刻が開始時刻より前になるように設定することで、
が負の数になる。t_seconds - これによりカウントの値が反転(負の値)され、通常あり得ない判定条件を満たす。
リアリズムに関する一考察
優勝作品には、現実的な状況下で偶然発生しない偽陽性を許容する能力が必要です。応募は以下のカテゴリに分けられました:
- 非現実的仮定: ホスト国ができることを単純化しすぎている場合(例:入力配列の腐敗)。
- データ誘発型攻撃: 特定の種類のテストスペクトル(スパイクなし、極端な値など)を用意してバグを起こすもの。
- 環境誘発型攻撃: OS の改ざん、物理接続の断絶など、システム環境を変更することでバグを発生させるもの。
環境誘発型攻撃について: 核査察シナリオにおいて、ホスト国がコンピュータを弄ることは許容されますか?
- 環境的な改ざんは微細であり、許可を得られる場合もある。
- 物理的管理上の理由だけで完全な排除は難しい。
- 「現実的」かを一概に判断することは困難だが、追加の仮定に頼らず自立した攻撃を提供できるデータ誘発型攻撃が好ましい傾向にある。
優勝者(Winner)
Linus Åkesson が優勝しました。
概要
Åkesson 氏は、コード全体は非常にシンプルで無害に見えるが、ヘッダーファイルでの
の不一致により重大な型ミスを引き起こす巧妙な攻撃を仕込んでいました。typedef
バグのメカニズム
-
ヘッダーの整合性の欠如:
- ファイル A(主処理)では
をfloat_t
(8バイト) として定義する。double - ファイル B (
) にはspectral_contrast.c
のみを含み、<math.h>
は単精度浮動小数点数として定義される。float_t - これにより、グローバルな
オーバーライドが失われ、関数への渡す型と引数の期待する型が不一致(Double vs Float)になる。typedef
- ファイル A(主処理)では
-
奇妙な解釈の発生:
- 本来 8 バイト分のダブルを処理すべきところが、4 バイト分の単精度として読み込まれる。
- IEEE 浮動小数点数形式のため、指数部のビットがマンサ(小数点以下の値)として誤認される。
- 重要な点: テスト対象のスペクトルは整数カウントで構成されているため、左詰めされたマントサにより、右側のバイトが
として解釈される。0.0
-
結果としての SQUASH 効果:
- 入力されたデータ配列が
のように歪んで解釈される。<SQUASH(val), 0, SQUASH(val2), 0, ...> - この非線形な圧縮変換により、実際のスペクトル形状が大きく変わるはずのデータであっても、検出器は「似ている」と判定してしまう。
- 入力されたデータ配列が
攻撃の手口(レシピ)
- ホスト国は、少量の核分裂性物質を残すだけでよい。
- テストオブジェクトに別の物質を配置して、スペクトルの右側に大きな署名を入れる。
- この場合、最初の総エネルギーテストはパスするが、歪んで解釈されたデータ同士は「一致」するという判定になる。
評価:
- システムクロック改ざんなどの環境攻撃を起こさず実現可能。
- 型混乱という技術的なミスを巧妙に利用しており、コード自体には明らかな悪意がないため見落とされやすい。
- 浮動小数点数のビット表現を理解している天才的な作劇。
おめでとうございます Linus Åkesson さん!あなたは 2015 年度の最もずる賢い C プログラマー です。
入選者一覧(Runners Up)
以下の応募者も優れた作品を提出していました:
Ghislain Lemaur
- ログ処理コード内で
のバッファオーバーフローにより、変数値を改ざんし、指数計算で NaN を発生させる。snprintf - ログファイルの UID 操作によってプログラム動作を停止させるという環境攻撃も含まれる。
Josh Lospinoso
とisinf()
のタイポによるエラーハンドリングの不備。sinf()- データ誘発型攻撃として難しいが、単純なミスを利用した例。
Stephen Dolan
- 浮動小数点の精度制限を利用して、極端な値を除外処理の中で相殺しゼロにする手法。
Matt Bierner
- エラーハンドラ呼び出しにおける文字列と関数の混同。
- ユニコード文字列のコマンド解釈を利用したスタック操作。
Gregory Stewart
- リスト処理ロジックとプール管理の不整合により、初期化されていないノードで常に一致判定を行う。
- 複数回のテスト実行をトリガーにしている。
Stephen Oman
- システムクロックの改ざん(環境攻撃)により時間を負にする。
- その結果としてデータ正規化処理が崩壊し、常に判定パスさせる。