Aikido

セグメンテーション・フォールトの防止:CとC++におけるメモリ安全性

セキュリティ

ルール
防止 共通 セグメンテーション フォールト パターン
ヌル ポインタ を参照しない、 バッファ オーバーフロー、
そして 後フリー使用 エラー 原因 クラッシュ そして セキュリティの脆弱性 脆弱性の原因となります。

対応言語 C/C++

はじめに

セグメンテーション・フォールトは、C/C++アプリケーションのクラッシュや悪用可能な脆弱性の最も一般的な原因である。これらのメモリ・アクセス違反は、コードが所有していないメモリを読み書きしようとしたときに発生します。一般的には、ヌル・ポインタ・デリファレンス、バッファ・オーバーフロー、ダングリング・ポインタ、解放されたメモリへのアクセスなどです。単一のセグメ ント・フォールトが本番サーバーをダウンさせることもありますが、さらに悪いことに、多くのセグメ ント・フォールト・パターンが任意のコード実行に悪用可能です。

なぜそれが重要なのか

セキュリティへの影響バッファ・オーバーフローとuse-after-free脆弱性は、ほとんどのメモリ破壊悪用の基盤である。攻撃者はこれらを利用して、リターンアドレスを上書きしたり、シェルコードを注入したり、プログラムの制御フローを操作したりする。2014年のHeartbleed脆弱性はバッファ・オーバーリードだった。現代のエクスプロイトは、攻撃者に直接メモリ・アクセスを提供するため、依然としてこれらのパターンを標的にしている。

システムの安定性: セグメンテーション・フォールトは、アプリケーションを即座にクラッシュさせる。本番システムでは、これはリクエストの取りこぼし、トランザクションの中断、状態の破損を意味する。捕捉可能な高レベル言語の例外とは異なり、セグメンテーション・フォールトはプロセスを終了させるため、再起動やリカバリ手順が必要になります。攻撃対象の拡大: チェックされていないポインタの再参照、 strcpy, memcpyまた、境界チェックを行わない配列アクセスは、悪用の入り口となる可能性がある。攻撃者はこれらの脆弱性を連鎖的に利用し、ある脆弱性を利用して別の脆弱性を悪用できるようにメモリを破壊する。

コード例

非準拠:

void process_user_data(const char* input) {
    char buffer[64];
    strcpy(buffer, input);  // No bounds checking

    char* token = strtok(buffer, ",");
    while (token != NULL) {
        process_token(token);
        token = strtok(NULL, ",");
    }
}

int* get_config_value(int key) {
    int* value = (int*)malloc(sizeof(int));
    *value = lookup_config(key);
    return value;  // Caller must free, but no documentation
}

なぜ危険なのか: 会社情報 strcpy() の呼び出しは、入力が63バイトを超えるとバッファオーバーフローを引き起こし、攻撃者にスタックメモリを上書きさせる。そのため get_config_value() 関数が呼び出されるたびにメモリがリークし、他のコードがまだそのメモリを参照している間に呼び出し元がメモリを解放すると、ぶら下がりポインタのリスクが発生する。

✅ 準拠:

void process_user_data(const char* input) {
    if (!input) return;

    size_t input_len = strlen(input);
    char* buffer = malloc(input_len + 1);
    if (!buffer) return;

    strncpy(buffer, input, input_len);
    buffer[input_len] = '\0';

    char* token = strtok(buffer, ",");
    while (token != NULL) {
        process_token(token);
        token = strtok(NULL, ",");
    }

    free(buffer);
}

int get_config_value(int key, int* out_value) {
    if (!out_value) return -1;

    *out_value = lookup_config(key);
    return 0;  // Caller owns out_value memory
}

なぜ安全なのか: ヌル・ポインタのチェックにより、非参照クラッシュを防止。動的アロケーションにより、固定バッファサイズの制限を排除。による境界チェック付きコピー strncpy() はオーバーフローを防ぐ。の所有権セマンティクスをクリアする。 get_config_value() 呼び出し元がメモリを提供する場合、割り当ての混乱やリークを避けることができる。

結論

CとC++におけるメモリ安全性は、ポインタ操作とメモリ割り当てのたびに防御的なプログラミングを必要とする。セグメンテーション・フォールトは避けられないものではなく、一貫したヌル・チェック、境界の検証、明確なメモリ所有権のパターンによって防ぐことができる。これらのパターンを本番前にキャッチすることで、クラッシュと悪用可能な脆弱性の両方を防ぐことができる。

よくある質問

ご質問は?

最も一般的なセグメンテーションの故障パターンとは?

上位5つは、NULLポインタの非参照(ptr != NULLをチェックせずにptr->fieldにアクセスする)、バッファ・オーバーフロー(strcpy、sprintf、境界チェックなしの配列アクセス)、use-after-free(free()後にメモリにアクセスする)、double-free(同じポインタに対してfree()を2回呼び出す)、スタック・バッファ・オーバーフロー(ローカル配列の境界を越えて書き込む)である。これらは、C/C++コードベースのメモリ破壊脆弱性の80%以上を占めている。

よくあるセグメンテーション違反のパターンを防ぐには?

再参照の前に常にポインタをチェックする(if (!ptr) return;)。strcpy()の代わりにstrncpy()を、sprintf()の代わりにsnprintf()を使用する。バッファサイズを明示的に追跡し、書き込み前に検証する。C++の場合、std::stringやstd::vectorが自動的に境界を処理してくれる。すべてのポインターをNULLに初期化し、解放後にNULLに設定する。

未定義ビヘイビアとセグメンテーションフォールトの違いは?

セグメンテーション・フォールトは、未定義の動作がもたらす可能性のある結果の1つではあるが、それだけではない。未定義の動作は、静かにメモリーを破壊したり、間違った結果を出したり、テストでは問題なく動作しているように見えても本番では失敗したりすることがある。セグメンテーションフォールトは、即座に、そして目に見える形でクラッシュするので、「幸運」である。サイレント・メモリ破壊は、アプリケーションの状態を破壊したり、セキュリ ティの脆弱性を作り出したりしながら、検出されないので、より悪いものです。

ヌル・ポインタ・チェックのパフォーマンス・コストは?

最小限である。ヌル・ポインタ・チェックは、最近のCPUがナノ秒単位で実行する単一の比較命令である。ほとんどのポインタが有効なので、分岐予測は通常正確です。パフォーマンス・コストは、本番環境でのクラッシュやセキュリティ脆弱性のコストに比べれば、ごくわずかです。最適化する前にプロファイルを作成すれば、ヌル・チェッ クがホット・パスに現れることはめったにないことがわかるだろう。

まずは無料で体験

コード、クラウド、ランタイムを1つの中央システムでセキュアに。
脆弱性を迅速に発見し、自動的に修正。

クレジットカードは不要。