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 アセンブリルーチンに存在します:
- このルーチンは、スタックポインタ (
%rsp) をアクティブスタックの新しい底に更新します。 %rspが更新されると、ucontext_t構造体はアクティブスタックまたは「レッドゾーン」(カーネルによって保護される%rsp以下の 128 バイト)の一部ではなくなります。- この正確な瞬間に
SIGUSR2などのシグナルが配信されると、カーネルは%rsp-128にシグナルフレームを構築し、ucontext_tメモリを上書きする可能性があります。 - 破損したメモリから復元された命令ポインタ (
%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 プロジェクトにアップストリームし、より広いコミュニティ向けにバグを解決しました。