
CVE-2026-39259
プロジェクト: https://github.com/alexfru/SmallerC
SmallerCのscanf実装は、フォーマット文字列内で%sまたは%[が明示的なフィールド幅なしで使用された場合、文字列読み取りに上限を強制しません。ランタイムは、バッファの実際の割り当てサイズに関係なく、空白またはEOFに遭遇するまで宛先バッファへの書き込みを続けます。境界を超えたバイトはスタック上に直接到達し、コンパイラがバッファの上に配置したもの(ローカル変数、保存されたレジスタ、リターンアドレス)を上書きします。
これは新しい脆弱性クラスではありません。無制限のscanf文字列読み取りはC言語の初期から文書化されており、まともな静的解析ツールならどれでも検出します。これをSmallerCの文脈で報告する価値があるのは、まさにターゲット環境です。SmallerCはDOSおよびベアメタル組み込みターゲット向けに設計されています。これらのプラットフォームは、定義上、スタックカナリア、ASLR、NXビット、あるいは現代のシステムで悪用を困難にするあらゆる緩和策を提供しません。堅牢化されたLinuxバイナリで動作するエクスプロイトにするには多大な研究努力を要する同じプリミティブが、フラットで予測可能なスタック上で動作するDOSプログラムでは、はるかに扱いやすくなります。
バッファは16バイトです。入力は20バイトの非空白文字です。%s変換には幅指定子がないため、sscanfは20バイトすべてとヌル終端文字(合計21バイト)を16バイトの割り当て領域に読み込みます。境界を超えた5バイトは隣接するスタックメモリを破壊します。正確に何が破壊されるかは、その関数に対するコンパイラのスタックレイアウトの決定に依存しますが、上書き自体は決定的かつ無条件であり、このコードパスがこの入力で実行されるたびに発生します。
概念実証
#include <stdioh>
#include <stringh>
/*
* Build with SmallerC targeting DOS or bare-metal
* Demonstrates unbounded %s write past a fixed stack buffer
*
* buffer is 16 bytes payload is 20 non-whitespace bytes
* sscanf writes 21 bytes (20 + null terminator) into buffer
* corrupting 5 bytes of adjacent stack memory
*
* To observe the corruption inspect stack memory after the call:
* the 5 bytes immediately above buffer will contain 'A' (0x41)
*/
int main() {
char buffer[16];
char canary[8];
memset(buffer 0x00 sizeof(buffer));
memset(canary 0xCC sizeof(canary)); /* marker to detect overwrite */
printf("[*] canary before: ");
for (int i = 0; i < 8; i++) printf("%02x " (unsigned char)canary[i]);
printf("\n");
sscanf("AAAAAAAAAAAAAAAAAAAA" "%s" buffer); /* 20 bytes into 16-byte buffer */
printf("[*] canary after: ");
for (int i = 0; i < 8; i++) printf("%02x " (unsigned char)canary[i]);
printf("\n");
if (memcmp(canary "\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC" 8) != 0)
printf("[!] stack corruption confirmed canary overwritten\n");
else
printf("[-] canary intact (stack layout placed it elsewhere)\n");
return 0;
}
影響を受けるビルドでの期待される出力:
[*] canary before: cc cc cc cc cc cc cc cc
[*] canary after: 41 41 41 41 41 cc cc cc
[!] stack corruption confirmed canary overwritten
バッファに対するカナリアの配置は、コンパイラのスタックレイアウトに依存します。出力でカナリアが無傷と示された場合でも、上書きは依然として発生しており、バッファの上の別の何かに到達しています。デバッガで実際のスタックフレームを検査し、破損した5バイトがどこに到達するかを特定して、再現コードを調整してください。
明示的に述べておくべき訂正があります。この脆弱性クラスに関する一部の報告では、ペイロード内のヌルバイトの後にターゲットアドレスを追記して(例: "AAAAAAAAAAAAAAAAAAA\x00\x90\x04\x08")、リターンアドレス制御を実証しようとします。これは機能しません。sscanfの%s変換は\x00を文字列終端として扱い、それに遭遇すると即座に読み取りを停止します。ヌルバイト以降のバイトは決して処理されません。実際のリターンアドレス制御を実証するには、ペイロードの重要な部分にヌルバイトを含めずに上書きを届ける必要があります。そのためには、ターゲットバイナリの正確なスタックレイアウト(バッファから保存されたリターンアドレスまでの距離、コンパイラがパディングを挿入したかどうか、適用されるアライメント制約)を知る必要があります。そのどれもが、この再現コードから自動的に得られるものではありません。
この再現コードが明確に立証するのは、破損プリミティブそのものです。領域外書き込みは現実に発生し、再現可能であり、レースコンディションやタイミングに依存しません。スタックレイアウトがビルド間で静的かつ予測可能なDOSまたは組み込みターゲットでは、このプリミティブから実際に動作するエクスプロイトへの橋渡しは、理論上の演習ではなく、現実的な研究努力です。
影響を受けるシナリオは狭いですが、作為的ではありません。プログラムがSmallerCでビルドされ、scanfファミリーの解析で無制限の%sまたは%[指定子を使用して固定サイズのスタックバッファに書き込み、攻撃者が影響を与えられるソースからの入力を受け入れる必要があります。これら4つの条件がすべて同時に成立する必要があります。正しいフィールド幅(char[16]には%15s)を使用するプログラムは影響を受けません。攻撃者が制御する入力を解析しないプログラムは影響を受けません。この問題は、SmallerCのランタイムが欠落した幅の制約を処理する方法の欠陥ですが、アプリケーションコードがその欠陥を信頼できない入力にさらした場合にのみ、セキュリティ上の懸念になります。
アプリケーション側の修正は簡単です。ヌル終端文字の分を残すフィールド幅を指定します。16バイトバッファには%15s、64バイトバッファには%63sです。これは標準的なCの慣行であり、フォーマット文字列構文で完全にサポートされています。SmallerCプロジェクト側では、より永続的な作業として、scanf、sscanf、fscanfにわたって制限付きおよび無制限の%sおよび%[の動作をカバーする回帰テストを追加し、明示的なフィールド幅が実装で実際に尊重されることを検証し、安全でないパターンを目立つように文書化することが挙げられます。フォーマット文字列リテラル内で%sまたは%[がフィールド幅なしで出現した場合に警告するコンパイラレベルの診断は、この種のミスを予防的に防ぎ、ツールチェーンへの有意義な追加となるでしょう。
深刻度は、外部入力が脆弱なコードパスに到達した場合に中(Medium)です。入力がローカルまたは非特権の場合は低(Low)に下がります。ターゲット環境、具体的にはSmallerCが想定するプラットフォームにおける現代のエクスプロイト緩和策の欠如が、これを一般的な「無制限のscanfを使わない」という助言と区別し、純粋にアプリケーション層の誤用として扱うのではなく、プロジェクトレベルで報告する価値があるものにしています。
クレジット: Yousif Wazni