OpenAI コアダンプ疫学: 18 年前の libunwind バグを修正

OpenAI コアダンプ疫学: 18 年前の libunwind バグを修正

OpenAI は、ChatGPT のデータプラグインと会話検索に使用される C++ ベースのデータインフラストラクチャサービスである Rockset 内で、説明不能なクラッシュのシリーズを解決しました。調査の結果、単一の問題のように見えていたものが実際には 2 つの無関係なバグであることが判明しました。特定の Azure ホストでのサイレントなハードウェア破損と、GNU libunwind ライブラリにおける長年にわたるレース条件です。

バグ 1: サイレントなハードウェア破損

1 つのクラッシュクラスターは、CPU が計算を誤って実行していた単一の物理 Azure ホストに遡ることができました。これにより、実行中にスタックポインタ (%rsp) がずれ、関数返却時にクラッシュが発生しました。

この破損はサイレントで、制御された環境では再現できなかったため、OpenAI はホストをブロックリストに追加することで問題を緩和しました。今後の発生を防ぐため、チームは致命的なシグナルハンドラーを改善し、ログにレジスタの状態を含めることで、フルコアダンプを必要とせずに迅速な検出を可能にしました。さらに、コントロールプレーンを更新し、VM のリサイクルよりも再利用を優先することで、問題のあるノードの検出を容易にしました。

バグ 2: 18 年前の GNU libunwind レース条件

技術的メカニズム

GNU libunwind がアンワインド転送を実行するとき、望むレジスタの状態を保持するためにスタック上に ucontext_t 構造体を合成します。脆弱性は _Ux86_64_setcontext アセンブリルーチンに存在します:

  1. このルーチンは、スタックポインタ (%rsp) をアクティブスタックの新しい底に更新します。
  2. %rsp が更新されると、ucontext_t 構造体はアクティブスタックまたは「レッドゾーン」(カーネルによって保護される %rsp 以下の 128 バイト)の一部ではなくなります。
  3. この正確な瞬間に SIGUSR2 などのシグナルが配信されると、カーネルは %rsp-128 にシグナルフレームを構築し、ucontext_t メモリを上書きする可能性があります。
  4. 破損したメモリから復元された命令ポインタ (%rip) が読み込まれると、プログラムはクラッシュし、しばしば NULL に戻ります。

レースウィンドウと確率

レースウィンドウは約 1 命令分の幅で、おおよそ 100 ピコ秒と推定されます。極めて狭いにもかかわらず、Rockset の環境における以下の 3 つの要因により、クラッシュの頻度が運用上可視になりました:

  • 高い例外発生率: Rockset は内部のインジェストバックプレッシャーに例外を使用し、過負荷状態のホストでは毎秒最大 $10^4$ 例外をスローします。
  • 高いシグナル発生率: coarse_thread_cputime_clock は CPU 時間の数ミリ秒ごとに SIGUSR2 シグナルを配信します。
  • スタック使用量の増加: SIGUSR2 ハンドラーの最近のアップデートで timer_getoverrun の呼び出しが追加され、ハンドラーが使用するスタック量が増加し、古い ucontext_t メモリを上書きしやすくなりました。

解決策と緩和策

OpenAI は、GNU libunwind から libgcc のアンワインダーに切り替えることで libunwind の問題を解決しました。これにより、大規模な VM でのロック競合が減少し、パフォーマンスも向上しました。さらに、OpenAI は自己完結型の再現器と修正を GNU libunwind プロジェクトにアップストリームし、より広いコミュニティ向けにバグを解決しました。

Sources