Table of Contents
バッファオーバーフローは、Cプログラミングにおける最も持続的かつ危険なセキュリティ脆弱性の1つです。 数十年にわたって文書化されているにもかかわらず、データ破損、システムクラッシュ、リモートコード実行などの重大な問題を引き起こし続けています。 安全なCコードを書くには、バッファオーバーフローが発生した方法と、それらを防ぐための懲戒的なアプローチの深い理解が必要です。 この記事では、堅牢で過流耐性のあるCコードを書くための包括的なガイドを提供し、基本的な概念をカバーし、安全な機能、保護、および近代的な対策を講じています。
バッファのオーバーフローを理解する
バッファが保持するのに割り当てられたよりも、プログラムがメモリ(バッファ)の連続ブロックにより多くのデータを書き込むと、バッファのオーバーフローが起こります。 スタックやヒープメモリに存在するバッファが、その境界線が隣接するメモリ場所を上書きするので、この割込みはプログラムの状態を変更したり、予測不可能な動作を導入したり、任意のコードを注入して実行したりすることができます。
結果は、オーバーライドされたものに依存します。 スタックの戻りアドレスを上書きすると、実行を攻撃制御されたコードにリダイレクトできます。 オーバーライティングポインタは、任意のメモリ書き込みにつながることができます。 単純なクラッシュでさえ、サービス拒否攻撃のために活用することができます。 メカニックを理解することは、予防のための最初のステップです。
Stack-Based オーバーフロー
関数内で宣言されたバッファを含むローカル変数は、スタックに保存されます。スタックは、戻りアドレス、保存されたフレームポインタ、およびその他の制御データを保持しています。 [のような線形バッファがオーバーランされると、データが戻りアドレスにこぼれ、そしてそれを超える。 モリスワーム(1988)のような古典的な悪用は、スタックオーバーフローを使用して、不正なアクセスを得ることができます。
Heap-Based オーバーフロー
動的に割り当てられた緩衝(、[]など)はヒープに横たわっています。 ここで流出すると、アロケータが使用するメタデータを破損し、ヒープスプレーまたは使用後の攻撃を介してクラッシュまたは悪用につながる可能性があります。 ヒープオーバーフローは、悪用するが、同様に危険です。
一般的な脆弱な機能と安全な代替品
C標準ライブラリは、バインドチェックを実行しない複数の機能を提供します。 それらを使用することは、バッファオーバーフローの最も一般的な原因です。 より安全なカウンターパートでそれらを置き換えることは、基本的なベストプラクティスです。
文字列 コピーとコンカテーション
- [] — 安全でない: NULLの終端までのコピー、長さ制限なし。 [
安全代替:[]]]] [[]]]] - ほとんどのn文字でコピー; ソースがnよりも長い場合、それはnull-terminateではないことに注意して、それは常に手動でnull-terminateです。 - より良いまだ: [ — BSD と多くの Linux システムで利用可能; 常に null を除外し、トランケーション検出用のソース文字列の長さを返します。
- ] — 未使用: 境界のない連結。
安全代替:] ]] - ほとんどのn文字で、常にnull-terminatesを指す。
フォーマットされた出力および入力
- [] — 安全でない: サイズのチェックを行わないバッファにフォーマットされた出力を書きます。
安全代替:[]]] [[]] - 出力を size-1文字に制限します。 - ] — 同様のリスク; を代わりに使用してください。
- ] — 非常に危険です。 C11標準から削除。 代わりに使用してください。
- ] — 境界チェックなし。 または ] フィールド幅のスペクサで使用してください。
記憶のコピーおよび移動
- ] — nが、宛先バッファサイズを上回らないと検証されていない場合にのみ安全。 [
より安全代替:[]] [(ハンドルオーバーラップ)、n ≤デストサイズを常に保障します。 - 一部のプラットフォームでは、Annex K (C11 でオプション) から が提供されているが、採用は限られている。
検証とサイズ管理
安全な機能でも、入力長さを検証し、適切なバッファサイズを確保し、潜在的なトランジションを優雅に処理する必要があります。
入力長さをチェック
外部入力(ユーザ入力、ネットワークデータ、ファイル内容)のコピーや処理の前に、許容範囲の最大値を決定し、超過するデータを拒否または特定します。例えば:
#define MAX_INPUT 255
char buffer[MAX_INPUT + 1]; // +1 for null
if (strlen(user_input) > MAX_INPUT) {
// Handle error: reject or truncate
fputs("Input too long", stderr);
return -1;
}
strncpy(buffer, user_input, sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0';
既知の限界と固定サイズのバッファを使用する
可能な限り、一定のサイズでバッファを定義し、コード全体で強制します。 大容量が供給されると、スタックオーバーフローを引き起こす可能性がある可変長配列(VLA)を避けます。 代わりに、明示的なサイズのチェックで動的に割り当てます。
ハンドルの固着 明示的に
や などの関数は、データをトランク付けすることができます。トランクの検出とトランクデータの許容値、またはエラーが発生した場合を決定するために戻り値に注意してください。トルーンを無視すると、予期しない状態にバッファを残すことができます。
保安用旗およびランタイムの保護
現代のコンパイラは、コード変更なしでバッファのオーバーフロー検出とミシグレーションを追加するフラグを提供します。 ビルドシステムでそれらを有効化します。
- []/[] — アドレスを返す前に、スタックカナリア(ランダム値)をインサートします。 バッファが戻りアドレスを変更する前に、カナリアを上書きした場合、悪用が完了する前にプログラムが中止されます。
- [] — 宛先バッファが小さすぎると、チェックされたバージョンで]とのような安全でない関数に呼び出しを置換します。 またはより高い最適化が必要です。
- [] — バッファのオーバーフローや情報漏洩につながることができる形式の文字列脆弱性について警告します。
- [] — バッファオーバーフロー、使用後のメモリエラーをランタイムで検出するためのアドレスSanitizer(ASan)のインスススコード。 実行をスローしますが、テストに有利です。
- — 流出チェックを最適化しないようにします(注意して使用)。
オペレーティング システムの保護
スタックのカナリアは1つのレイヤーです。 現代のOSの既存の緩和技術には、次のものが含まれます。
- [データ実行防止(DEP)/NXビット — マークススタックと非実行可能で、シェルコードの実行を防止します。
- [ アドレススペースレイアウトランダム化(ASLR)[ — メモリアドレス(スタック、ヒープ、共有ライブラリ)をランダム化して、ターゲットを予測しにくい。
- []移転読み取り専用(RELRO)[ — GOT(グローバルオフセットテーブル)をオーバーライトから保護します。
これらの保護(通常デフォルト)を有効にすると、悪用のためのバーが上昇しますが、安全なコーディングを置き換えません。
コード監査と静的分析
ヒューマンレビューは、自動静的解析と組み合わせることで、バッファのオーバーフローの問題が早期にキャッチできます。これらを開発ワークフローに統合します。
- [] マニュアルコードレビュー — バッファ境界を超えて書き込み、安全でない機能、欠落サイズのチェック、およびループの使用を探します。
- 統計解析ツール — 、 ]、 ]、 [は、潜在的な過流を検出し、危険な機能の使用、およびオフ・バイ・ワンのエラー。 それらはCIパイプラインで実行することができます。
- []Fuzzing — libFuzzer、AFL、またはその他のfuzzersを使用して、流出を引き起こす可能性のある予期しないデータで入力処理を自動的にテストします。
セキュアコードの実用例
傷のチェックで安全な文字列のコピー
#include <stdio.h>
#include <string.h>
int safe_string_copy(char *dest, size_t dest_size, const char *src) {
if (!dest || !src || dest_size == 0) {
return -1; // Invalid parameters
}
size_t src_len = strlen(src);
if (src_len >= dest_size) {
// Source too large; truncation or error
// Option: copy what fits and null-terminate
strncpy(dest, src, dest_size - 1);
dest[dest_size - 1] = '\0';
return 1; // Truncation occurred
}
strncpy(dest, src, dest_size);
// strncpy fills remaining with null, so dest_size fits; no need to null-terminate if src shorter
return 0; // Success, no truncation
}
バッファサイズのための安全な整数処理
バッファオーバーフローは、計算サイズが整数のオーバーフローから生じることもあります。 割り当て前に常に算数をチェックしてください。
#include <stdlib.h>
#include <limits.h>
#include <errno.h>
void *safe_malloc_array(size_t nmemb, size_t size) {
if (nmemb == 0 || size == 0) {
return NULL; // Or handle zero-size allocation
}
if (nmemb > SIZE_MAX / size) {
// Integer overflow would occur
errno = ENOMEM;
return NULL;
}
return malloc(nmemb * size);
}
フォーマットされた文字列のsnprintfを使用する
char log_message[256];
int ret = snprintf(log_message, sizeof(log_message),
"User %s logged in from %s", username, ip_address);
if (ret < 0) {
// Output error
} else if ((size_t)ret >= sizeof(log_message)) {
// Truncation occurred; handle if needed
}
追加のベストプラクティス
- []バッファを初期化 — 初期化されていないメモリを漏れないようにバッファをゼロ初期化します。
- [] 未踏深度[で再帰がない場合、スタックオーバーフローは深い再帰から発生し、反復または限界深さを使用することができます。
- [] ] 修飾子[]] を使うと、コンパイラが最適化され、オーバーフローを直接防止するのではなく、エイリアシングの問題がキャッチされる可能性があります。
- [] プレファ ] の誤差] — 入力文字列の誤った変更を防ぎ、意図を強制します。
- ] 値が無効にしない 、 ] 、 ] などの関数から戻り値が無視されない。
さらなる学習のためのリソース
- セイ・CERT C コーディング標準[ — セキュアな C コーディングのための包括的なルール。
- [CWE-120: 入力のサイズチェックなしでバッファコピー — MITREのバッファオーバーフローの弱点の分類。
- []OWASPバッファオーバーフロー[ — 公開Webアプリケーションセキュリティプロジェクトからの実用的なガイダンス。
- [GNU Cライブラリマニュアル:文字列と配列ユーティリティ[] - 安全な文字列関数のドキュメント。
- []アドレスSanitizer - 速いメモリエラー検出器。
コンテンツ
C のバッファのオーバーフローを防止するオプションではありません。それは言語を扱う任意の開発者の基本的な責任です。オーバーフローのメカニズムを理解し、より安全な代替品、厳格に検証された入力とサイズと危険な機能を交換し、コンパイラ保護を有効にし、静的解析とテストを採用することで、これらの脆弱性のリスクを大幅に削減することができます。単一の技術は十分ではありません。ディープで防御する - コーディングの規準、コンパイラフラグ、OS mitig を組み合わせ、安全なコードとテストを行うには、これらの脆弱性を安全にテストすることができます。これらは、安全なコードとテストを実行しているかどうかを検証します。