「C/C++セキュアコーディング」流し読み

以下、記憶に頼ってメモ(汗
2章 文字列操作
対策:getsを使わない
代替:

  • fgets() CONS: 改行文字を保存するので互換にできない
  • gets_s():stdinからしか読まない。最大文字数を指定 CONS: 入力が長すぎるとエラーとなり、データはすべて失われるとともに、文字数の指定をバッファより大きくしてしまうと、オーバーフローする可能性がある。(プログラミングミスは救えない)

対策:

  • memcopy, memmoveもセキュアな版を使う
  • strcpy, strcatも危ない。strcpy_s, strcat_s もあるが、文字数を明示的に指定するstrncpyのほうがportabilityが高いかもしれない。strncpyにもstrncpy_sが存在する。
  • std::string というライブラリ関数を使うのも手だ
  • カナリー(canary)というデータパターンをスタックに埋めておいて、それで上書きを検出する手法がある。固定のパターン(CR,LF,NULL,-1)や、秘密鍵によって生成されるランダムパターンがある。

3章 ポインタ偽装
関数ポインタの書き換えは任意のコードへのジャンプになる。関数ポインタはデータなので書き換え可能領域に置かれるので、CPUやOSのアーキテクチャによる保護が効き難い。

OSのジャンプテーブル(global offset table)の脆弱性
Linuxはrelocatableにするためにテーブルが書き換え可能なためにそこを狙われることがある。Windowsでは、書き換え不能としているので、例外が発生する。

4章 動的メモリ管理
メモリ領域の二重開放などのプログラミングエラーが問題となる。

  • 開放済み領域への書き込み

対策:

  • 開放free()したポインタをNULLにしておく。これで再利用(二重解放や解放済み領域へのアクセス)するとエラーとなって検出可能。
  • ヒープベースの脆弱性はスタックベースの脆弱性に比べて悪用が難しい。(←場所が特定しずらい)

5章 整数演算
暗黙の型変換によるプログラミングエラーを利用。オーバーフロー、アンダーフローした値でバッファサイズを指定させれば、バッファオーバーフローを起こすことができる。

単なるバグが攻撃されることがある。これはソースが公開されていなくてもバイナリを見れば分かる。

対策:

6章 書式指定出力(printf)
書式引数が外部から来るようにしてしまうと、容易に攻撃されてしまう。
バッファオーバーフロー、プログラムの異常終了、スタック内部の調査(書式の中の変数の数と、実際の引数の数が不一致ならば、スタックを読みまくられてしまう。)、メモリの上書き(%nの書式指定)
対策:

  • 動的な指定を、あらかじめ用意した選択肢に限定する
  • 書き込みバイトの制限
  • stdioではなく、iostreamを使うと、危険な書式指定を使うケースが減る
  • taintedな変数はチェックなしには使わない

7章 ファイル入出力
競合関係によるデッドロックを起こす可能性がある
対策:

  • 並列性を除去
  • 共有の度合いを下げる
  • アクセスの手順を整理

8章 実践手法

  • 安全なソフトウェア開発のための原則
    • 効率的(単純で小さな設計)
    • フェイルセーフなデフォルト:必要なものを許可するという設計
    • すべてのアクセスの権限を検査する
    • オープンな設計
    • 権限の分離(鍵を2個にする)
    • 最小限の権限
    • 共通メカニズムの最小化
    • 心理的受容性の高いメカニズム(ユーザーに使ってもらいやすいものが良い)
  • セキュリティはシステムの品質にかかわるという認識
  • 脅威をモデル化して理解を共有する
    • 資産の把握
    • 全体像の把握
    • アプリケーションの分解
    • 脅威の特定
    • 脅威の文書化
    • 脅威の格付け
  • ユースケースとミスユースケースの把握
  • コンパイラや静的チェックのツールを利用
  • 監査と侵入テスト
  • W^X (書き込み可能と実行可能は混ぜてはいけない)
  • 多層の防御(Defence in depth)