
2026/10/03 21:19
インフェリッドが暴れだした
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
「野生ポインタ」という重大なセキュリティ欠陥が、Infidel インタープリタ内で発見されました。このバグは、ゲームをクラッシュさせるかデータを破損させる可能性があります。問題の発生原因は、コンパイラが誤ってメモリアドレス 11129(DESERT-TABLE アレイ)をインデックス番号(30/hex 1e)として処理し、特定の実在地点として扱わなかったことにあります。この誤解釈は、ZIL ルーチン
DESERT-TO-TABLE 内で起こり、ローカル変数 TBL が明示的にグローバルアドレスに設定される代わりにデフォルト値で初期化されます。その結果、データの書き込みによって未使用のメモリブロック(アドレス 30–63)が上書きされ、ENDLESS-DESERT ルームを離れる前に 9 つ以上のオブジェクトをドロップすると、最後のオブジェクトのデータがアドレス 64 から始まる短縮語テーブル(テキスト圧縮に使用)を破損します。この脆弱性は早期のテスト段階から存在していたにもかかわらず隠されており、プレイヤーは通常、オーバーフローをトリガーするだけの数のアイテムをドロップしないため、文字列データを上書きしたときにテキスト表示ルーチンで遭遇すると、無意味なテキストを表示(例:"You sands" ではなく "the sands")したり、クラッシュを引き起こしたりするという深刻なランタイム不安定性として表面化します。開発者は、この問題を解決するためには、コードを更新して TBL を正しいグローバルアドレスに明示的に設定する(<SET TBL ,DESERT-TABLE> を使用すること)、またはデフォルト値に依存する代わりに引数として渡す必要があります。同様のエラーは他のプロパティテーブルルーチン(PROP-TO-TBL、TBL-TO-TABLE)にも存在しますが、現在その影響は限定的です。これは掘削メカニズムが無効になっているためです。この核心の問題を解決することは、Infidel の全バージョンにおける総合的な信頼性と安定性を確保するために不可欠であり、Macintosh 版のアップデートを含む元のリリースにも適用されます。本文
『インフィデール』史上最大級のコンパイラバグ:メモリ破損と略語データの破壊
『インフィデール』は一般公開されましたが、数週間前から開発に没頭しておりました。最近、ゲームを遊んでいるだけでは検出しにくい重大なバグを発見しました。これは私が唯一所有する**「記憶レベルのデバッグ機能(デバッガー)」**があったためです。
なぜこれが「重大」なのか
多くのプレイヤーは gameplay に影響がないため「些細なバグ」と思いがちですが、C プログラマーとしての倫理観と以下の理由から深刻です。
- ワイルドポインタ問題: 未定義変数への参照により、意図しないメモリ領域への乱書き(スクリブル)が発生しています。
- コンパイラの欠陥: これは単なる実装ミスではなく、コンパイラ自体がエラーを処理していない事実が重なります。
バグの背景:エンドレス・デザート仕組み
『インフィデール』はエジプトの砂漠を舞台にしています。
- マップ構造:
- 西側:ナイル川
- 東側:3×3 のグリッド状に配置された 9 つの地点(その一つに埋もれたピラミッドあり)
- 他方への移動・キャンプからの離脱:公式には「砂漠で道に迷った」と判定されます。
- 実装技術:
- 未地図化の領域はすべて**
**という一つの部屋として表現されます。ENDLESS-DESERT - プレイヤーが他のエリアに戻るのではなく、別の方向へ進もうとすれば即座に
ルームに戻ります。ENDLESS-DESERT
- 未地図化の領域はすべて**
- オブジェクト管理:
- 砂漠で遺棄したアイテムの座標情報は**
**という配列に記録されます。DESERT-TABLE - 同じ座標に戻ると、そのアイテムがシャッフルされて再表示されます。
- 砂漠で遺棄したアイテムの座標情報は**
動作例
プレイヤーは同じ
ENDLESS-DESERT ルーム内にいても、遺棄したアイテムの違いで「場所が違う」錯覚を与えます。
コマンド実行:
> DROP AXE, SHOVEL pick axe: Dropped. shovel: Dropped. > EAST (丘から強風が吹き、視界が遮られる) > WEST (拾われた斧がある新しい場所と錯覚するが、実際は同じ部屋)
問題点:DESERT-TO-TABLE ルーチンの欠陥
この仕組みを動かす
DESERT-TO-TABLE ルーチンには致命的なバグが含まれています。
1. ソースコード上の問題
以下のコードでは、配列への初期化処理が不十分です。
<ROUTINE DESERT-TO-TABLE ( SLOC "AUX" (TBL ,DESERT-TABLE) ; デフォルト値としての利用 (CNT 0) (F <FIRST? ,ENDLESS-DESERT>) N) <REPEAT () <COND (.F <SET N <NEXT? .F>>) (ELSE <RETURN>)> <COND (<EQUAL? .F ,WINNER>) (<FSET? .F ,TAKEBIT> <REPEAT () <COND (<==? <GET .TBL .CNT> 0> ; 空のスロットを探す <PUT .TBL .CNT .SLOC> <PUT .TBL <+ .CNT 1> .F> <SET CNT <+ .CNT 2>> <REMOVE .F> <RETURN>) (ELSE <SET CNT <+ .CNT 2>>)>>)> <SET F .N>>>
- 問題箇所:
で、グローバル変数"AUX" (TBL ,DESERT-TABLE)
を直接デフォルト値として参照しています。DESERT-TABLE
2. コンパイル結果の分析(ディスアッセンブル)
ZIL コードをマシンコード(Z マシン)へコンパイルすると、以下の挙動が見て取れます。
Routine 109bc, 5 locals (0000, 001e, 0000, 0000, 0000) ; ローカル変数定義 109c7: GET_CHILD ENDLESS-DESERT -> .F ; オブジェクトリスト取得 109cb: JZ .F [TRUE] RTRUE ; ループ終了条件確認 109ce: GET_SIBLING .F -> .N ; 次のオブジェクトへ ... (中略) ... 109dd: LOADW .TBL, .CNT -> -(SP) ; デフォルト値 TBL を読み込む 109e1: JZ (SP)+ [FALSE] 109fb ; もし TBL が NULL であれば早期終了 109e4: STOREW .TBL, .CNT, .SLOC ; データを書き込み開始
致命的な発見:グローバル変数の誤処理
- 期待される挙動:
にTBL
のアドレス(11129)が初期化されるべき。DESERT-TABLE - 実際の挙動: ZIL コンパイラは、ヘッダー行のローカル変数定義を無視し、グローバル変数の番号を使用している。
- グローバル変数番号:
は 15 番目 → ヘックス表記でDESERT-TABLE
(30)1e - 初期化値:
に 30 が書き込まれている(正しくは 11129)。TBL
- グローバル変数番号:
; 実際には TBL = 30 で初期化されている ; 配列はアドレス 11129 から始まるはずだが、30 を書き込み先として扱われる
結果: オブジェクトの座標や ID が書かれた際、本来の配列(アドレス 11129)ではなく、**メモリ上の別領域(アドレス 30 以降)**が上書きされます。これは「配列溢れ」以上の破壊行為です。
悪化:略語データの破損
メモリの先頭部分(アドレス 0〜63)はヘッダーや略語データで構成されています。
- アドレス 0〜29: Z マシンバージョン 3 で有効な領域。
- アドレス 30〜63: 未使用領域(ただし、重要なデータが存在する)。
- アドレス 64 以降: 略語データ(単語リストの置換テーブル)の開始。
シナリオ:9 つのアイテムを遺棄する場合
砂漠に 9 つのオブジェクトを置いて離れると、最後の書き込みが
ENDLESS-DESERT のキャパシティ管理ではなく、アドレス 30 の領域から書き込まれます。
- 通常の挙動: オブジェクトは正しいテーブルに保存され、後で回収可能。
- バグ発生時:
- 書き込み先が「未使用のメモリー領域(アドレス 30)」へずれる。
- その領域には略語テーブルが存在する可能性がある(またはヘッダーと重なっている)。
デモンストレーション:破損したテキスト出力
テストにおいて、最後に配置されたオブジェクトが略語テーブルの上書きを行った際、以下の異常なメッセージが表示されました。
コマンド:
EAST (遺棄した場所に戻る)
通常出力:
砂漠 ここには斧がある。
バグ発生時の出力:
砂漠 あなたは広大な砂と熱の荒廃した不毛地帯にいる。小さなトカゲが頭を上げて「君」という言葉を発し、「タイムズスクエアまでの最短ルート」について尋ねる。あなたは一瞬困った顔をし、適切な地下鉄路線を勧めようとした時、そのトカゲは消えてしまっている。
- 原因: 略語リストの最初の単語
が、破損データに上書きされてthe
に置換されたため、意味が通じない文章が出力されました。You - コンパイルエラーなし: 終止符がない破損データを出力ルーチンが処理し、次の有効な文字列へ進んでしまいます。
さらに別のテストでは、インタープリター(ゲームそのもの)をクラッシュさせることに成功しました。これは初期のビットパターンを上書きし、無限ループや読み込みエラーを引き起こしたためです。
結論と考察
バグの実態
- 誰にも発見されなかった: リリース後、多くのプレイヤーがこのバグの影響を受けていました。しかし、「砂漠に 9 つ以上のアイテムを置く」という行動は稀であり、誰も試みませんでした。
- テストの欠落: エンドレス・デザートでの長時間滞在や大量のアイテム遺棄についてはプレイテストが不足していました。
修正策
バグはシンプルです。デフォルト値の使用を避け、明示的に初期化すれば解決します。
修正パッチ案:
<ROUTINE DESERT-TO-TABLE (SLOC (DESERT-TABLE) ...)> ; デフォルト値削除 <SET TBL ,DESERT-TABLE> ; 関数名体内で明示的に初期化 ...
残された課題
- PROP-TO-TBL: 同じく
テーブル(アドレス 11329、グローバル番号 22)でも同様のバグが存在する可能性が高いです。これらは「砂の穴」を管理する処理に関連していると思われますが、ゲームでは掘ることを禁止しています(残骸コード)。PROP-TBL - 今後の調査: 他のインフィデール系ゲームやアップデート版でも、コンパイラによるグローバル変数の扱いに問題がないか確認が必要です。
このバグは、コンパイラが「デフォルト値としてグローバル変数を使う」というケースを想定していなかったことが原因です。細部への配慮が典型例ですが、メモリ安全性については致命的な甘えがありました。