SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

実例で学ぶ脆弱性対策コーディング

Linuxのカーネルに潜む脆弱性をつぶすパッチ

実例で学ぶ脆弱性対策コーディング 第5回

  • X ポスト
  • このエントリーをはてなブックマークに追加

コンパイラの最適化と脆弱性にまつわる事例

 コンパイラの最適化に関する問題は2007年にgccのメーリングリストでも議論を呼びました。以下に当時の投稿を引用します(訳文は著者による)。

 最近興味深い問題に遭遇した。この問題、GNUARMのドキュメントでも触れておくべきではないだろうか。

 

 問題は、gccの最適化レベル2以上でコンパイルすると、一度ポインタの値を使用あるいはテストしたコードの後ろにおかれたNULLポインタのチェックが削除されてしまうというもの。ARM7のようなハードではハードウエア的にメモリ管理を行う仕組みが存在しないため、プログラムはエラーを報告することなく黙って終了してしまう。

 

 以下のコードをみてほしい。

void bad_code(void *a)
{
   int *b = a;
   int c = *b;
   static int d;

   if(b) {
      d = c;
   }
}

 -O2もしくはそれ以上でコンパイルすると、bの条件文はおそらく実行されないだろう(関数に0が渡されようが渡されまいが、d = cが必ず実行される)。

 

 オプティマイザがこのような動作をする理由は、bが最初に使用されるとき("int c = *b"の行)、もしbの値がゼロであるならばハードウェアフォルトが発行されるはずであり、これに続くテストである"if(b)"は不必要であると仮定して削除するのである。

 

 以下のコンパイルフラグを使うとこの問題を防げる。

-fno-delete-null-pointer-checks

 ARM-7をターゲットに-O2以上でコンパイルする際は常にこのフラグを使う方が良いだろう。

 

出典:

subject: Compiler silently removes null pointer checks - msg#00003

List: gcc.cross-compiling.arm

URL: http://osdir.com/ml/gcc.cross-compiling.arm/2007-10/msg00003.html

オーバーフロー対策での事例

 未定義の動作とコンパイラの最適化の問題には、他にもセキュリティ上の問題につながった事例があります。例えば、

char *buf;
size_t len;

のような型宣言を行い、下記のようにポインタ演算を使ってオーバーフローのチェックを行うコードを書くとどうなるでしょうか。

len = 1<<30;
[...]
if(buf+len < buf) {/* ラップアラウンドのチェック  */
   /* オーバーフロー時の処理を行うコード  */
}

 C/C++では、ポインタと整数との加減算を行った結果は、元のポインタが指している配列のなかの、整数の値だけずれた位置を指しているものと解釈されます。上記コードではbufに非負の値を加算しているわけですから、buf + lenはbufが指しているアドレスよりもずっと先にあるアドレスを意味し、buf + len >= bufが成り立ちます。もし、この計算結果が指しているアドレスが配列の外にはみ出したら未定義の動作となってしまいますから、そのような状況を無視すれば、buf + len >= bufは常に成立し、if文による余計なチェックを削除するという最適化が可能になります。実際、gcc 4.2およびそれ以降のバージョンで上記のコードをコンパイルすると、if文のチェックは最適化により削除されてしまいます。

 このようなプログラマの意図に反する最適化を防ぐためには、

#include <stdint.h>
[...]
if((uintptr_t)buf+len < (uintptr_t)buf)
[...]

のようにオブジェクトの型をchar*というポインタ型からuintptr_tという符号無し整数型にキャストして、整数値の比較を行うコードにする手段があります。符号無し整数型の値に対しては、計算結果がその型で表現できないほど大きくなる場合はラップアラウンドした結果とする、ということが明確に規定されているからです(C99、セクション6.2.5)。なお、厳密に言うならば、uintptr_tはC99では省略可能とされており定義されていない処理系がありうること(C99、セクション7.18.1.4)、また、ポインタ型オブジェクトの表現を整数とみなしたものが単純にメモリアドレスの値になっているという前提はC99で保証されているわけではなく、あらゆるアーキテクチャで同じように動作するとは限らないことにご注意ください。

 コンパイラによる最適化は効率的なコードを得るために重要な機能ではありますが、同時に脆弱なプログラムを生み出してしまう危険もあるので注意が必要です。未定義の動作や処理系定義の動作となる状況にどのようなものがあるかを把握すると共に、自分が使用する処理系の振る舞いについて正しく理解しておくことが重要です。

参考情報

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
実例で学ぶ脆弱性対策コーディング連載記事一覧

もっと読む

この記事の著者

久保 正樹(JPCERT コーディネーションセンター)(クボ マサキ(JPCERT コーディネーションセンター))

脆弱性アナリストJPCERTコーディネーションセンター慶応義塾大学環境情報学部卒。ソニーでデスクトップPCのソフトウェア開発に携わったのち、米国ダートマス大学にてオーディオ信号処理、電子音響音楽の研究を行い、電子音響音楽修士を取得。2005年4月よりJPCERTコーディネーションセンターにて、脆弱性...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

戸田 洋三(JPCERT コーディネーションセンター)(トダ ヨウゾウ(JPCERT コーディネーションセンター))

リードアナリストJPCERTコーディネーションセンター東京工業大学情報理工学研究科修士課程修了。学生時代は、型理論および証明からのプログラム抽出を研究。その後、千葉大学総合情報処理センターのスタッフとして、学内ネットワークの運営、地域ネットワーク、IPマルチキャストの実験ネットワークであるJP-MB...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/5241 2010/07/06 14:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング