「バッファオーバーフロー」の版間の差分

出典: フリー百科事典『ウィキペディア(Wikipedia)』
削除された内容 追加された内容
U-ichi (会話 | 投稿記録)
→‎C言語の場合: 微妙に修正(gets はバッファオーバーランを防げないが scanfは回避する方法が皆無ではないので)
26行目: 26行目:
===C言語の場合===
===C言語の場合===


[[C言語]]の標準入出力関数である[[gets]]関数はバッファ長のチェックを行わないで標準入力をバッファに書き込むので、この関数を使う全てのプログラムには、バッファオーバーランによる不正動作の危険性がある。また使い方が分かりやすいという理由でC言語初心者向けの入門プログラミングでしばしば用いられる[[scanf]]関数も細かい設定を行わない場合は様の危険性があり、これらの関数を実用的なプログラムで用いることあまり推奨されない。
[[C言語]]の標準入出力関数である[[gets]]関数はバッファ長のチェックを行わないで標準入力をバッファに書き込むので、この関数を使う全てのプログラムには、バッファオーバーランによる不正動作の危険性がある。また使い方が分かりやすいという理由でC言語初心者向けの入門プログラミングでしばしば用いられる[[scanf]]関数も同危険性を持っている。これらの関数を実用的なプログラムで用いならない。


次のプログラムはgets関数を用いた例である(ただしセキュリティ上、''gets関数はそれ自体をテストする以外の目的で使用されるべきではない。Linux Programmer's Manualには「gets()は絶対に使用してはならない。」と書かれている'')。バッファ長として200[[バイト]]確保されている。gets関数はバッファの長さについては関知しないため、200バイトを超えても改行文字かEOFが現れなければバッファオーバーランが発生する。
次のプログラムはgets関数を用いた例である(セキュリティ上、''gets関数はそれ自体をテストする以外の目的で使用されるべきではない。Linux Programmer's Manualには「gets()は絶対に使用してはならない。」と書かれている'')。バッファ長として200[[バイト]]確保されている。gets関数はバッファの長さについては関知しないため、200バイトを超えても改行文字かEOFが現れなければバッファオーバーランが発生する。


#include <stdio.h>
#include <stdio.h>
38行目: 38行目:
}
}


例えばgets関数の代わりにfgets関数を用いることで、この問題を回避できる。次のプログラムは上記のプログラムと等価である。fgets関数にはバッファのサイズを渡すことができ、このバイト数を超えてバッファに書き込みを行わない。しかし、fgets関数も誤った使方をするとバッファオーバーランを引き起こ
例えばgets関数の代わりにfgets関数を用いることで、この問題を回避できる。次のプログラムは上記のプログラムと等価である。fgets関数にはバッファのサイズを渡すことができ、このバイト数を超えてバッファに書き込みを行わない。従ってバッファサイズが正く設定されていれば、fgets関数におバッファオーバーラン起こり得ない


#include <stdio.h>
#include <stdio.h>

2007年8月23日 (木) 15:37時点における版

バッファオーバーラン (buffer overrun) とは、コンピュータプログラムバグによって起こる、コンピュータの望ましくない動作の一つである。バッファオーバーフロー (buffer overflow) とも言う。

バッファオーバーランはコンピュータセキュリティ上の深刻なセキュリティーホールとなりうるため、バッファオーバーランが起こる可能性のあるコンピュータプログラムはすぐに修正する必要がある。

概要

バッファオーバーランは、バグによってデータバッファ領域の内外を上書きしてしまい、誤動作を引き起こす。そうした場合、通常はそのプログラム(ないしオペレーティングシステム)が動作不安定になりまたは停止するが、しばしば、そのようなバグのあるプログラムに対して、意図的なデータ(悪意のあるコード)を与える事により、コンピュータの動作を乗っ取ってしまう事が問題となる。

バッファオーバーランはその上書きする領域の種類によって、スタックオーバーランヒープオーバーランに大別される。

スタック、ヒープのいずれもプログラムの実行に不可欠なデータ(例えばサブルーチンのリターンアドレスや、ヒープ内のコード)を内包する事があり、そのようなデータを意図的に上書きさせることにより、意図的なコードを実行させることが可能になる。

電子メールアドレスを題材にした例

コンピュータプログラムを作るとき、固定長のバッファとよばれる領域を確保してそこにデータを保存するという手法がよく使われる。

たとえば、電子メールアドレスは200文字以上にはならないだろうと予想して

  1. 200文字分の領域をバッファとして用意する。
  2. ユーザが200文字以上のメールアドレスを入力する。
  3. プログラムがバッファの大きさをチェックせずに入力データを書き込む。
  4. バッファとして確保した領域をはみだしてデータが書き込まれてしまう。

