「数値を扱うコードなんてどこにでもある、でもまさかそれがハッキングに使われるとは思わなかった」——整数オーバーフロー脆弱性を初めて学んだエンジニアの多くが、こうした感想を口にします。
ファイルサイズ計算、メモリ確保、ループカウンタ、リクエスト数の集計。アプリケーションの中で数値を扱う処理は無数にあります。そのすべてが潜在的な攻撃面になりうるのが、整数オーバーフローという脆弱性です。
この記事では、整数オーバーフロー脆弱性の仕組みを攻撃者の視点で分解し、過去に起きた重大被害の実例、そして開発者・情シス担当者が今日から実践できる防御手順を解説します。
整数オーバーフローとは?なぜセキュリティ問題になるのか
コンピュータの整数型には、扱える値の範囲に上限と下限があります。たとえば符号なし16ビット整数(uint16_t)が表現できる最大値は65,535(2の16乗 – 1)です。この限界を超えた計算が行われると、値は自動的に「ラップアラウンド」して先頭(0)に戻ります。
これ自体は数値演算の仕様ですが、問題はこの値がセキュリティ上重要な処理——バッファサイズの計算や権限チェック——に使われるときです。
たとえば次のような状況を想像してください。
・ケース1: ファイルサイズを uint16_t で受け取り、そのサイズ分のバッファを確保するコードがあるとします。攻撃者が65,536バイトのファイルを送ると、計算結果は「0」になります。サイズ0のバッファを確保した後に実際のデータを書き込もうとすると、バッファオーバーフローに発展します。
・ケース2: ショッピングサイトの在庫数を符号なし整数で管理している場合、在庫0の商品を「1個追加」すると在庫が最大値(たとえば4,294,967,295)に跳ね上がります。これを悪用して無制限に商品を注文できてしまった実例が過去に存在します。
・ケース3: パケット長フィールドを加算する際にオーバーフローが起きると、意図より短いバッファが確保されてデータの読み書き位置がずれる「オーバーリード」「オーバーライト」につながります。
整数オーバーフローそれ自体は「計算が壊れる」だけです。しかし、壊れた値が後続の処理で信頼されたまま使われることで、深刻な脆弱性に変化します。
攻撃の仕組み――「単純な計算ミス」が重大事故に発展する理由
整数オーバーフローを悪用した攻撃は、一般に次の流れで進みます。
1. 攻撃者による値の観察と境界値テスト
まず攻撃者はターゲットのアプリケーションが受け付ける数値フィールド(ファイルサイズ、個数、オフセット値など)を特定します。Webアプリならリクエストパラメータ、バイナリプロトコルならパケット内の長さフィールドが主なターゲットです。
そして型の上限値付近(65,535、2,147,483,647、4,294,967,295など)や、計算結果が特定の値になる組み合わせを送信して応答の変化を観察します。
2. オーバーフローの誘発
# 攻撃例(擬似コード) # サーバー側のバッファ確保ロジック uint16_t header_size = 100; uint16_t body_size = attacker_supplied_value; # 攻撃者が制御 uint16_t total_size = header_size + body_size; # オーバーフローポイント char *buf = malloc(total_size); # サイズが小さすぎるバッファが確保される memcpy(buf, data, header_size + body_size); # 実際のデータ量でオーバーライト
攻撃者が body_size に 65,436(= 65,536 – 100)を指定すると、total_size は 65,536 になります。uint16_t の最大値 65,535 を超えるため、total_size は「0」に化けます。malloc(0) は実装によって動作が異なりますが、その後の memcpy で大量のデータをほぼサイズなしのバッファに書き込もうとする場合、ヒープ領域が破壊されます。
3. 後続の処理を乗っ取る
オーバーフローが引き起こした「予期しない値」が、その後の処理でどう使われるかが被害の深刻さを決めます。
・メモリ破壊: malloc のサイズがゼロ・マイナスになってバッファオーバーフローに連鎖する
・任意コード実行(RCE): スタックやヒープを制御できれば関数ポインタやリターンアドレスを書き換えられる
・権限昇格: 権限フラグを保持する整数型がオーバーフローして管理者フラグに化ける
・認証バイパス: ループカウンタの誤動作で認証チェックがスキップされる
・サービス妨害(DoS): 無限ループやクラッシュを引き起こす
【参考】符号あり整数(Signed Integer)でのリスク
符号あり32ビット整数(int32_t)の最大値は 2,147,483,647 です。ここに1を加えると規格上は「未定義動作」(C/C++の場合)になり、コンパイラの最適化によって予測不能な結果になります。多くの環境では -2,147,483,648(最小値)になりますが、これを信頼してはいけません。特にサイズ比較でマイナス値が生まれると、「if (size > MAX_ALLOWED)」のようなチェックをすり抜けてしまいます。
具体的な防御手順
1. 安全な整数型の選択と演算前チェック
根本的な対策は、演算結果が型の範囲を超えないかを計算前に検証することです。
# C言語での安全なサイズ計算例 # 危険なコード(オーバーフローチェックなし) size_t total = a + b; # a=SIZE_MAX, b=1 で 0 になる # 安全なコード(加算前に上限チェック) if (b > SIZE_MAX - a) { # エラー処理: オーバーフローが起きる return ERROR_OVERFLOW; } size_t total = a + b; # C23 / GCC の checked arithmetic を使う場合 #include
size_t total; if (ckd_add(&total, a, b)) { return ERROR_OVERFLOW; }
・size_t を使う: メモリサイズには必ず size_t(符号なし・ポインタサイズ)を使い、負値が混入しないようにする
・型の明示: int / unsigned など曖昧な型を避け、uint32_t / int64_t など固定幅型を使う
・外部入力のサニタイズ: ユーザー入力・ネットワーク入力の数値は必ず型の範囲内に収まっているか検証してから使う
2. コンパイラのセキュリティオプションを活用する
GCC・Clangには整数オーバーフローを検出・緩和するフラグがあります。
# GCC / Clang でのサニタイザオプション(開発・テスト時に使用) gcc -fsanitize=undefined,signed-integer-overflow -o app src.c # 本番ビルドでの推奨オプション gcc -Wall -Wextra -Woverflow -Wconversion \ -D_FORTIFY_SOURCE=2 \ -fstack-protector-strong \ -o app src.c # Clang の場合は -fsanitize=integer でより広範な検出が可能 clang -fsanitize=integer -o app src.c
・-fsanitize=undefined: 未定義動作(符号あり整数オーバーフローを含む)をランタイムで検出してクラッシュ
・-Wconversion: 型が暗黙的に変換される箇所をコンパイル警告として出力
・-D_FORTIFY_SOURCE=2: バッファ操作関数の境界チェックを強化
3. 静的解析ツールとSCAで脆弱なコードを検出する
脆弱性スキャンの文脈でも重要ですが、整数オーバーフローは静的解析が特に効果的です。
・Clang Static Analyzer: `scan-build make` で整数オーバーフローを含む多様なバグを検出できる無料ツール
・Coverity / SonarQube: 商用・OSS の静的解析。CI/CDパイプラインに組み込んでプルリクエスト単位で検出
・Semgrep: カスタムルール対応の軽量静的解析。特定パターンの整数計算を重点的にチェックするルールを作成できる
・SCAツール(Dependabot / OWASP Dependency-Check): 依存ライブラリに整数オーバーフローのCVEが報告されていないか継続監視する
中小企業でも今日からできること
「自分のところはライブラリを使うだけでC言語なんて書かない」という組織でも、整数オーバーフローは無縁ではありません。使用しているOSSライブラリやミドルウェアが脆弱なコードを内包している可能性があるからです。
・依存ライブラリのバージョン確認と更新: Webアプリが使うライブラリ(npm / pip / Maven / Composer)の脆弱性情報をCVSSスコアで優先度をつけて管理する。パッチ管理の仕組みを整えることが第一歩
・WAFでの入力値制限: Webフロントエンドからの数値入力に対し、WAFで「異常に大きな数値」を弾くルールを設ける。極端に大きな整数値は正規ユーザーが送ることはほぼない
・コードレビューチェックリストに追加: 開発チームがあれば「サイズ計算・型変換・ユーザー入力の数値処理」を重点レビュー項目に加える
・既製品アプリは最新版を維持: WordPressプラグイン・SaaS型ERP・在庫管理システムなどのベンダーパッチを遅れなく適用する。整数オーバーフロー起因のCVEを見落とさないようCVSS 7.0以上を週次でチェックする習慣をつける
・インシデント対応計画: 万が一整数オーバーフロー起因のクラッシュが発生した場合の連絡ルート・復旧手順を事前に決めておく
よくある誤解と注意点
【誤解1】「高水準言語(Python・Java)では関係ない」
Python 3 の整数型は多倍長なのでオーバーフローは起きません。しかし Java の int は32ビット固定幅なのでオーバーフローが起きます。また、高水準言語でも内部のCライブラリを呼び出す際には問題が潜みます。言語で油断せず、ライブラリ・フレームワーク選定時に調査することが大切です。
【誤解2】「オーバーフローはクラッシュするだけ」
クラッシュ(DoS)で止まれば「まだマシ」なケースがあります。問題は、クラッシュせずに誤った値が流通してしまう場合です。正規の操作に見えながらデータが破壊されたり、権限チェックがすり抜けたりするケースは、発見が遅れる分だけ被害が広がります。
【誤解3】「テスト環境でエラーが出なかったから安全」
整数オーバーフローは「正常系テスト」では再現しません。境界値テスト(型の最大値付近)と、Ubersanitize オプションを有効にした専用ビルドでのファジングテストが必要です。本番相当の負荷をかけたときだけ現れる問題も多くあります。
【注意】「未定義動作」の怖さ
C/C++ における符号あり整数のオーバーフローは「未定義動作」です。コンパイラが最適化の過程でオーバーフロー後のコードを「到達しない」と判断して除去する可能性があります。つまり、「オーバーフロー後にエラー処理をしているはずなのに実行されない」という状況が起きえます。符号なし整数はラップアラウンドが規格で定義されているため予測可能ですが、安全が保証されているわけではありません。
本記事のまとめ
| ポイント | 内容 |
|---|---|
| 脆弱性の本質 | 整数型の範囲を超えた演算結果が後続処理で信頼されたまま使われることでバッファオーバーフロー・権限昇格に発展する |
| 攻撃の誘発点 | ファイルサイズ・バッファサイズ・配列インデックス・ループカウンタなど「外部入力が関わる数値計算」すべて |
| 開発者向け対策 | 型の選択(固定幅型)、演算前の境界チェック、コンパイラのサニタイザ・警告フラグの有効化 |
| 情シス向け対策 | 依存ライブラリの定期更新・パッチ管理、WAFによる入力制限、SCAツールの導入 |
| 誤解しやすい点 | 高水準言語でも内部ライブラリ経由でリスクが潜む。クラッシュせずに悪用されるケースの方が危険 |
整数オーバーフローは「古い脆弱性クラス」と思われがちですが、2020年代に入っても主要OSやライブラリで関連CVEが発見され続けています。特に組み込み機器・IoT・ゲームエンジン・暗号ライブラリなど、パフォーマンス重視でCやRustが使われる領域では依然として現役の攻撃面です。
「数値を扱うコードはどこにでもある」——その認識を持って、ライブラリの選定からコードレビュー、テスト手法までを見直してみてください。
PR
体系的に学ぶ 安全なWebアプリケーションの作り方 第2版(徳丸浩/SBクリエイティブ)
整数オーバーフローを含む多様な脆弱性の仕組みと対策を体系的に解説した定番書籍。開発者だけでなく脆弱性診断を担当する情シス担当者にも役立ちます。
