「うちのサーバープロセスが突然クラッシュした。ログを見ると Segmentation fault(セグメンテーション違反)とある。これって攻撃者に利用されることがあるの?」
こうした疑問を持つエンジニアは少なくありません。NULL ポインタ参照(Null Pointer Dereference)は C・C++ で書かれたソフトウェアに昔から潜む古典的な脆弱性ですが、「クラッシュするだけ」と油断しているとサービス停止(DoS)の踏み台になり、カーネル空間では条件次第で任意コード実行にまで発展します。
この記事では、NULL ポインタ参照の仕組みを防御目的で解説し、開発・テスト・運用の各フェーズで実践できる具体的な対策をまとめます。

NULL ポインタ参照(Null Pointer Dereference)とは?
NULL ポインタ参照(CWE-476)は、「アドレス 0(NULL)を指しているポインタ変数を、値が入っているかのようにアクセスしてしまう」プログラムのミスに起因する脆弱性です。
C・C++ では変数を宣言しただけでは初期値が保証されず、メモリ確保(malloc/new)が失敗しても NULL が返ることがあります。そのポインタに対して NULL かどうかチェックせずに読み書きすると、アドレス 0 付近のメモリにアクセスを試み、OS がプロセスを強制終了します。これが一般的に言う「Segmentation fault」の原因の一つです。
・発生言語: C・C++ が最多。Java や Python でも NullPointerException・AttributeError として現れるが、動作は異なる
・CWE 分類: CWE-476(NULL Pointer Dereference)
・CVSS 傾向: 単純なクラッシュ止まりなら Medium 前後、カーネルドライバで権限昇格につながると High~Critical に達することがある
脆弱性の基本的な概念(セキュリティホールの種類・発見方法・リスク評価)については別記事でまとめています。
なぜ危険なのか:クラッシュだけで終わらないリスク
「クラッシュ=再起動すれば終わり」ではありません。NULL ポインタ参照が悪用された場合のリスクは次の3段階で考えると整理しやすいです。
1. サービス停止(DoS)
プロセスが異常終了することで、Webサーバー・データベース・認証サービスが止まります。攻撃者が細工したリクエストを繰り返し送り、NULL ポインタ参照を意図的に引き起こし続ければ、サービスは実質的に利用不能になります。
2. 情報開示
クラッシュ時にスタックトレースやコアダンプが生成される設定になっていると、メモリ上の断片的な情報(他ユーザーのセッションデータ・認証情報)が含まれる場合があります。クラッシュレポートを攻撃者が確認できる環境では、次の攻撃の足がかりにされることがあります。
【注意】3. カーネル空間での権限昇格
ユーザー空間アプリのクラッシュは通常「そのプロセスが死ぬだけ」で済みます。しかし Linux カーネルドライバや組み込み系ソフトウェアで NULL ポインタ参照が起きると状況が異なります。
古い Linux(カーネル 2.6系以前)では mmap_min_addr の値が 0 だったため、攻撃者がユーザー空間でアドレス 0 に自分のコードをマップしておき、カーネルが NULL を参照した瞬間に攻撃コードが kernel 権限で実行される――というローカル権限昇格が実際に問題になりました。現代の Linux は mmap_min_addr によってアドレス 0 付近のマッピングを制限しており、この手法は大幅に緩和されています。
Linuxのカーネルエクスプロイト緩和策(ASLR・NXビット・スタックカナリア)についても合わせて確認しておくと理解が深まります。
攻撃の仕組みを理解する(防御のために知る)
1. 基本的な NULL デリファレンスの流れ(想定例)
以下は教育目的の想定例です。実在の製品・企業とは無関係の架空のコードです。
【想定例:脆弱なコード(C言語)】
# 想定例: malloc の戻り値を確認しないケース char *buf = malloc(1024); # malloc が失敗すると buf は NULL になる # NULL チェックなしにアクセスするとクラッシュ strcpy(buf, user_input); # ← ここで Segmentation fault
【想定例:安全なコード(NULLチェックあり)】
# 想定例: malloc 後に NULL チェックを入れた安全なパターン char *buf = malloc(1024); if (buf == NULL) { # エラーハンドリング(ログ出力・エラー返却) log_error("メモリ確保に失敗しました"); return -1; } strcpy(buf, user_input);
これだけで多くの NULL デリファレンスは防げます。しかし実際のコードベースでは、関数の戻り値確認の漏れ・複雑な条件分岐・マルチスレッド競合などによって発見が難しいケースが生じます。
2. カーネルドライバで問題になる典型パターン(想定例)
組み込み系・カーネルモジュール開発の想定例です。
・ドライバが ioctl ハンドラを実装しているが、特定の引数が 0 (NULL)の場合のチェックを省略している
・攻撃者がローカルアカウントから ioctl を呼び出して NULL ポインタ参照を発生させる
・古い環境では mmap_min_addr=0 のため、アドレス 0 に仕込んだシェルコードがカーネル権限で実行される
この手法は現代 Linux では mmap_min_addr の設定(通常 65536 以上)と SMEP/SMAP(カーネルがユーザー空間コードを実行・参照できない CPU 機能)によって大幅に緩和されていますが、設定が誤っていたり古いカーネルを使い続けていたりすると今でもリスクになります。
エクスプロイトとは?脆弱性を突く攻撃コードの仕組みについては別記事で詳しく解説しています。
具体的な防御手順
1. 開発時:NULLチェックの徹底と防御的プログラミング
・malloc/new の戻り値を必ず確認する: 戻り値が NULL のままアクセスしないルールをコーディング規約に明記する
・早期リターンパターンを採用する: 関数の先頭で引数の NULL チェックをまとめて行い、問題があればすぐ return する
・スマートポインタを活用する(C++): std::unique_ptr・std::shared_ptr を使うと生ポインタの NULL 管理ミスを減らせる
・Rust・Go などメモリ安全言語への移行を検討する: 新規開発コンポーネントでは NULL 安全性をコンパイラが保証する言語の採用が根本対策になる
2. コンパイラオプション・静的解析ツールの活用
# GCC/Clang の警告フラグ(NULLチェック漏れを検出しやすくなる) gcc -Wall -Wextra -Wnull-dereference -fsanitize=address -o myapp myapp.c # AddressSanitizer 有効化(開発・テスト環境で実行すると NULL デリファレンスをその場で検出) # -fsanitize=address は本番ビルドには含めない(パフォーマンス影響大)
・Clang Static Analyzer: ビルド時に NULL デリファレンス経路を静的に解析する
・Coverity・SonarQube・CodeQL: CI/CD パイプラインに統合して PR ごとに自動チェックする
・Valgrind(memcheck): 実行時メモリエラーを検出するが速度低下が大きいためテスト環境専用
SAST(静的アプリケーションセキュリティテスト)の仕組みと主要ツールについては別記事で整理しています。
3. OS・カーネルレベルの緩和策(mmap_min_addr・SMEP/SMAP)
# mmap_min_addr の現在値を確認(65536 以上が推奨) cat /proc/sys/vm/mmap_min_addr # 設定変更(再起動後も有効にする場合は /etc/sysctl.conf に追記) sysctl -w vm.mmap_min_addr=65536 # /etc/sysctl.conf または /etc/sysctl.d/ に追記 # vm.mmap_min_addr = 65536 # SMEP/SMAP は CPU 機能なので自動有効(現代の x86_64 サーバーでは通常有効) # 確認方法 grep -E 'smep|smap' /proc/cpuinfo | head -1
・SMEP(Supervisor Mode Execution Prevention): カーネルがユーザー空間のコードを実行できないようにする CPU 機能。カーネル NULL ポインタ参照が仮にあっても権限昇格を防ぐ効果がある
・SMAP(Supervisor Mode Access Prevention): カーネルがユーザー空間のデータを参照することも制限する。SMEP と組み合わせることで攻撃の成功確率を大幅に下げる
4. コードレビューと SAST の CI 統合
・PR ごとに静的解析を必須化する: GitHub Actions や GitLab CI に CodeQL・Semgrep を組み込み、NULL チェック漏れが混入した段階でマージをブロックする
・ファジング(Fuzz Testing)を導入する: libFuzzer・AFL++ などを使い、異常入力値に対して NULL デリファレンスが起きないかランダムテストをかける
・コードレビューチェックリストに NULL 安全性の確認項目を入れる: 「malloc・new の戻り値確認あり」「関数引数の NULL ガードあり」を必須チェック項目にする
セキュアコーディングの基本原則と実装ガイドも合わせて読むと、NULL 参照以外の脆弱性パターンも体系的に理解できます。
中小企業でも今日からできること
「C・C++ で書かれたアプリなんてうちにはない」と思われがちですが、オープンソースのミドルウェア(nginx・OpenSSL・SQLite など)はほぼすべて C で書かれています。自社開発でなくても、使っているソフトウェアの NULL ポインタ関連の CVE に気づかずパッチ未適用のままになっているケースが一般的には多く見られます。
・使用中のオープンソースソフトウェアの CVE を定期確認する: JVN(https://jvndb.jvn.jp/)や NVD でキーワード「Null Pointer Dereference」と使用ソフト名で検索してみる
・OS・ミドルウェアのパッチ適用を月次で行う: カーネルアップデートには NULL ポインタ参照の修正が含まれることが頻繁にあり、適用遅延がリスクになる
・サーバーの mmap_min_addr を確認する: 上述のコマンド一つで確認でき、設定が 0 であれば即座に変更すべき
・自社開発がある場合は CI に Semgrep 無料版を追加する: 無料で使える静的解析ルールに NULL ポインタ参照の検出ルールが含まれており、既存コードのリスクを可視化できる
よくある誤解と注意点
【注意】「クラッシュするだけだから CVSS スコアが低い=放置していい」は危険
一般的には DoS のみの NULL ポインタ参照は CVSS Medium 程度に評価されますが、実際の環境やカーネルバージョン・設定によっては権限昇格につながります。また、同じバイナリに複数の脆弱性が存在し、NULL デリファレンスがその後の攻撃のドアを開けることもあります。「スコアが低いから後回し」ではなく、コンテキストに応じて優先度を判断することが重要です。
Java・Python でも「似た問題」は起きる
Java の NullPointerException(NPE)や Python の AttributeError は C とは異なりプロセスが落ちるわけではなく例外として処理されます。ただし、適切にキャッチされていない NPE がサーバーログに大量出力される状況は情報開示や DoS の一形態となりえます。言語が変わっても「参照前にオブジェクトの存在確認」の原則は共通です。
Use-After-Free との混同
Use-After-Free(UAF)は「解放済みメモリへのポインタを使い続ける」脆弱性で、NULL ポインタ参照とは発生メカニズムが異なります。NULL ポインタは「アドレスが 0」であることが問題で、UAF は「アドレスは有効な値だがすでに無効化された領域」を指している点が違います。メモリ安全性の観点では両者を区別して理解することが重要です。

本記事のまとめ
| 観点 | 内容 |
|---|---|
| 脆弱性の本質 | NULL ポインタへのアクセスによるクラッシュ(CWE-476) |
| 主なリスク | DoS(サービス停止)、情報開示、一部環境での権限昇格 |
| コードレベルの対策 | malloc/new の戻り値確認、早期リターン、スマートポインタ活用 |
| ツールによる対策 | AddressSanitizer、Clang Static Analyzer、CodeQL、Valgrind |
| OS レベルの対策 | mmap_min_addr の設定確認(65536 以上)、SMEP/SMAP の有効化確認 |
| 運用上の対策 | OS・ミドルウェアの月次パッチ適用、JVN・NVD での CVE 追跡 |
NULL ポインタ参照は「古い脆弱性」ですが、今もオープンソースソフトウェアの CVE として定期的に報告されています。コード品質の問題と捉えがちですが、攻撃者にとっては DoS の道具であり、カーネルやファームウェアでは権限昇格のきっかけになります。開発・ツール・OS の3層で対策を重ねることで、クラッシュが引き金になる攻撃シナリオを断ち切れます。
PR
サイバーセキュリティプログラミング 第2版(Justin Seitz・Tim Arnold/オライリー・ジャパン)
Pythonを使ったセキュリティツール開発から脆弱性の仕組みまで実践的に解説。メモリ安全性を含むプログラムレベルのセキュリティを手を動かしながら学びたいエンジニアに適した一冊です。