これがバッファオーバーランである。仮にはみ出した部分にプログラムの動作上意味を持つデータがあれば、これを上書きして破壊することにより、プログラムはユーザの意図しない挙動を示すであろう。

このようにバッファオーバーランは、プログラムが用意したバッファの大きさを超えてデータを書き込んでしまうバグである。

C言語の場合

C言語の標準入出力関数であるgets関数はバッファ長のチェックを行わないで標準入力をバッファに書き込むので、この関数を使う全てのプログラムには、バッファオーバーランによる不正動作の危険性がある。また使い方が分かりやすいという理由でC言語初心者向けの入門プログラミングでしばしば用いられるscanf関数も同じ危険性を持っている。これらの関数を実用的なプログラムで用いてはならない。

次のプログラムはgets関数を用いた例である(セキュリティ上、gets関数はそれ自体をテストする以外の目的で使用されるべきではない。Linux Programmer's Manualには「gets()は絶対に使用してはならない。」と書かれている)。バッファ長として200バイト確保されている。gets関数はバッファの長さについては関知しないため、200バイトを超えても改行文字かEOFが現れなければバッファオーバーランが発生する。

#include <stdio.h>
int main(int argc, char *argv[])
{
  char buf[200];
  gets(buf);
  ....
}

例えばgets関数の代わりにfgets関数を用いることで、この問題を回避できる。次のプログラムは上記のプログラムと等価である。fgets関数にはバッファのサイズを渡すことができ、このバイト数を超えてバッファに書き込みを行わない。従ってバッファサイズが正しく設定されていれば、fgets関数においてバッファオーバーランは起こり得ない。

#include <stdio.h>
int main(int argc, char *argv[])
{
  char buf[200];
  fgets(buf, 200, stdin);
  ....
}

これ以外のC言語の標準文字列処理関数の多くにも同様の問題(脆弱性)がある。

バッファオーバーランが起こす問題

オペレーティングシステム(OS)によっては、プログラムのコード領域とデータ領域を区別せず、コードがデータ領域に書かれていてもそのまま実行してしまう物がある。

もっとも典型的なバッファオーバーランは、データ領域のうちでもスタック領域に対するものである。前述のバッファがスタック領域に割り当てられたものである場合(この割当てはC言語の自動変数で典型的である)、はみ出したデータがスタック領域の当該バッファ割当て部分よりも外の部分を書き換えてしまう。一方、スタック領域にはプログラムカウンタにリストアされるべきサブルーチンからのリターンアドレスが格納されているが、そのリターンアドレスをバッファーオーバーランしたデータで書き換えてしまう事になる。

バッファーオーバーラン等の不正動作に対する保護機能が無いようなOS上で実行されるアプリケーションソフトウェアでは、プログラム作成者ないし利用ユーザの意図の有無に関わらず、常にこの危険性を含んでいる。現在大衆向けに販売されているOSの多くは、このようなメモリ保護機能を持たない事が問題の根底にある。

悪用

クラッカーは、このバッファオーバーランを意図的に起こしてデータの改竄やコンピュータシステムの損壊につながる操作をおこなう(通常は、ワームウイルス等の不正プログラムを作成し、それに攻撃を実行させる)。

通常は、不正アクセスの手段として不正なデータをコンピュータに対して送信すると言う事があるが、バッファオーバーラン攻撃を行う場合には、送信データに不正なプログラムのコードを挿入し、さらに前述のスタック領域上のリターンアドレス等を、この不正コードが存在するアドレスに書き換える事等により、任意の不正なコードを相手のコンピューターにおいて実行させ、OS上の管理者権限を不正に奪取するなど様々な攻撃を行う。

近年、コンピューターの制御を乗っ取り、攻撃を行うウイルスはバッファーオーバーランを利用した物が多い。脆弱性を示す為に作られたプログラムExploitを悪用し、そのプログラムにウイルス機能を載せた物が大多数を占める。2003年8月インターネットトラフィックにおいてバックグラウンドノイズとされるトラフィックが1kbps未満から突然30~40kbpsに跳ね上がった。これはWindowsのRPCサブシステムにおけるバッファオーバーランによるセキュリティホールを攻撃し、制御を乗っ取り自らウイルスを放出するMSBlastウイルスによる攻撃が全世界規模で発生した為である。

C言語でかかれ、古いライブラリ関数を多用している、そして多くの文字列処理を行っている、"sendmail"プログラムは近年こそ毎年のペースまで下がったが、以前は毎月のようにバッファオーバーランによるセキュリティホールが発覚し、修正されていた。そしてsendmailを突破口としてセキュリティを破られたシステムはとても多く、その数はWinnyによる情報流出を上回るものである。このようなセキュリティ上の観点から(またライセンスの関係もあるが)sendmailを標準プログラムから排除する動きがあり、いくつかのOSディストリビューションの標準セットからsendmailは取り除かれてしまった。

対応

バッファオーバーランは潜在的に大量に存在する問題であり、その対策のために多くの研究が行われているが、いまだに決め手となる技術は存在していない。 バッファオーバーランをおこすソフトウェアの多くは C言語などコンパイラ言語で書かれているため、自動的なチェック機構を導入すると深刻な性能低下をひきおこすことが多いためである。 アドレスランダム化 (address randomization) の技術は、バッファオーバーランが本来メモリ中の特定の位置にプログラムに配置されていることを前提としていることから、メモリ構造を並べ替えて外部の攻撃者が予測不可能にするものである。また、Linux などの OS では一度メモリに書かれた領域をマークしておき、その部分を実行禁止にするという技術 (PaX) も導入されている。しかしこの 2つをもってしても、クラッカーが巧みにメモリをスキャンすることで攻撃が可能になってしまう例があることが示されている。いっぽう、プログラムのソースコードを静的にチェックすることで、事前に明らかなバッファオーバーランの可能性がある場合は警告を発するなどのシステムも研究されている。しかしこの場合もすべてのエラーを発見できるわけではない。

その他の対応としては、適切なメモリ保護機能を持ったOSやCPU(例えば、データ領域のデータをコードとして実行させないなど)を使用するか、または、実行時に常にバッファオーバフローを検知する機能や仕組みを、利用するかまたは自ら実装する事が、根本的解決方法である。このような機能を備えた環境を整備できない場合、常にバッファ長を意識したプログラムを書くよう努力し、見落としがあった場合はあきらめるしかない。

メモリ保護機能の実装例の一つとして、完全なハーバードアーキテクチャのハードウエアとオペレーティングシステムの組み合わせが考えられる。この構成はバッファーオーバーランの脅威を緩和する。データ領域におかれた任意のコードを実行する手段が無いからである(プログラムを異常動作させる事は可能であろう)。これと同程度の保護を実現する手段としてAMD社が開発したAMD64 Enhanced Virus Protectionがある。これはWindowsXP SP2と組み合わせて利用する事により、ページ単位でコード領域とデータ領域を区別し、バッファーオーバーランの脅威を軽減している(AMD社製品ではOpteronAthlon 64Turion 64Sempronの一部製品がこれに対応している。Intel社製品ではXD・eXecute Disable Bitという名前で同じ機能を実装しておりNetBurstアーキテクチャPentium4ファミリにおいてIntel 64対応プロセッサで利用できる。またCoreマイクロアーキテクチャの全てのプロセッサでは標準装備となる見通しである)。ただし、WindowsXP SP2はこのセキュリティ強化を含めて多くのセキュリティホールを修正した結果互換性に問題が生じた為、大手企業ではSP2の導入に消極的である。

インタプリタ言語には、配列の確保領域外へのアクセスを処理系が自動的にチェックするものが多いため、配列によるバッファに対するバッファオーバーランが発生しにくい(インタプリタ言語ではエラーメッセージを出して動作が停止するが、コンパイラ言語ではそのまま実行し不正動作を起こす)。ただし、インプタプリタそのものはほとんど例外なくコンパイラ言語によって書かれているので、絶対に信頼できるわけではない。また言語に付属するライブラリモジュールは母体となった言語のライブラリやシステムコールに由来し、脆弱性が組み込まれてしまうこともある。

配列領域をチェックする機能は16bit時代から、80x86CPUのBOUND命令など既に実装されていたが、チェックの結果問題があった場合には例外を発生させる等扱いが難しかった。現在のCPUにおいてもチェックにまつわるオーバーヘッドが無視できないので、リリースバイナリにこれらの機能を含めることはまれである。

現在、バッファオーバーランはもっとも重大なセキュリティーホールのひとつと考えられているため、あるプログラムでバッファオーバーランの脆弱性が発見されると、一般にそれに対する修正作業の優先度は非常に高く、プログラムの更新バージョンの公開や修正プログラムの配布などが行われる。

なお2006年4月21日情報処理推進機構(IPA)は、P2Pデータ交換ソフトWinnyにおけるバッファーオーバーランによる脆弱性を発表した。この発表に基づき、いくつかのセキュリティ調査会社はこの脆弱性が適切なデータを用意する事で任意のコードを実行する事が可能である事を報告した。しかしソースコードが京都府警ハイテク犯罪対策室によって押収されている為プログラムの修正が出来ないので、この脆弱性に対する対策は「Winnyを使用しない事」とされた(その後リバースエンジニアリングによってWinny利用者による修正バージョンが配布された)。

関連項目