「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)