
GNU coreutilsの「split」プログラムにおける脆弱性により、ユーザーが制御するデータを用いたヒープバッファオーバーフローが発生します。
この脆弱性は、 40bf1591bb4362fa91e501bcec7c2029c5f65a43 (2023年3月4日)で導入されました。 修正は c4c5ed8f4e9cd55a12966d4f520e3a13101637d9 (2024年1月17日)でリリースされています。
影響を受けるバージョン: GNU coreutils v9.4; v9.3; v9.2
概念実証:
このリポジトリ内の split_me サンプルファイルを使用して、影響を受けるバージョンでクラッシュをトリガーできます。
split -C 1024 ./split_me
これにより、split がセグメンテーションフォールト(SIGABRT)でクラッシュします。
この脆弱性は、QRコードを使用してエアギャップシステムからデータ抽出を自動化しようとしているときに発見しました。
qrencode で生成されたQRコードの容量は約4000文字であるため、split を多用する必要がありました。
特定のテストケースで、split がセグメンテーションフォールトでクラッシュしました。
GNU coreutils はオープンソースであるため、 バイナリをリバースエンジニアリングする代わりに、ソースを使用してバグを特定できます。 オープンソースプロジェクトでは、バグレポートを可能な限り具体的にすることが望ましく、 理想的にはバグを導入した正確なコミットと行、および修正案を提供します。 これにより、メンテナーはレポートを迅速に検証でき、応答時間が短縮されます。
異なるシステム間でバグを検証しているときに、
クラッシュが比較的新しいバージョンの split でのみ発生することに気付きました。
良質なコミットと不良なコミットがあれば、
コミット履歴の二分探索を実行して、実際にバグを導入したコミットを見つけることができます。
Git にはこのユースケース専用のツール、git bisect が用意されています。
テストするコミットを自動的に提案し、それを良または悪としてマークできます。
最終的に、バグを導入したコミットにたどり着きます。今回の場合は次のとおりです:
commit 40bf1591bb4362fa91e501bcec7c2029c5f65a43
Author: Paul Eggert <[email protected]>
Date: Sat Mar 4 11:42:16 2023 -0800
split: prefer signed integers to size_t
This allows for better runtime checking with gcc
-fsanitize=undefined.
* src/split.c: Include idx.h.
(open_pipes_alloc, n_open_pipes, suffix_length)
(set_suffix_length, input_file_size, sufindex, outbase_length)
(outfile_length, addsuf_length, create, cwrite, bytes_split)
(lines_split, line_bytes_split, lines_chunk_split)
(bytes_chunk_extract, ofile_open, lines_rr, main):
Prefer signed integers (typically idx_t) to size_t.
src/split.c | 105 ++++++++++++++++++++++++++++++------------------------------
1 file changed, 52 insertions(+), 53 deletions(-)
次にプログラムを(理想的にはアドレスサニタイザを使用して)コンパイルし、
クラッシュする正確な行を見つけます。
確認すべき行は約50行だけなので、
バグを特定するのは容易です。
今回の場合、クラッシュは不正なインデックスを使用した memcpy() 呼び出しで発生しました。
そして実際、memcpy() 呼び出しの周辺を確認すると、
その直前でインデックス計算を変更する差分が見つかります:
@@ -816,15 +820,10 @@
/* Update hold if needed. */
if ((eoc && split_rest) || (!eoc && n_left))
{
- size_t n_buf = eoc ? split_rest : n_left;
+ idx_t n_buf = eoc ? split_rest : n_left;
if (hold_size - n_hold < n_buf)
- {
- if (hold_size <= SIZE_MAX - bufsize)
- hold_size += bufsize;
- else
- xalloc_die ();
- hold = xrealloc (hold, hold_size);
- }
+ hold = xpalloc (hold, &hold_size, n_buf - (hold_size - n_hold),
+ -1, sizeof *hold);
memcpy (hold + n_hold, sob, n_buf);
n_hold += n_buf;
n_left -= n_buf;
これらの変更を元に戻して再コンパイルすると、
split はすべてのテストケースをエラーなく処理します。
残っているのは、ロジックを確認してバグを検証し、修正を開発することだけです。