strcpyは常に危険か?(2)

数ヶ月前にふと思い立ってこんな記事を書いたのですが、思ったより反響が大きいため、多少ブラッシュアップしてみました。目的としては「文字列をコピーする作業を含む関数を作成する時に考慮する事」です。

一番簡単な、関数の引数で長さは与えられない状況を検討します。

引き続き、ご指導いただけたら幸いです。


■ 命題

文字列を単純にコピーする操作を含む関数を作成する。関数の引数で長さは与えられない。

■ 基礎知識

スタック、ヒープを問わず、CやC++のポインタは何処までアクセスして良いかを関数内部で判断出来ない。
→関数内部だけで完全にセキュアな文字列関数を作ることは出来ない。引数に長さが与えられても、多くの対策が出来るからセキュアなだけである。
関数外部の入力検証・浄化が完璧であるならば、strcpyはセキュア
入力の浄化を行っていない場合、どんな関数でもインセキュア、しかし、関数内で二重の防護措置をとる事は有用。

つまり、今回は関数内で出来る限りの対策をしていくことになる。以下に例を示す。

例1: WindowsにおいてはGetProcessHeapsやHeapWalk関数を組み合わせ、高確率で適正なポインタを判断できるIsBad~Ptr系が利用可能。当然シングルスレッド限定。

例2: コンパイラやエラーチェッカーの中には、境界値と思われる場所に「カナリア値」を入れておくことによってオーバーフローを検出する機能を持つものもある。

以下、紹介する対策の殆どはOSのメモリ管理機構や高度なコンパイラ、エラーチェッカーによってサポートされている作業である。

そのようなサポートが受けられない時の参考にして欲しい。

■ 対策

関数の「内部」では

  1. コピー元の長さの最大値を知っている(例えばコピー元がファイルパスで○文字までと決まっている)
  2. コピー先の長さの最大値を知っている
  3. コピー元・コピー先ともに最大値をを知っている
  4. 両方決まっていない

という状況が考えられる。以下にあげる対策のうち、必須以外は状況に応じて採用する。

  • ◎ 必須(常に採用)
  • ● リリース時(バグの発見率や効率を落とすが、安全のために利用する)
  • ○ デバック時(バグの発見率を上げるために利用しても良い)
  • △ デバック時限定(支障があるため、デバックで除去すべき)

今後、この4つの印(◎●○△)で対策を示す事にする。私のお薦めは赤字にする。

さらにいうなら、案は単なる候補であり、全て採用すると矛盾を生じる場合があるため、↑などの参照がない限り独立した対策とみなす。例えリリースでも目的が安定稼働ではなく品質向上の場合にデバック用の対策をする事もあり得る。

まずは4つの状況によらない共通の対策をあげる。

コピー前

● コピー元・コピー先のNULLチェックを行う(全てのソースで無効なポインタを常にNULLとしていないと無意味)
コピー元・コピー先のNULLチェックをassertで行う

コピー中(Windows限定の対策)

構造化例外処理(SEH)でアクセス違反を捕らえた場合、unwind前にアプリケーションの終了を試みる
↑構造化例外処理(SEH)のアクセス違反は無条件で停止し、デバッカを即座に呼び出す(出来るならFirstChanceで停止できるようにデバッカを設定)

コピー後

終端が”である事を確認(もし、終端を安全に判断できる状況なら付加する)

加えてマルチスレッド対策では、メモリにロックをかけてロック中に書き込まれない事を調査する必要がある。

ここからは上記に示した4つの状況に固有の方法について書いていく。

1.(コピー元の取り得る最大値を知っている状況)

コピー前

コピー元の値を文字列の終端か、最大長まで参照する(最大長まで参照して”が無い場合関数を終了)
△ ↑の代わりにコピー元のポインタにIsBadStringPtrを利用する # Windows限定

コピー中

コピー元の最大値を超える前にコピーを中止する

2.(コピー先の取り得る最大値を知っている状況)

コピー前

● コピー前にコピー先を最大値分”で埋める
コピー元の長さを問わず、コピー先の取り得る最大値に”を書き込む(プロービング)
△ コピー先の最大値を設定してIsBadWritePtrを利用する # Windows限定
△ コピー元のポインタにIsBadStringPtrを利用する # Windows限定
コピー先を最大値-1だけ’0xCC’等で埋める(終端が”になってない場合を検出しやすくする)

コピー中

コピー先の最大値を超える前にコピーを中止する

コピー後

プロービングを行った場合、コピー後に最大値部分が”になっていることを再確認(オーバーランチェック)

3.(コピー先、コピー元の取り得る最大値を知っている状況)

1と2の組み合わせ。厳しい方のルールを採用。

4.(上記以外)

共通項目で挙げた以外の対策を行い、strcpyを呼び出すしかない。

■ 用語集

私の記憶であるため、正確な意味は各自調査のこと。

プロービング
コピー先にデータを書き込む前に自分のデータで書き込みテストを行う事。いきなりコピーもとを書き込むより安全とされているらしい。バッファ全体を埋める、終端だけの2通りが普通。

オーバーランチェック
コピー先の長さが分かっている場合、終端にダミー値をいれる事。コピー後に値
を確認、書き換えられていたら関数失敗。文字列の場合は”でも良い。

Leave a Reply

最初のコメントを頂けますか?

更新通知を受け取る »
avatar
wpDiscuz