Secure Programming for Linux HOWTO David A. Wheeler, dwheeler@dwheeler.com version 1.23, 5 January 2000 The Linux JF Project version 1.23j, 3 Feburary 2000 この論文は,安全なプログラムを Linux システム用に書くための設計と実装 のガイドラインを集めたものである.対象となるプログラムには,リモートデ ータのビューアとして使われるプログラム,CGI スクリプト,ネットワークサ ーバ,setuid/setgid して使用するプログラム等がある. ______________________________________________________________________ 目次 1. 序文 2. 背景知識 2.1 Linux とオープンソースソフトウェア 2.2 セキュリティの基本原則 2.3 安全にすべきプログラムの種類 2.4 神経質なのは良いことである 2.5 設計と実装のガイドラインに関する情報源 2.6 この論文における表記方法 3. Linux のセキュリティ機能の概要 3.1 プロセス 3.1.1 プロセスの属性 3.1.2 POSIX ケーパビリティ 3.1.3 プロセスの生成と操作 3.2 ファイルシステム 3.2.1 ファイルシステムオブジェクトの属性 3.2.2 作成時の初期値 3.2.3 アクセス制御属性の変更 3.2.4 アクセス制御情報の利用 3.2.5 ファイルシステムの階層 3.3 System V IPC 3.4 ソケットとネットワーク接続 3.5 quota とリソース制限 3.6 監査 3.7 PAM 4. 全ての入力を検証する 4.1 コマンドライン 4.2 環境変数 4.3 ファイルデスクリプタ 4.4 ファイルの内容 4.5 CGI の入力 4.6 他の入力 4.7 有効な入力時間と負荷レベルの制限 5. バッファオーバーフローの回避 5.1 C/C++ における危険 5.2 ライブラリを用いた解決方法(C/C++) 5.3 コンパイルを行う解決方法(C/C++) 5.4 他の言語の使用 6. プログラムの内部と入口の構造化 6.1 インタフェースを安全にする 6.2 与える権限をできるだけ小さくする 6.3 デフォルトは安全な設定にする 6.4 フェールオープン 6.5 競合状態の回避 6.6 信頼できる通信経路しか信頼しない 6.7 内部的な整合性をチェックするようなコードを書く 6.8 自分自身でリソースを制限する 7. 他のリソースの呼び出しの際には注意する 7.1 呼び出しの際に正しい値だけを使う 7.2 システムコールの戻り値は全てチェックする 8. 相手に返す情報は注意深く選ぶ 8.1 返す情報をできるだけ少なくする 8.2 出力先が詰まっている場合や応答しない場合への対処 9. 特殊な話題 9.1 ロッキング 9.2 パスワード 9.3 乱数 9.4 暗号のアルゴリズムとプロトコル 9.5 Java 9.6 PAM 9.7 その他の話題 10. 結論 11. 参考文献 12. 文書のライセンス 13. 日本語訳について ______________________________________________________________________ 1. 序文 この論文は,安全なプログラムを Linux システム用に書くための設計と実装 のガイドラインを集めたものである.この論文の目的である「安全なプログラ ム」とは,安全な位置とそうでない位置との境界上に置かれ,自分とは同等の 権限を持っていないソースからの入力を扱うプログラムのことである.これに 該当するプログラムには,リモートデータのビューアとして使われるプログラ ム,CGI スクリプト,ネットワークサーバ,setuid/setgid して使用するプロ グラム等がある.本論文で説明する原則の多くは Linux カーネルにも当ては まるが,この論文ではカーネルそのものの修正には触れない.この論文で述べ るガイドラインは,安全なプログラムを作る方法に関するさまざまな情報から 「学んだ教訓」のまとめとして作成され(筆者が加えた意見もある),より大き な原則として整理し直されている. この論文は保証基準,ソフトウェアエンジニアリングの工程,品質保証のアプ ローチは扱わない.これらは重要な問題であるが,他の場所で広く議論されて いる.このような基準にはテスト,ピアレビュー,構成管理,形式的手法が含 まれる.特にセキュリティに関する開発保証基準を扱ったものとして は,Common Criteria [CC 1999], System Security Engineering Capability Maturity Model [SSE-CMM 1999] がある.より一般的なソフトウェアエンジニ アリング手法やプロセスは Software Engineering Institute's Capability Maturity Model for Software (SE-CMM), ISO 9000 (および ISO 9001, ISO 9001-3), ISO 12207 等の文書で定義されている. この論文は,与えられた環境でシステム(あるいはネットワーク)が安全となる ように設定する方法については議論しない.既存のプログラムを安全に使う方 法が重要なことは明らかだが,安全な設定については非常に多くの文書で議論 されている.Linux システムの安全な設定に関する情報は Fenzi [1999], Seifried [1999], Wreski [1998] を含む多岐に渡る文献から入手できる. この論文では,読者はセキュリティに関する一般的な知識,UNIX 的 OS のセ キュリティモデル,C 言語の知識を持っているものとする.また,セキュリ ティに対する Linux のプログラミングモデルに関する知識も多少扱ってい る. この論文の書式は, Linux Documentation Project (LDP, ) に容易に加えられるように意図して整形してあ る.この論文のオリジナルは から入手できる. この論文は David A. Wheeler の著作物であり((C) 1999 David A. Wheeler), GNU General Public License (GPL) により保護される.詳しくは 最後の節を参照すること. この論文では,まず Linux とセキュリティに関する背景的な知識を議論す る.次の節では Linux の一般的なセキュリティモデルの解説として,プロセ ス,ファイルシステムオブジェクト等のセキュリティに関する属性と操作の概 要を説明する.その次に本論文の主題である,Linux システム上でアプリケー ションを開発する際の設計と実装のガイドラインを述べる.この説明は,全て の入力の検証,バッファオーバーフローの回避,プログラムの内部および入口 の構成,外部リソースの慎重な呼び出し,情報の慎重な返送,そして特殊な話 題に関する情報(乱数の取得方法など)に分けられる.そして最後に結論と参考 文献を述べて論文の結びとする. 2. 背景知識 2.1. Linux とオープンソースソフトウェア 1984 年に Richard Stallman 率いる Free Software Foundation (FSF) は GNU プロジェクトを開始した.このプロジェクトはフリーな版の UNIX OS を 作ろうとするものである.Stallman は「フリー」という言葉によって,自由 に使い,読み,変更し,再配布できるソフトウェアを表そうとした. 1991 年 に Linux Torvalds は OS のカーネルの開発を始めた.Linus はこれを 「Linux」と名付けた[Torvalds 1999].このカーネルは FSF のツール群や他 の品々と組み合わせることができ,自由に配布可能で,しかも非常に役立つ OS だった.この論文ではカーネル本体を「Linux カーネル」と呼び,組み合 わせて作られた全体のものを「Linux」と呼ぶことにする(この組み合せでな く,GNU/Linux という言葉が使われる場合も多い). 色々な組織によって,利用可能な品々が色々な形で組み合わせられている.そ れぞれの組み合わせを「ディストリビューション」と呼び,ディストリビュー ションを開発している組織を「ディストリビュータ」と呼ぶ.有名なディスト リビューションには RedHat, Mandrake, SuSE, Caldera, Corel, Debian 等が ある.この論文は特定のディストリビューションに依存しない.この論文で仮 定するのは,カーネルのバージョンが 2.2 以降であることと,C ライブラリ が glic 2.1 以降であることである.この仮定は,現在の主要な Linux ディ ストリビューションに対しては基本的に有効である. このような「フリーソフトウェア」への注目が高まるとともに,フリーソフト ウェアを定義し,説明する必要性が高まってきた.広く用いられている用語は 「オープンソースソフトウェア」であり,その定義は [OSI 1999] で詳しく述 べられている. Eric Raymond [1997, 1998] はフリーソフトウェアの開発プ ロセスを考察した文章をいくつか書き,大きな影響を与えた. Linux は UNIX のソースコードを使っていないが,Linux のインタフェースは 意図的に UNIX に似せてある.したがって,UNIX の教訓は Linux にも当ては まり,それはセキュリティについても言える.この論文に書かれている情報の 多くは,実際にはどのような UNIX 的 OS にも当てはまるが,Linux の利用者 が Linux の機能を生かせるようにするため, Linux 固有の情報も意図的に加 えている.この論文は扱う対象を絞るため,意図的に Linux だけに焦点を当 てている.全ての UNIX 的 OS を扱うと移植性の問題や他の OS の機能の問題 が関わってくるが,そうなるとこの論文が大きくなりすぎてしまうからであ る. Linux は UNIX に似せて作られたものなので,UNIX のセキュリティ機構を備 えている.その機構に含まれるのは,各プロセスに対するユーザ ID と グル ープ ID (uid と gid),ファイルシステムの読み取り/書き込み/実行の(ユー ザ/グループ/全体に対する) パーミッション,System V のプロセス間通 信(IPC),ソケットベースの IPC(ネットワーク通信を含む)等である. UNIX システムに関する一般的な知識については Thompson [1974] や Bach [1986] を参照すること.これらの文献では基本的なセキュリティ機構についても述べ られている.第 3 章では,Linux で重要なセキュリティ機構を概説する. 2.2. セキュリティの基本原則 セキュリティに関して馴染んでおくべき一般的な原則は数多くある. [Pfleeger 1997] 等のコンピュータセキュリティに関する教科書を読むこと. Saltzer [1974] および Saltzer と Schroeder [1975] は,安全な防御システ ムの設計の原則として以下の項目を挙げている.これらの項目は現在でもなお 有効である: o 権限をできるだけ小さくすること.それぞれのユーザとプログラムは,で きる限り小さい権限を使って動作すべきである.そうすれば,攻撃を受け たときの被害を最小限に抑えることができる. o 機構が経済的であること.防御システムは小さく,単純に,素直に設計す べきである. o オープンな設計であること.防御機構は「攻撃者が機構の仕組みを知らな いこと」をあてにしてはならない.逆に防御機構そのものの内容は公開さ れているべきで,機構の安全性はパスワード等,比較的少数の (しかも容 易に変更可能な)要素に依存すべきである.そうすれば,数多くの第三者の 検証を受けることが可能となる. Bruce Schneier は,賢い技術者は「セ キュリティに関することは全てオープンソースコードに頼るべき」である と指摘している.彼はまた,オープンソースコードは多くの人に検閲され ることや見つかった問題は全て解決されることを確かめている[Schneier 1999]. o 完全に仲介を行うこと.考え得るアクセス方法は全てチェックしなければ ならない.チェック機構は回避できないように配置すること.例えば,ク ライアント-サーバモデルにおいては,一般的にはサーバは全てのアクセス をチェックしなければならない.なぜなら,ユーザは改造等により独自の クライアントを作ることができるからである. o 禁止を基本の態度とすること.デフォルトの動作ではサービスを拒否すべ きである. o 権限を分離すること.理想的には,オブジェクトへのアクセスは複数の条 件に基づいて行うべきである.そうすれば,一つの防御機構が破られても 完全なアクセスを許すことはない. o 共通して持つ機構を最小限にすること.共有されているオブジェクトは情 報流出の経路となる危険性を持っている.したがって,オブジェクトは物 理的にも論理的にも分割すべきである. o 使いやすいこと.使いやすい機構はユーザに避けられにくい. 2.3. 安全にすべきプログラムの種類 安全にする必要があるプログラム(この論文での定義に基づく)は多岐に渡る. 以下では一般的な種類をいくつか述べる: o リモートのデータのビューアとして使われるアプリケーションプログラ ム.ビューアとして使われるプログラム(ワードプロセッサや何らかのフォ ーマットのファイルのビューア)は,リモートの信頼できないユーザから送 られたデータを表示するように求められることがしばしばある (この要求 はウェブブラウザが自動的に行うこともある).明らかなことだが,信頼で きないユーザからの入力に対してアプリケーションから任意のプログラム を実行させることは許可すべきでない.初期化マクロ(データを表示する時 に実行される)に対応することも普通は賢明とは言えない.初期化マクロに 対応しなければならない場合は,安全な砂箱 (sandbox) (複雑かつエラー 対処の作業を行う)を用意しなければならない. [ 訳注: 砂箱とは,ペッ トが排泄するときに使う砂を入れた箱のこと.しつけの悪いペットに粗相 をされても外には影響が出ないように,しつけの悪いプログラムが悪さを しても影響が出ないという意味. ] バッファオーバーフローのような問 題(後述)には注意すること.バッファオーバーフローが起きると,信頼で きないユーザがビューア経由で任意のプログラムを実行できてしまうこと がある. o システム管理者(root)が使うアプリケーションプログラム.この種のプロ グラムは,システム管理者でないユーザが変更できる情報を信用すべきで ない. o ローカルのサーバ(デーモンとも呼ばれる). o ネットワーク利用が可能なサーバ(ネットワークデーモンと呼ばれることも ある). o CGI スクリプト. CGI スクリプトはネットワーク利用が可能なサーバの特 殊なケースであるが,非常に一般的なので独自に分類にしている. CGI ス クリプトはウェブサーバ経由で間接的に呼び出される.ウェブサーバは一 部の攻撃を排除するが,それでもなお CGI スクリプト側で対処しなければ ならない攻撃もたくさん残っている. o setuid/setgid されるプログラム.このようなプログラムはローカルのユ ーザに起動され,起動した直後にプログラムの所有者や所有グループの権 限を得る.色々な理由により,setuid/setgid されるプログラムは安全に するのが非常に困難なプログラムである.なぜなら,入力の大部分が信頼 できないユーザの制御の下にあり,そういった入力の一部は明らかでない からである. この論文ではこれらいくつかの種類の問題を一つの集合にまとめる.このアプ ローチの欠点は,ここで指摘された問題の一部は必ずしも全ての種類の問題に は当てはまらないことである.特に setuid/setgid したプログラムには多く の驚くような問題があるため,この論文で述べるガイドラインのいくつかは setuid/setgid したプログラムにしか当てはまらない.しかし綺麗に分類する ことは難しい.なぜなら,プログラムによっては複数の領域にまたがることが あるからである(例えば,CGI スクリプトは setuid/setgid されることもある し,同様の効果が得られるように設定されることもある).全ての種類の問題 をまとめて捉えることの利点は,あるプログラムに間違った分類を当てはめる ことなしに全ての問題を検討できることである.以下に述べるように,原則の 多くは安全にする必要がある全てのプログラムに適用できる. この論文の大部分では, C で書いたプログラムに対する暗黙の偏向がある. ただし C++, Perl, Python, Ada95, Java といった他のプログラムについて も,多少記述している.その理由は,Linux 上で安全なプログラムを実装する ためのもっとも一般的な言語が C だからであり(ただし CGI スクリプトは除 く.この分野では Perl がよく使われる),他の多くの言語の実装も C ライブ ラリを呼び出すからである.ただし,これは C が安全なプログラムを書く目 的に「最も適した」言語であると言っているわけではない.また,この論文で 説明した原則のほとんどは使用するプログラミング言語に関係なく適用でき る. 2.4. 神経質なのは良いことである 安全なプログラムを書く上で最も難しいのは,このようなプログラムを書く際 には普段とは違った心構えが必要となる点である.短く言えば「神経質な心構 え」が必要となる.その理由は,間違い(欠陥,バグとも呼ばれる)がもたらす 影響が大きく異なるからである. 普通の安全でないプログラムには多くのエラーがある.こういったエラーは望 ましいものではないが,普通は稀で滅多に起こらない条件によるので,ユーザ がバグに出会っても,その後はバグを避けながらツールを使えるだろう. 安全なプログラムでは状況は逆になる.一部のユーザは稀で滅多に起こらない 条件を故意に探し出して起こし,認められていない権限をその攻撃によって得 ようとする.したがって,安全にすべきプログラムを書く際には,神経質なの は良いことである. 2.5. 設計と実装のガイドラインに関する情報源 いくつかの文献で安全なプログラムの書き方(あるいは換言すると,既存のプ ログラムが持つセキュリティ上の問題を見つけ出す方法)が説明されており, この論文でも述べるガイドラインの基本的な内容が強調されている. 一般目的のサーバおよび setuid/setgid されたプログラムについては,重要 な文献がたくさんある(ただし,その一部はどこかで引用されていなければ探 すのが困難である). AUSCERT はプログラミングのためのチェックリストを出 している[AUSCERT 1996].これは Garfinkel と Spafford による文献の 22 章の一部の,安全な SUID とネットワークプログラムの書き方に関する議論を 元にしている[Garfinkel 1996]. Matt Bishop [1996, 1997] はこの分野にお ける非常に重要な論文の作成と発表を行っている. Galvin [1998a] は安全な プログラムを開発するための単純なプロセスとチェックリストを述べてい る.Galvin は後に Galvin [1998b] でこのチェックリストを新しくしてい る. Sitaker [1999] は「Linux security audit(Linux セキュリティ監査)」 チームが調べるべき項目の一覧を発表している. Shostack [1999] はセキュ リティが重要視されるコードのためのチェックリストを独自に定義している. Secure Unix Programming FAQ からも役立つ知識が得られるだろう [Al- Herbish 1999].役立つ情報は Ranum [1998] からもいくつか得られる.勧め られていることの中には注意して扱わなければならないものもある.例え ば,access(3) に通常存在する危険な競合状態に触れずに access(3) の使用 を推奨している [不明] ような人もいる. Wood [1985] はいくつかの有益な アドバイスを「Security for Programmers」の章で述べている.ただし,この 内容的は古くなっている. Bellovin [1994] と FreeBSD [1999] にも役立つ ガイドラインが含まれている. 多くの文献で,ウェブとのインタフェースとなる CGI (Common Gateway Interface) を用いたプログラムのためのセキュリティに関するガイドライン が与えられている.このような文献としては Gundavaram [unknown], Kim [1996], Phillips [1995], Stein [1999], Webber [1999] が挙げられる. この論文は,非常に役立つと筆者が考えたガイドラインのをまとめたものであ る.したがって,全ての可能性を網羅したガイドラインではない.この論文の 構成は筆者独自のものであり(各リストはそれぞれ独自の異なる構成を持 つ),Linux 専用のガイドライン(例: ケーパビリティや fsuid 値等) も筆者 独自のものである.前述の参考文献も全て読むことを強くお勧めする. ここで「どうして単に他の文献を紹介するだけでなく自分で独自の文書を書い たのか?」という疑問を持たれるかもしれない.それに対する答えは以下であ る: o これらの情報の多くが分散している.重要な情報をひとつのまとまった文 書にしておけば使いやすい. o これらの一部の情報はプログラマ向けでなく,システム管理者やユーザ向 けに書かれている. o 一部の情報は Linux に関係しない.例えば,多くのチェックリストは setuid されたシェルスクリプトに対する警告を行っているが,Linux はこ のようなシェルスクリプトを通常は許可していないので,改めて警告する 必要がない. o 入手できる情報の多くは,可搬性の高い構成を強く意識している(全ての UNIX 的 OS で利用できるように作られている).可搬性を考えると Linux 固有の技術を避けることは正しいが,Linux 固有の技術を使えば実際にセ キュリティの向上に役立つ場合もある. Linux 以外の OS への可搬性が本 来望ましい場合であっても,Linux 上では Linux 固有の機能を使いたいこ ともあるだろう. o Linux に特化するアプローチは珍しいものではない.他の OS (FreeBSD 等)でも,独自のセキュリティ関連のプログラミングガイドがある. 2.6. この論文における表記方法 システムのマニュアルページは「名称(番号)」の形式で参照する.ここで番号 はマニュアルのセクション番号である. C と C++ は文字 '\0' (ASCII の 0) を特別扱いするので,この論文ではこの値を NIL と書くことにする.「どこ も指さない」ことを意味するポインタ値は NULL と呼ぶ.多くの環境で C コ ンパイラは整数値 0 を値 NULL に変換するが,C 言語の標準規格には,実際 に全てのビットが 0 である値として NULL を実装することを求める規則はな い. 3. Linux のセキュリティ機能の概要 Linux のセキュリティ機能の使い方のガイドラインを説明する前に,プログラ マの観点からこれらの機能について知っておくと役に立つと思われる.この節 では Linux のセキュリティ機能を簡単に説明する.これらの機能について既 に理解しているなら,次の節に進んでも構わない. 多くのプログラミングガイドは,Linux のセキュリティ関連の部分を切り詰 め,重要な情報を飛ばしている.特にこのようなガイドでは,何かの「使い 方」の一般的な内容は説明しているが,その使い方に影響を与えるセキュリ ティ属性については体裁しか整えていないことが多い.その逆に,マニュアル ページには個々の関数についての詳しい情報が載っているが,マニュアルペー ジでは往々にして森の中に木が埋もれてしまうことになる.この節ではこの ギャップの橋渡しを試みる.つまり,プログラマが使うと思われる Linux の セキュリティ機構の概要を説明する.この節では Linux のセキュリティ関連 事項に特に注目して,普通のプログラミングガイドよりも深い話題を扱い,そ してさらに詳しい情報が得られるように参考文献を紹介する. UNIX のプログ ラマには馴染みがある分野だと思われるが,Linux における拡張や Linux 固 有の事情があるので驚くこともあるかもしれない.この節ではこのような相違 点もできるだけ述べることにする. まずは基礎を説明する. Linux は基本的に 2 つの部分に分けられる.すなわ ち Linux カーネル (カーネルモジュールを含む)と「ユーザ空間」である.ユ ーザ空間はカーネルの上にあり、様々なプログラムがここで動作する.ユーザ がログインすると,そのユーザ名はそのユーザが所属する「UID(user id の意 味)」と「GID(group id の意味)」を表す整数値に割り当てられる. UID 0 は 特殊な権限(役割)を持つユーザで,伝統的に「root」と呼ばれる.このユーザ はほとんどのセキュリティチェックを越えることができ,システム管理のため に用いられる.プロセスはセキュリティの点から見ると唯一の「主体」であ る(つまり,プロセスだけがアクティブなオブジェクトである).プロセスは様 々なデータオブジェクト,特にファイルシステムオブジェクト (FSO, filesystem object),System V プロセス間通信(IPC)オブジェクト,ネットワ ークオブジェクトにアクセスできる.次以降の節ではこれらを詳しく説明す る. 3.1. プロセス Linux では,ユーザレベルの動作はプロセスの実行として実装されている.多 くのシステムは分離された「スレッド」をサポートしている.Linux では,複 数のスレッドが複数のプロセスを用いて実装されていることもある(この場 合,Linux カーネルはスレッドレベルの速度を得るための最適化を実行す る). 3.1.1. プロセスの属性 全てのプロセスはセキュリティ関連の属性の集合を持っている.これを以下に 挙げる: o RUID, RGID - 実 UID および実 GID.これはプロセスを実行したユーザで ある. o EUID, EGID - 実効 UID と実効 GID.権限チェックのためのユーザ(ファイ ルシステムの権限チェックは除く). o FSUID, FSGID - ファイルシステムのアクセスチェックに使われる UID と GID.普通は FSUID と EUID は同じであり,FSGID と EGID は同じであ る.これは Linux 固有の属性である. o SUID, SGID - 保存 UID と保存 GID.パーミッションの「オン/オフ」の切 替えをサポートするために使われる.詳しくは後述する. o groups - ユーザが所属しているグループ(GID)のリスト. o umask - 新しいファイルシステムオブジェクトを作る際のデフォルトのア クセス制御の設定を決めるビットの集合.umask(2) を参照すること. o スケジューリング用パラメータ - 各プロセスはスケジューリングのポリシ ーを持っており,デフォルトのポリシーが SCHED_OTHER になっているプロ セスは nice 値,優先度(priority),カウンタを持っている.詳しくは sched_setscheduler(2) を参照すること. o ケーパビリティ - POSIX のケーパビリティ情報.実際にはプロセスには 3 組のケーパビリティがある.すなわち実効ケーパビリティ,継承可能ケー パビリティ,許可ケーパビリティである. POSIX ケーパビリティに関する 詳しい情報は後述する. o limit 値 - プロセスごとのリソースの制限(後述). o ファイルシステムのルート - プロセスがルートファイルシステムと考えて いる場所.chroot(2) を参照. どの属性がそれぞれのプロセスに関連しているのかを正確に知る必要が本当に あれば,Linux のソースコードを調べること.特に include/linux/sched.h での task_struct を調べるとよい. 3.1.2. POSIX ケーパビリティ Linux バージョン 2.2 で「POSIX ケーパビリティ」の内部サポートが追加さ れた.POSIX ケーパビリティは,通常は root が持っている権限を,より細か い多数の権限に分割するといった機能に対応する. POSIX ケーパビリティは IEEE 標準のドラフトで定義されている.したがって,これは Linux 固有の機 能ではないが,他の UNIX 的システムではあまりサポートされていない. Linux の文献(この論文も含む)に「root の権限を必要とする」と書かれてい るほとんど全ての場合は,実際には「ケーパビリティを必要とする」という意 味である.これはケーパビリティに関する文書に書かれている.必要としてい る特定のケーパビリティについて知る必要があれば,ケーパビリティに関する 文書で調べること. 最終的には,ファイルシステム内のファイルにケーパビリティを割り当て可能 にすることが目指されているが,この論文の執筆時点では,その機能にはまだ 対応していない.ケーパビリティの転送機能には対応しているが,この機能は デフォルトで無効にされている. Linux のバージョン 2.2.11 には,ケーパ ビリティをより直接的に使いやすくする機能が追加された.この機能は「ケー パビリティ限界集合(capability bounding set)」と呼ばれる.ケーパビリ ティ限界集合は,システム上の全てのプロセスが持つことが許されているケー パビリティのリストである(許されていないケーパビリティは,特殊な init プロセスだけが持てる).ケーパビリティが限界リストに載っていない場合 は,特権を持っているかどうかに関係なく,どんなプロセスもそのケーパビリ ティを使用できない.この機能は例えば,カーネルモジュールの読み込みを無 効にするときに使われる.この機能を生かしたツールの例としては,LCAP ( ) が挙げられる. POSIX ケーパビリティに関する詳しい情報は で入手で きる. 3.1.3. プロセスの生成と操作 プロセスの生成には, fork(2), 使用が勧められない vfork(2),そして Linux 固有の clone(2) が使える.これらのシステムコール全ては,既にある プロセスを複製し,2 つのプロセスを作成する.プロセスは execve(2) とそ の各種フロントエンド(例えば exec(3), system(3), and popen(3) を参照)を 呼び出すことによって別のプログラムを実行できる. プログラムが実行された時にそのファイルの setuid ビットか setgid ビット が立っていると,そのプロセスの EUID (setuid の場合)または EGID (setgid の場合)がファイルの値に設定される. Linux では,シェルスクリプト等の普 通のスクリプトではこのような操作は行われない点に注意すること.この操作 をスクリプトに対して行うのはセキュリティ的な危険が多くあるからであ る(他の UNIX 的 OS には setuid したシェルスクリプトに対応しているもの もある).例外的に,Perl では setuid した Perl スクリプトに対応した設定 を行える. 場合によっては,プロセスはさまざまな UID 値や GID 値に影響を及ぼすこと ができる.setuid(2), seteuid(2), setreuid(2), setfsuid(2) を参照するこ と.特に SUID 属性は,信頼したプログラムが一時的に UID を切替えること を許す. RUID が変更された場合,または EUID に RUID と異なる値が設定さ れた場合には,SUID には新しい EUID が設定される.特権を持たないユーザ は,自分の SUID から自分の EUID を, RUID から EUID を,EUID から RUID を設定できる. FSUID プロセス属性は,NFS サーバ等のプログラムに対し,ある UID の権限 をファイルシステム関係のものに限って許可するためのものである。その UID 権限でプロセスにシグナルを送ることはできなくする。 EUID が変更される と,FSUID は必ず新しい値に変更される.つまり, FSUID 値 は setfsuid(2) (Linux 固有のシステムコール)を使わなくても変わる場合がある. root 以外 からの呼び出しでは,FSUID には現在の RUID 値, EUID 値, SEUID 値, ある いは現在の FSUID 値しか設定できない. 3.2. ファイルシステム ファイルシステムオブジェクト(FSO)は通常ファイル,ディレクトリ,シンボ リックリンク,名前付きパイプ(FIFO),ソケット,キャラクタ特殊(デバイ ス)ファイル,ブロック特殊(デバイス)ファイルのいずれでもよい(このリスト は find(1) コマンドで表示される).ファイルシステムオブジェクトはファイ ルシステム上に集められる.ファイルシステムは親となるファイルシステム上 のディレクトリにマウント/アンマウントできる.ファイルシステムはファイ ルと少し異なるアクセス制御属性を持ち,アクセス制御はマウント時に選択し たオプションの影響を受ける. 3.2.1. ファイルシステムオブジェクトの属性 現在 ext2 は Linux システムで最もよく使われているファイルシステムであ る.ext2 ファイルシステムは各ファイルシステムオブジェクトに対して以下 の属性をサポートしている: o 所有 UID と GID - これはファイルシステムオブジェクトの「所有者」を 特定する.特に補足しない限り,アクセス制御属性を変更できるのは所有 者と root だけである. o ユーザ(所有者),グループ,その他のユーザに対する読み取り,書き込 み,実行ビット.通常ファイルの場合は,読み取り,書き込み,実行は言 葉通りの意味を持つ.ディレクトリの場合,「読み取り」パーミッション はディレクトリの内容を表示するために必要であるが,これに対して「実 行」パーミッションは「検索」パーミッションと呼ばれることもあり,実 際にそのディレクトリに入ってその内容を使うために必要となる.ディレ クトリの場合,「書き込み」パーミッションはそのディレクトリ内での ファイルの追加,削除,ファイル名変更を許可する.ファイルの追加だけ を許可したければ後述の sticky ビットを設定すること.シンボリックリ ンクのパーミッション値は全く使われない点に注意すること.つまり,関 係があるのはシンボリックリンクが置かれているディレクトリとリンク先 のファイルのパーミッション値だけである. o ``sticky'' ビット - sticky ビットがディレクトリに設定されると, unlink(削除)を行えるユーザは root, ファイルの所有者,ディレクトリの 所有者だけに制限される.これは UNIX で非常に一般的に使われている拡 張機能であり,一般ユーザもこのビットを設定できる.古いバージョンの UNIX ではこれを「プログラムテキスト保存」ビットを呼んでおり,(ス ワップアウトしないで)メモリ上だけで実行すべき実行ファイルを示すため に使っていたが,Linux の仮想メモリ管理機能により,このやり方は時代 遅れになった. o setuid, setgid - 実行ファイルに設定すると,このファイルを実行したプ ロセスの実効 UID と実効 GID は (それぞれ)ファイルの所有者の UID と GID となる.全ての UNIX 的 OS はこの機能をサポートしている. setgid がディレクトリに設定されると,そのディレクトリ内で作成されたファイ ルの GID は自動的にディレクトリの GID に再設定される.どの実行権も 持たないファイルに setgid を設定すると,そのファイルがアクセスされ ている間は強制的にロックがかかるようになる(ただし,マウントしている ファイルシステムが強制ロックをサポートしている場合).この機構の負荷 は驚くほど高く,UNIX 的 OS で広く使われているわけではない. o タイムスタンプ - アクセス時刻と修正時刻は全てのファイルシステムオブ ジェクトについて保存される.ただし,所有者はこの時刻を自由に設定す ることができるので(touch(1)を参照),この情報を信頼するにあたっては 注意すること.全ての UNIX 的 OS はこの機能をサポートしている. o 不変ビット(immutable bit) - ファイルシステムオブジェクトに対する一 切の変更を禁止できる.このビットの設定と解除を行えるのは root だけ である.この機能をサポートしているのは ext2 ファイルシステムだけで あり,全ての UNIX システムで(あるいは全ての Linux 用ファイルシステ ムでさえ)広く使えるわけではない. o 追加限定ビット - ファイルシステムオブジェクトに追加のみが許可され る.このビットの設定と解除を行えるのは root だけである.この機能を サポートしているのは ext2 ファイルシステムだけであり,全ての UNIX システムで(あるいは全ての Linux 用ファイルシステムでさえ)広く使える わけではない. 以上の値の多くはマウント時に影響を受ける.つまり,例えば, (メディア上 に保持している値に関係なく)あるビットがある特定の値を持っているかのよ うに扱われることがある.詳しい情報については mount(1) を参照すること. ファイルシステムによっては,一部のアクセス制御値をサポートしていないこ とがある.繰り返しになるがこのようなファイルシステムの扱いについては mount(1)を参照すること. アクセス制御リスト(ACL, access control list)と POSIX ケーパビリティ値 をファイルシステムに追加する作業が現在行われているが,この機能は普通 の Linux 2.2 には入っていない. 3.2.2. 作成時の初期値 ファイルシステムオブジェクトの生成時には以下の規則が適用される.ファイ ルシステムオブジェクト(FSO)が(例えば creat(2) を使って)生成されたと き,その FSO の UID には,FSO を生成したプロセスの FSUID が設定され る.普通は FSO の GID には,FSO を生成したプロセスの FSGID が設定され るが,FSO が作られるディレクトリの setgid ビットが設定されている場合 や,ファイルシステムの「GRPID」フラグが設定されている場合には, FSO の GID にはこのディレクトリの GID が設定される.この特殊なケースを使うと 「プロジェクト」ディレクトリに対応できる.つまり,「プロジェクト」用の ディレクトリを作り,そのプロジェクトのための専用のグループを作り,その グループが所有するプロジェクトのためのディレクトリを作ってから,その ディレクトリに setgid を設定する.すると,このディレクトリに作成された ファイルは自動的にプロジェクトのものとなる.同様に,setgid ビットが設 定されている(そしてファイルシステムの GRPID が設定されていない)ディレ クトリ内に新しくサブディレクトリを作ると,そのサブディレクトリの setgid ビットも設定される(したがって,プロジェクトのサブディレクトリも 「正しく」動作する).それ以外の場合には全て,新しいファイルの setgid ビットはクリアされる. FSO の基本的なアクセス制御値(読み取り,書き込 み,実行)は (要求された値 & ~ プロセスの umask 値)によって求められる. 新しいファイルが作られたときは,sticky ビットと setuid ビットは必ずク リアされた状態になっている. 3.2.3. アクセス制御属性の変更 アクセス制御属性のほとんどは chmod(2) や chmod(1) で設定できる.ただ し, chown(1), chgrp(1), chattr(1) も参照すること. Linux では,あるファイルの所有者を変更できるのは root だけである点に注 意すること.一部の UNIX 的 OS には一般ユーザが所有権を変更できるものが あるが,これは扱いが面倒になる.例えばディスクの使用量を制限しようとし た場合,一般ユーザに所有権の変更を許していると,大きなファイルは誰か別 の「被害者」のもののようにできてしまう. 3.2.4. アクセス制御情報の利用 Linux およびほとんどの UNIX 的 OS では,属性値の読み書きはファイルをオ ープンしたときにしかチェックされない.つまり,一度ファイルをオープンし た後は読み書きするたびの再チェックは行われない.これらの属性値をしてい るシステムコールは非常にたくさんある.なぜなら,ファイルシステムは Linux の中心にとても近い部分にあるからである.このようなシステムコール には open(2), creat(2), link(2), unlink(2), rename(2), mknod(2), symlink(2), socket(2) 等がある. 3.2.5. ファイルシステムの階層 長年の伝統により「どのファイルをどこに置くか」という約束ごとができてい る.情報をファイル階層内に配置するときには,この決まりに従うこと.この 決まりには hier(5) にまとめられている.さらに詳しい情報は Filesystem Hierarchy Standard (FHS) として入手できる.この規約は,以前に存在した Linux Filesystem Structure standard (FSSTND) を改めたものである.詳し くは を参照すること. 3.3. System V IPC Linux は System V IPC オブジェクト,すなわち,System V のメッセージ キュー,セマフォセット,共有メモリセグメントに対応している.これらのオ ブジェクトはそれぞれ以下の属性を持っている: o 作成者,作成者のグループ,その他のユーザに対する読み書きのパーミッ ション. o 作成者の UID と GID - オブジェクトの作成者の UID と GID. o 所有者の UID と GID - オブジェクトの所有者の UID と GID (初期状態で は作成者の UID, GID と同じ). このようなオブジェクトにアクセス際に適用される規則は以下の通りである: o プロセスが root 権限を持っていればアクセスは許可される. o もし,プロセスの EUID がオブジェクトの所有者か作成者の UID ならば, 該当する作成者のパーミッションビットがチェックされ,アクセスを許可 するかどうかが確認される. o プロセスの EGID がオブジェクトの所有者か作成者の GID であるか,ある いはプロセスのグループのどれかがオブジェクトの所有者か作成者の GID ならば,該当する作成者のグループパーミッションビットがアクセスに際 してチェックされる. o 上記のいずれでもない場合は,該当する「その他のユーザ」のパーミッ ションビットがアクセスに際してチェックされる. root が実行したプロセスまたは所有者か作成者の EUID を持つプロセスは, 所有者 UID と所有者 GID を設定したり,オブジェクトを削除できる.詳しい 説明が ipc(5) にある. 3.4. ソケットとネットワーク接続 ソケットは通信,特にネットワーク通信で用いられる. socket(2) は通信用 の端点(endpoint)を作成し,デスクリプタを返す.詳しくは socket(2) や, その他の関連情報を参照すること. Linux では,TCP と UDP を 1024 番未満 のローカルポートに割り当てるには root 権限が必要な点に注意するこ と(1024 番未満のリモートポートに割り当てる場合には特別な権限は必要な い). 3.5. quota とリソース制限 Linux にはファイルシステムの quota とプロセスのリソース制限を行うため の機構がある.ここでは用語に注意すること.というのも,quota とリソース 制限には「ハードな」制限と「ソフトな」制限があるが,これらの用語は少し 違う意味を持つからである. 記憶装置(ファイルシステム)の quota 制限を各マウントポイントごとに定義 できる.定義は,指定されたユーザかグループが使用できる記憶装置のブロッ ク数か,ユニークなファイル数(inode の数)について行う.「ハード」な quota 制限は決して超えることができない制限であるが,「ソフト」な quota 制限は一時的に超過することが許される.詳しくは quota(1), quotactl(2), quotaon(8) を参照すること. rlimit 機構は,プロセスに対する非常に多くの種類の quota に対応してい る.例えばファイルサイズ,子プロセスの数,オープンできるファイルの数な どである.制限には「ソフトな」制限(現在の制限(current limit)とも呼ばれ る)と「ハード」な制限(上限(upper limit)とも呼ばれる)がある.ソフトな制 限を超えることはどんな場合も許されないが,ソフトな制限値はシステムコー ルを使ってハードな上限値まで上げることができる.詳しくは getrlimit(), setrlimit(), getrusage() を参照すること. 3.6. 監査 現在もっとも一般的な「監査」機構は syslogd(8) である. wtmp(5), utmp(5), lastlog(8), acct(2) も見ると良いだろう.サーバプログラムに よっては(例えば WWW サーバの Apache 等)は,独自の監査機構も備えてい る. 3.7. PAM 認証を行う際,ほとんどの Linux システムでは PAM (Pluggable Authentication Modules, 差し替え可能な認証モジュール) が用いられ る.PAM を用いると認証の設定を行うことができる(例えばパスワードの使 用,スマートカードの使用など). PAM については,この論文の後の方で詳し く説明する. 4. 全ての入力を検証する 入力によっては信頼できないユーザから行われることがあり,こういった場合 には入力を使用する前に検証(フィルタリング)する必要がある.何が正しいか を決めておき,その定義に合わないものは全て排除すべきである.その逆(何 が正しくないかを決めておき,それに当てはまるものを排除する)を行っては ならない.なぜなら,重要なケースを処理し忘れるかもしれないからである. 文字列の最大の長さ(必要なら最小の長さ)を制限しておき,その長さを超えた ときにも制御を失わないようにすること(この問題についての詳しい説明は, 「バッファオーバーフロー」の節を参照すること). 文字列の場合は,正しい文字やパターンを(例えば正規表現として)決めてお き,それに当てはまらないものを全て排除すること.文字列が制御文字(特に 改行文字や NIL 文字)やシェルのメタ文字を含むときには特別な問題が起き る.したがって,このようなメタ文字は入力された時点ですぐに「エスケー プ」し,間違って送られてしまわないようにするとよいだろう. CERT はこれ をさらに進めて,エスケープする必要がない文字のリストに含まれていない全 ての文字をエスケープすることを推奨している[CERT 1998, CMU 1998].詳し くは「呼び出しの際に正しい値だけを使う」の節を参照すること. 全ての数値について,許可する最小値(0 のことが多い)と最大値を決める. ファイル名はチェックすべきだ.普通は「..」(上位ディレクトリ)は正しい値 とは認めないとよいだろう.ファイル名では,ディレクトリの変更は全て禁止 するとよいだろう.例えば,「/」を正しい文字の集合に含めないなどの方法 がある.電子メールアドレスを完全にチェックしようとすると非常に面倒であ る.なぜなら,全てのアドレスに対応しようとしても,検証が非常に面倒な古 い形式のアドレスが存在するからである.このようなチェックが必要であれ ば, mailaddr(7) と IETF RFC 822 [RFC 822] に詳しい情報がある. これらの検査は普通,一ヶ所で集中して行うべきである.なぜなら,後で正当 性の検査自体の正確さの確認を行うことが容易になるからである. 正当性の検証するコードを自分で作った場合は,それが実際に正しく動作する ことを確かめること.このことは,別のプログラムが使う入力(ファイル名や 電子メールアドレス,URL 等)をチェックする場合には特に重要である.この ようなテストには気づきにくい間違いがあることが多く,いわゆる「代理人問 題」(チェック用のプログラムの前提が,実際にデータを使うプログラムの前 提と異なる問題)を起こすことがある. 以下の節では,プログラムへ与える様々な種類の入力について説明する.この 入力には環境変数や umask 値など,プロセスが持っている状態も含む点に注 意すること.必ずしも全ての入力を信頼できないユーザが行うわけではないの で,注意する必要があるのは信頼できないユーザからの入力だけでよい. 4.1. コマンドライン 多くのプログラムはコマンドラインを入力のインタフェースとして用い,引数 として渡すことにより入力を受け取る. setuid/setgid されたプログラムは 信頼できないユーザからコマンドライン入力を受け取ることがあるので,自分 自身で防御しなければならない.また,ユーザはコマンドラインをかなり自由 に扱うことができる(execve(3) 等のシステムコールを用いる).したがっ て,setuid/setgid されるプログラムはコマンドライン入力を検証しなければ ならないし,コマンドライン引数 0 が示すプログラム名を信用してはならな い(ユーザはプログラム名に NULL を含めた自由な値を設定できる). 4.2. 環境変数 デフォルトでは環境変数は親プロセスから引き継がれる.しかし,あるプログ ラムが他のプログラムを実行する際には,環境変数を任意の値に設定すること ができる.これは setuid/setgid されるプログラムにとって危険である.な ぜなら,このようなプログラムを呼び出す元のプログラムは環境変数を制御し て送り付けることができるからである.環境変数は普通は引き継がれるので, この危険性も引き継がれていく. 環境変数は,同じフィールドに複数の値を持てる形式で保存される(例えば SHELL 変数を 2 つ持てる).普通のコマンドシェルではこのような設定は禁止 されているが,クラッカーはこのような状況を作ることができる.つまりプロ グラムは 1 つの値しかチェックしないが,実際には別の値が使われることも ある.さらに悪いことに,多くのライブラリやプログラムは環境変数で制御さ れているが,制御方法があいまいだったり,分かりにくかったり,そもそも文 書化されていないことさえある.例えば,sh と bash は IFS 変数を使ってコ マンドライン引数を区切る文字を決める.シェルはいくつかの低レベルシステ ムコールを使って呼び出されるので, IFS に普通でない値を設定することに より,安全そうに見えるシステムコールを破壊することができる. setuid/setgid されたプログラムを安全にするには,まず(もし存在するなら) 入力として必要な環境変数の短いリストを注意深く取り出す.次に,大域変数 environ に NULL を設定することにより環境変数全てを消去し,その後に必要 な環境変数の小さい集合に安全な値を再設定する (ユーザが指定した値は含め ない).このような値としては PATH(プログラムを探す対象のディレクトリの リスト.カレントディレクトリを含めてはならない), IFS(デフォルトの `` \t\n'' を設定すること), TZ(タイムゾーン)等がある. 4.3. ファイルデスクリプタ プログラムには「オープンしたファイルデスクリプタ」の集合が渡される.こ れは予めオープンされているファイルである. setuid/setgid されたプログ ラムは,オープンするファイルをユーザが (パーミッションの制限内で)選べ るという事実に対処できなければならない. setuid/setgid されたプログラ ムは,新しいファイルをオープンしたときに常に決まったファイルデスクリプ タ ID でオープンされると仮定してはならない.標準入力,標準出力,標準エ ラー出力が端末であることや,あるいはオープンされていることすら仮定して はならない. 4.4. ファイルの内容 あるプログラムが指定されたファイルから指示を受ける場合は,信頼している ユーザしかファイルの内容を制御できない場合を除き,そのファイルを特別に 信頼してはならない.つまり,信頼できないユーザはそのファイルやディレク トリ,そしてファイルの全ての親ディレクトリを編集できてはならない.そう でない場合には,このファイルは信頼できないものとして扱わなければならな い. 4.5. CGI の入力 CGI の入力は,内部的には設定された環境変数の集合と標準入力である.これ らの値は検証しなければならない. その他の難しい点としては,CGI への入力の多くがいわゆる「URL エンコー ド」,つまり一部の値が %HH の形で書かれている形式で与えられる点があ る.ここで HH はそのバイト値を表す 16 進コードである. CGI ライブラリ は,入力を URL デコードして,さらにデコードによって得られたバイト値が 適切かどうかをチェックすることにより,入力を正しく処理しなければならな い. %00 (NIL) や %0A (改行) のように問題がある値を含め,全ての値を正 しく扱えなければならない.入力を複数回デコードしてはならない.そうでな いと,「%2500」のような値の処理を誤ってしまう(まず %25 が「%」に変換さ れ,その結果得られた「%00」が間違って展開されて NIL 文字になってしま う). CGI スクリプトは入力に特殊文字を含める攻撃をよく受ける.これについては 上記のコメントを参照すること. 一部の HTML form は,不正な値をある程度取り除くためにクライアント側で のチェックを行う.このチェックやユーザの手助けにはなるが,セキュリティ の役には立たない.なぜなら,攻撃者はこういった「不正な」値を直接ウェブ サーバに送ることができるからである. (「信頼できる経路しか信頼しない」 の節で)後述するように,サーバは自分の全ての入力についてテストを実行し なければならない. 4.6. 他の入力 プログラムは必ず全ての入力を制御できていなければならない.これは setuid/setgid されたプログラムでは特に難しい.なぜなら,そのような入力 が非常にたくさんあるからである.プログラムが考慮しなければならない他の 入力としては,カレントディレクトリ,シグナル,メモリマッ プ(mmap),System V IPC, umask (新しく生成されるファイルのデフォルトの パーミッションを決める)等がある.プログラムの起動時に,適切かつ完全に 指定されたディレクトリに(chdir(2) を用いて)明示的に移動することも考慮 すること. 4.7. 有効な入力時間と負荷レベルの制限 タイムアウトと負荷レベルの制限は行うこと.特にネットワークからの入力デ ータに対してはこの制限を行うこと.さもないと,攻撃者はサービスの要求を 送り続けることにより,簡単にサービス妨害攻撃を行えてしまう. 5. バッファオーバーフローの回避 非常に一般的なセキュリティ的欠陥に「バッファオーバーフロー」がある.技 術的には,バッファオーバーフローはプログラムの内部的実装の問題だが, バッファオーバーフローは一般的かつ重大な問題なので,この問題の説明に章 をひとつ割くことにする.この問題がどれだけ重要かというと,CERT による 勧告の 1998 年 9, 13 号,そして 1999 年の勧告の少なくとも半分がバッ ファオーバーフローに関係している. Bugtraq における非公式の調査による と,セキュリティの弱点の一番多い原因はバッファオーバーフローであると回 答者の約 2/3 が感じている(残りの回答者は「設定ミス」であるとしている) [Cowan 1999]. バッファオーバフローが起こるのは,値の集合(普通は文字列)を固定長のバッ ファに書き込む際にバッファの終端を超えて書き込み続けたときである.バッ ファオーバーフローはユーザからの入力をバッファに読み込む時に起こる可能 性があるが,他の種類の処理をプログラム中で行っている最中にも起こる可能 性がある. 安全であるべきプログラムでバッファオーバーフローを許すと,普通は攻撃者 が悪用できてしまう.バッファが C 言語のローカル変数ならば,オーバーフ ローを利用して攻撃者の好きなコードを実行する関数を呼ばせることができる (詳しくは [Aleph1 1996] を参照すること).バッファがヒープ中にあればま だましであるが,攻撃者はこれを利用してプログラム中の変数を制御すること ができる. プログラミング言語によっては,本質的にこの問題の影響を受けない.その理 由としては,言語が自動的に配列のサイズを変える場合(例: Perl)や,普通は バッファオーバーフローを検出して防ぐ場合(例: Ada95)がある.しかし,C はバッファオーバーフローを防ぐ手段を全く持たないし,C++ もこの問題を起 こすような使い方が簡単にできてしまう. 5.1. C/C++ における危険 C を使う場合は,境界を絶対に超えないことが確実な場合を除き,境界を チェックしない危険な関数の使用を避けるべきである.多くの場合に避けるべ き関数としては, strcpy(3), strcat(3), sprintf(3), gets(3) 等がある. これらの関数はそれぞれ strncpy(3), strncat(3), snprintf(3), fgets(3) で置き換えるべきであるが,詳しくは後で説明する.関数 strlen(3) は,文 字列の終端の NIL 文字が見つかることが確かでない限りは利用を避けるべき である.この他に(使い方によっては)バッファオーバーフローを許すかもしれ ない関数としては,fscanf(3), scanf(3), vsprintf(3), realpath(3), getopt(3), getpass(3), streadd(3), strecpy(3), strtrns(3) がある. 5.2. ライブラリを用いた解決方法(C/C++) C/C++ における解決方法の一つは,バッファオーバーフローの問題を持たない ライブラリ関数を使うことである. C でバッファオーバーフローを起こさないための「標準的」な解決方法は,こ のような問題を受けない C ライブラリの標準関数を使うことである.このア プローチでは,標準ライブラリ関数の strncpy(3) と strncat(3) に深く頼る ことになる.このアプローチを使う場合は注意すること: これらの関数のセマ ンティクスは少し変わっていて,正しく使うことが難しい.関数 strncpy(3) は,コピー元の文字列の長さがコピー先の長さ以上の場合,コピー先の文字列 を NIL で終端しない.したがって,strncpy(3) を呼び出した後は,コピー先 の文字列の最後の文字に必ず NIL を設定すること. strncpy(3) と strncat(3) のいずれでも,利用可能な容量の残りを渡してやらなければなら ないが,この計算は間違いやすい(そして,この計算を間違うとバッファオー バーフロー攻撃を許すことになる).どちらの関数も,バッファオーバーフロ ーが起きたかどうかを調べる簡単な仕組みを持っていない.最後 に,strncpy(3) は置き換えの対象である strcpy(3) よりも性能的には不利で ある.その理由は,strncpy(3) はコピー先の残りの領域を 0 で埋めるからで ある. 別の方法として(これは OpenBSD よりもらってきた方法である), Miller と de Raadt が開発した strlcpy(3) と strlcat(3) がある[Miller 1999].これ は最小化アプローチであり,,文字列のコピーと連結を標準の関数群と異な る(そして間違いが起こりにくい)インタフェースで C 言語に提供する.これ らの関数のソースコードと文書は, か らBSD 形式のライセンスに基づいて入手できる. さらに別の方法としては,固定長のバッファを用いず,全ての文字列を動的に 割り当て直す方法がある.この汎用的な方法は,GNU プログラミングガイドラ インが推奨している. C 言語で文字列の動的な再割り当てを自動的に行うた めのツールセットの一つとして,Forrest J. Cavalier III による「libmib allocated string functions」がある.これは から入手できる.このソース コードはオープンソースである.文書はオープンソースではないが,自由に入 手できる. 役に立つかもしれないライブラリは他にもある.例えば,glib ライブラリは オープンソースのプラットフォームで広く利用可能である(GTK+ ツールキット は glib を用いているが,glib は GTK+ とは別に利用可能である).今回は glib を解析して,glib のライブラリ関数がバッファオーバーフローを起こさ ないことを明らかにすることはできなかったが,これは正しいようである.本 論文の後の版では,どの glib 関数を使えばバッファオーバーフローの問題を 避けられるかを確かめられると考えている. 5.3. コンパイルを行う解決方法(C/C++) 全く別のアプローチとして,境界チェックを行うコンパイル方法を用いること ができる(方法の一覧については [Sitaker 1999] を参照).筆者の意見では, こういったツールは防御を何重にもする場合には非常に役立つが,この技術を 単独の防御手段として用いるのは賢明とは言えない.この理由は少なくとも 2 つある.まず最初の理由は,こういったツールのほとんどは一部のバッファオ ーバーフローしか防げない(「完全」に防ごうとすると,一般に速度が 12-30 倍遅くなる).これは単に,C や C++ はバッファオーバーフローを防ぐように は設計されていないからである.次の理由としては,オープンソースのプログ ラムでは,プログラムのコンパイルする際に使われるツールがはっきりしな い.したがって,あるシステムのデフォルトの「普通」のコンパイラを使った とたんにセキュリティの欠陥が生じることになる. より便利なツールのひとつに「StackGuard」がある.このツールは「防御」の ための値(「canary(カナリア)」と呼ばれる)を復帰アドレスの前に挿入する. もしバッファオーバーフローによって復帰アドレスが書き換えられると,この canary 値が(多分)変化し,システムはそのアドレスを使う前にバッファオー バーフローを検出できる.このツールは非常に重要だが,他の値を書き換える ようなバッファオーバーフローは防げない点に注意すること. StackGuard を 拡張して他のデータ要素にも canary 値を追加できるツールがある.これは 「PointGuard」と呼ばれている. PointGuard は特定の値(例えば関数へのポ インタや longjump バッファ等)を自動的に保護する.しかし,PointGuard を 使って他の型の変数を守るためには,プログラマが特定の仲介作業を行う必要 がある(プログラマは,canary 値を使って守るべきデータを指定しなければな らない).このツールは重要だが,保護はいらないと考えていたが実は保護が 必要なデータの保護を間違って省略してしまいやすい. StackGuard, PointGuard やこれらの代わりとなるツールに関する詳しい情報ついて は,Cowan [1999] を参照すること. 関連事項として,Linux カーネルを修正してスタックセグメントを実行可能で なくすることもできる.このようなパッチは実際に存在する( Solar Designer のパッチにこの機能が含まれている.パッチは から入手できる).しかし,この論文を書 いている時点では,このパッチは Linux カーネルには取り込まれていない. その根拠のひとつは,このパッチは見ためほど防御効果がないことである.つ まり,攻撃者は単に既にプログラムに入っている他の「面白い」場所(例え ば,ライブラリ内,ヒープ内,静的データセグメント等)をシステムに呼ばせ ることができるのである.また,Linux はスタック内に実行可能なコードを必 要とすることもある.例えば,シグナルの実装や,GCC の「トランポリン」を 実装する時である. [訳注: 「トランポリン」とは,入れ子になった関数に入 るために実行時に生成されるコードのことです] Solar Designer のパッチは これらのケースにも対処できるが,そのためにパッチが複雑になっている.個 人的にはこのパッチが本家の Linux 配布に組み込まれることを希望してい る.なぜなら,このパッチにより攻撃がいくらか困難になるし,ある範囲の既 存の攻撃を防ぐことができるからである.しかし,このパッチは見ためほど防 御を強化しないことと,比較的容易に出し抜ける点については筆者は Linus Torvalds たちと同意見である.この機能を含めないことに関する Linus Torvalds の説明は で読むこ とができる. 要約すると,まずはバッファオーバーフローによる攻撃を受けない正しいプロ グラムを開発する方がよいということである.そして,それを行った上 で,StackGuard 等の技術やツールを追加の安全ネットとして使うとよい.コ ード自身からバッファオーバーフローを無くすように努力すれば, StackGuard もさらに効果的に使えるだろう.なぜなら,防御のために StackGuard を呼び出すこととなる「鎧の隙間」が少なくなるからである. 5.4. 他の言語の使用 バッファオーバーフローの問題は,バッファオーバーフローに対して安全な Perl, Python, Ada95 といった他の言語でも問題となる.もちろん,こういっ た言語を使ったからといって全ての問題がなくなるわけではない.特に「呼び 出しの際に正しい値だけを使う」で述べられている NIL 文字に関する説明を 参照すること.また,他の言語のインフラ(例えば実行時ライブラリ等)が利用 可能かつ安全であることを保証することの問題もある.ただしそれでも,バッ ファオーバーフローが起こらない安全なプログラムを開発する際には,他の言 語の使用を確かに検討すべきである. 6. プログラムの内部と入口の構造化 6.1. インタフェースを安全にする インタフェースはできるだけ小さく(できる限り単純に),狭く(必要な機能し か提供せず),迂回できないようにすべきである.信頼はできるだけしないよ うにすべきである.アプリケーションやデータのビューアは外部で作られた ファイルの表示に使われることがあるので,安全な「砂箱」を作るだけの大量 の作業をするつもりがないのであれば,普通はプログラム(自動実行マクロを 含む)の実行を許可してはならない. 6.2. 与える権限をできるだけ小さくする 先に述べたように,プログラムに持たせるのは作業に必要最小限の権限だけに すべきであるという重要な一般原則がある.こうしておけば,プログラムが壊 れても被害は抑えられる.もっとも極端な例は,安全にしなければならないよ うなプログラムを単に書かないことである.それで済むのであれば,ぜひそう すべきである. Linux では,プロセスのパーミッションを決める主要な要素は,プロセスが 持っている ID である: 各プロセスはユーザとグループの両方について実 ID, 実効 ID, ファイルシステム ID, 保存 ID を持っている.これらの値をうまく 処理することは,パーミッションを最小限に保つ上で非常に重要である. パーミッションは,以下で述べる複数の観点から最小化すべきである: o 与える最高のパーミッションをできるだけ小さくすること.プログラムに は,root 権限は可能ならば与えない.あるファイルにアクセスする必要が あるだけならば,プログラムに setuid root してはならない.この場合は 専用のグループを作り,問題のファイルをそのグループに入れ,そしてプ ログラムをそのグループに setgid することを考慮すべきである.このよ うに,プログラムは setuid でなく setgid するように試みること.なぜ なら,グループに所属する方が得られる権限が小さいからである(特に,グ ループに所属しただけではファイルのパーミッションを変更する権限は得 られない).もし,ファイルにアクセスするためにプログラムのユーザパー ミッションが常に切り替わっている場合(例えば NFS サーバ等)は,Linux 固有の値である「ファイルシステム UID」(FSUID)だけの設定を考えるこ と.なぜなら, FSUID を使えば,競合状態を起こさずに,しかもユーザか らプロセスにシグナルを送らせないでファイルアクセスを制限できるから である. プログラムに root 権限を与えなければならない場合は,Linux 2.2 以降 で利用可能な POSIX のケーパビリティ機能を使い,プログラムを起動して すぐに権限をできるだけ小さくすることも考慮すること. cap_set_proc(3) や Linux 固有の capsetp(3) を関数を起動直後に用いる ことによって,それ以降はプログラムの権限を本当に必要な権限だけに減 らすことができる.必ずしも全ての UNIX 的 OS には POSIX ケーパビリ ティが実装されていない点に注意すること. Linux における POSIX ケー パビリティの実装に関する詳しい情報については, を参 照すること. o パーミッションが有効な時間をできるだけ短くすること.必 ず,setuid(2), seteuid(2) やこの関係の関数を用いて,プログラムのパ ーミッションが必要な時だけに有効となるようにすること. o パーミッションが有効になり得る時間をできるだけ短くすること.できる 限り早く,パーミッションを完全に放棄すること. Linux は「保存」ID を持っているので,最も簡単なアプローチは他のユーザの ID を二度信頼 できない ID に設定することである. setuid/setgid されたプログラムで は,そうしない理由があるのでなければ普通は実効 GID と実効 UID に実 際の ID を指定すること.特に fork(2) の直後はこれを守ること. root 権限を捨てて他のユーザの権限に切替えるときはまず GID を変更しなけれ ばならない点に注意すること.そうしなければ動作しない! o パーミッションを持つ必要があるモジュールの数をできるだけ少なくする こと.パーミッションを持っているモジュールの数が少なければ,そのモ ジュールが安全かどうかを調べることがずっと容易になる.これを実現す る方法の一つは,前の項目に従うことである.つまり,ある一つのモジュ ールが権限を使った後にそれを捨てれば,後で呼び出された他のモジュー ルがその権限を濫用することは不可能である.別の方法としては,コマン ドを分けるやり方がある.つまり,ある特権ユーザ(root 等)のために色々 な種類の作業が行える単独のコマンドと,setuid されているけれど小さ く,単純であり限られた一部のコマンドだけを許す(入力が正しければ作業 コマンドを最初のプログラムに渡す)ツールを用意するのである.これは GUI ベースのシステムでは特に有効である.つまり,GUI 部分は一般ユー ザで動作させ,GUI 部分からのリクエストを特殊権限を持った別のモジュ ールに渡すのである. o 利用可能なリソースをできるだけ少なくする.ファイルやディレクトリの パーミッションは,プログラムを使って書き換えられるのが少数のユーザ だけになるように設定すべきである.これはゲームのハイスコアなどでよ く使われる方法である.つまり,ゲームは普通は games グループに setgid にされていて,スコアファイルはグループ games が所有している が,ゲームのプログラムは誰か別のユーザ(root など)が所有している.こ うしていれば,ゲームのシステムを破った犯人はハイスコアを変更できる が,ゲームの実行ファイルや設定ファイルを変更することはできない. 異なる機能には異なるユーザやグループを用意することを検討すること. そうすれば,あるシステムを破っても,そのまま続けて他のシステムに被 害を与えることはできない. chroot(2) コマンドを使えば,プログラムが限られた数のファイルしか利 用できないようにできる.これを行うにはディレクトリを注意深く設定す る必要がある(いわゆる「chroot の牢獄(chroot jail)」). root 権限を 持ったプログラムはそれでもシステム破りを行えるが (mknod(2) 等のシス テムコールでシステムメモリを変更する),それ以外の場合はこのような 「牢獄」を使うことでプログラムのセキュリティが大幅に向上する. OS によっては,ひとつのプロセス内に信頼処理を行う層を複数個持っている ものがある.例えば,Multics のリング機構などである.標準の UNIX や Linux は,単独のプロセス内で関数を使ってこのように信頼処理のレベルを複 数個に分割する方法を持っていない.カーネルに対するシステムコールでパー ミッションを向上させることはできるが,一つのプロセスは一つの信頼レベル しか持てない. Linux や他の UNIX 的 OS では,ひとつのプロセスを複数個 のプロセスに fork させることよってこの機能を疑似的に実現できることもあ る.これを行うには安全な通信経路を設定し(普通は名前なしパイプが使われ る),それから複数のプロセスへの fork を行い,そしてできる限り多くのパ ーミッションを捨てる.それから,単純なプロトコルを用いて信頼度の高いプ ロセスから信頼度の低いプロセスに動作を要求できるようにし,信頼度の高い プロセスは限られた要求にしか対応しないことを確実に行う. これは Java 2 や Fluke のような技術が優位性を持っている分野のひとつで ある. Java 2 では,細かく分けられたパーミッションが指定できる(例: 特 定のファイルだけをオープンできるパーミッション).しかし,普通は汎用の OS はこのような機能は持っていない. Linux の各プロセスは Linux 固有の状態値を 2 つ持っている.つまり,ファ イルシステムユーザ ID (FSUID) とファイルシステムグループ ID(FSGID) で ある.これらの値はファイルシステムのパーミッションをチェックするときに 使われる. root 権限を持つプログラムは,ID を一般ユーザのもの切り替え てファイルシステムをアクセスするのではなく,単に FSUID と FSGID を変え るだけという方法も検討すべきである.その理由は,プロセスの実行 UID を 設定すると,その UID に対応するユーザはそのプロセスにシグナルを送れる が,FSUID を変更しただけならばシグナルを送れないからである.この方法の 利点は,このシステムコールは他の POSIX システムには移植できない点であ る. 6.3. デフォルトは安全な設定にする インストール時は,ユーザに設定の機会を与えるまでは,プログラムは全ての アクセスを拒否すべきである.インストールされたファイルやディレクトリは 世界中から書き込める状態であってはならない.また実際には,信頼するユー ザ以外には読めなくしておくとよいだろう.設定を記述するところがあれば, デフォルトの設定はユーザが特別に許可していなければアクセスを拒否する設 定にしてあるべきである. 6.4. フェールオープン 安全なプログラムは常に「フェールオープン」であるべきである.つまり,プ ログラムが何か失敗すると,プログラムは全てのアクセスを拒否するように設 計されているべきである(「フェールセーフ」とも呼ばれる).もし何らかのお かしな動き(不正な入力,「なるはずのない状態に」なること等)があるような らば,プログラムは即座にサービスを停止すべきである.「ユーザの求めてい ることを調べ」ようとはせず,即座にサービスを禁止すること.このようなや り方は(ユーザから見た場合の)信頼性や便利さを損ねることがあるが,セキュ リティは向上する. 6.5. 競合状態の回避 安全にすべきプログラムで,受け取ったリクエストを認めるべきかどうかを決 めなければならず,もし認めるべきであればリクエストを実行しなければなら ない.プログラムがリクエストを実行する前の意思決定には,信頼できないユ ーザが何らかの影響を及ぼす手段があってはならない. この問題はファイルシステムでは繰り返し起きる.プログラムでは一般的に, リクエストを認めるかどうかを決めるために access(2) を用い,その後で open(2) を使うのは避けるべきである.なぜなら,2 つのシステムコールが呼 ばれる合間にユーザがファイルを移動させることができるからである.安全な プログラムでは,この方法ではなく,実効 ID かファイルシステム ID を設定 し,それから直接 open システムコールを呼び出すべきである. access(2) を安全に使うことも可能ではあるが,それはユーザがファイルおよびファイル システムのルートからのパス上にあるどのディレクトリにも影響を及ぼせない 時だけである. 6.6. 信頼できる通信経路しか信頼しない 一般的には,信頼できない通信経路から得た結果を信頼してはならない. ほとんどのコンピュータネットワーク(もちろん一般のインターネットの場合 も)認証されていない送信データはまったく信頼に値しない.例えばインター ネット上ではヘッダ値を含めて任意のパケットを偽造できるので,パケットの 認証を行える場合を除いて,これらの値をセキュリティ的な判断を下すための 主な基準にしてはならない.場合によっては,「内部ネットワーク」から来た と主張しているパケットが実際にそうであると断言できることもあるが(ロー カルのファイアウォールが外部からのなりすましを防ぐ場合など),ファイ ウォールが破られたり,別の経路があったり,携帯機器を持ち込まれた場合に はこのような仮定も疑わしくなってしまう.同様の話で,低位のポート番 号(1024 未満)が信用できると仮定してはならない.ほとんどのネットワーク ではこのようなリクエストは偽造できるし,低位のポート番号が使えるような プラットフォームを作ることもできるからである. 標準であるが本質的に安全でないプロトコル(例: ftp や rlogin)を実装しよ うとしている場合は,安全なデフォルト設定を用意し,前述のような仮定を明 文化しておくこと. ドメインネームシステム(DNS)はコンピュータの名前と IP(数値)アドレスを結 びつけるためにインターネット上で広く使われている.「DNS 逆引き」と呼ば れる技術を使えば,一部の単純ななりすまし攻撃を防ぐことができ,ホストの 名前を調べるときにも役立つ.しかし,この技術を信頼して認証の決定に使う ことはできない.この問題は結局,DNS のリクエストは最終的には攻撃者が制 御しているかもしれないどこかのリモートシステムに送られるという点であ る.したがって,DNS の参照結果を入力として扱う際には,検証を行う必要が あるし,結果を信用して重要なアクセス制御に用いてはならない. パスワードを求める場合は,信頼できる経路をできるだけ用意すること(例え ば,ログインの前に偽造できないキーを押させたり,LED の点滅のような偽造 できないパターンの表示を行うことが必要である).パスワードを扱うときに は,パスワードの暗号化を接続の信頼できる端点で行うこと. また,どんな電子メール(「From 行」のアドレスを含む)でも偽造することが 可能である.電子署名の利用は,このような攻撃の多くを阻止する手段とな る.もっと簡単な防御手段は,ランダムに生成した特殊な値をつけた電子メー ルをやりとりすることである.公開メーリングリストの登録などの重要性の低 いやりとりでは,普通はこの程度で十分である. 信頼できないネットワーク上に信頼できる通信経路を用意することが必要がな らば,何らかの種類の暗号サービス(最低限のところでは,暗号的に安全な ハッシュ)が必要である.詳しくは「暗号のアルゴリズムとプロトコル」の節 を参照すること. クライアント/サーバモデル(CGI を含む)はどんな形のものであれ,サーバは クライアントはどんな値でも変更できることを想定していなければならない. 例えば,いわゆる「隠しフィールド」やクッキーの値も,CGI プログラムが受 け取る前にクライアント側で変更できる.クライアントが偽造を行えず,サー バがチェックできるような形で署名がなされているのでなければ,このような 値を信用することはできない. 関数 getlogin(3) や ttyname(3) は,ローカルのユーザが制御できる情報を 返すので,セキュリティ上の目的でこれらの情報を信用してはならない. 6.7. 内部的な整合性をチェックするようなコードを書く プログラムは,関数呼び出しの引数と基本状態の仮定が正しいことを保証する ためのチェックを行わなくてはならない. C 言語では,assert(3) のような マクロがチェックに役立つ. 6.8. 自分自身でリソースを制限する ネットワークデーモンの場合は,高すぎる負荷は切り捨てるか制限すること. limit 値を設定し(setrlimit(2) を使用),使われるリソースを制限するこ と.少なくとも,「core」ファイルの生成は setrlimit(2) で起こらないよう に設定すること.プログラムが異常終了したときに Linux が生成する core ファイルには通常は全てのプログラムメモリが保存されるが,このようなファ イルにはパスワードやその他の問題があるデータが入るかもしれないからであ る. 7. 他のリソースの呼び出しの際には注意する 7.1. 呼び出しの際に正しい値だけを使う 他のプログラムの呼び出しは,全ての引数が正しくかつ期待されている値であ る場合に限って許すこと.これは見た感じよりも難しいことである.なぜな ら,おかしな方法で低レベル関数を呼び出しているかもしれないライブラリ関 数やコマンドがたくさん存在するからである.例えば,popen(3) や system(3) などのシステムコールはコマンドシェルを呼び出す方法で実装され ているので,シェルのメタ文字の影響を受けてしまう.同様に,execlp(3) と execvp(3) はシェルを呼び出すことがある.多くのガイドラインで は,popen(3), system(3), execlp(3), execvp(3) の使用は絶対に避け,C で プロセスを立ち上げる時には直接 execve(3) を使うことを勧めている[Galvin 1998b].同じように,Perl やシェルのバッククォート記号(`)もコマンドシェ ルを呼び出す. この問題で一番悪質な例は,シェルのメタ文字である. Linux の標準のコマ ンドシェルは多くの文字を特別扱いする.このような文字がシェルに送られた 場合,その文字をエスケープしていなければ特殊な処理が行われる.この動作 を利用してプログラムを破ることが可能である. WWW Security FAQ [Stein 1999, Q37] によると,このようなメタ文字は以下の通りである: & ; ` ' \ " | * ? ~ < > ^ ( ) [ ] { } $ \n \r これらの文字のどれかを忘れていると悲惨なことになる可能性がある.例え ば,多くのプログラムはバックスラッシュをメタ文字として省略する[rfp 1999].「全ての入力を検証する」の章で説明したように,望ましいアプロー チは,少なくともこれらの文字が入力に現われたら即座にエスケープすること である. これに関連する問題として,NIL 文字(文字 `0')が困った影響をもたらす可能 性がある. C/C++ のほとんどの関数は,NIL 文字は文字列の終わりを表すも のとしているが,他の言語(Perl や Ada95 等)では NIL 文字を含む文字列を 扱うことができる.多くの関数やシステムコールは C の慣習に従っているの で,チェックしたものが実際に使われるものと異なってしまうことになる[rfp 1999]. 他のプログラムを呼び出すときやファイルを参照するときには,必ず絶対パス で(/usr/bin/sort)指定すること.プログラムを呼び出す場合にこのような指 定を行うと,PATH 変数が正しく設定されていなくても「間違った」コマンド を呼ぶことがなくなる.他のファイルを参照する場合にも,開始ディレクトリ を「間違う」問題を減らすことができる. 7.2. システムコールの戻り値は全てチェックする エラー状態を返せるシステムコールを呼び出す場合には必ず,エラー状態を チェックしなければならない.その理由のひとつは,ほとんど全てのシステム コールはシステムのリソースを制限する必要があるが,ユーザはいろいろな方 法でリソースに影響を与えられることである. setuid/setgid されたプログ ラムには,setrlimit(3) や nice(2) といったシステムコールを使って制限を 設定するとよい.サーバプログラムや CGI スクリプトを外部から使うユーザ は,大量のリクエストを同時に送るだけでシステムのリソースを食い潰すこと ができるかもしれない.エラーを無難に処理できないのであれば,先に説明し たフェールオープンな対応を行うこと. 8. 相手に返す情報は注意深く選ぶ 8.1. 返す情報をできるだけ少なくする 信頼できないユーザには情報を与えすぎてはならない.単に成功または失敗を 返し,失敗の場合も単に失敗した事実と失敗した理由に関する最小限の情報を 返すだけにすること.詳しい情報は監査するためのログとして保存する.以下 に例を示す: o プログラムが何らかのユーザ認証を必要とする場合(ネットワークサービス やログインプログラム等を書いている場合),認証の前にユーザに与える情 報はできる限り少なくすること.特に,認証の前にプログラムのバージョ ン番号を教えることは避けなければならない.プログラムのバージョンを 教えてしまい,しかもそれが弱点を持っているバージョンであることを明 らかになった場合,そのバージョンからアップグレードしていないユーザ は弱点を持っていることを攻撃者に宣伝しているようなものである. o パスワードを受け付けるプログラムの場合,パスワードをエコーバックし てはならない.パスワードをエコーバックすると,パスワードを覗ける抜 け道を作ってしまうことになる. 8.2. 出力先が詰まっている場合や応答しない場合への対処 ユーザは,安全にすべきプログラムがユーザに情報を返す出力経路を詰まらせ たり,応答しなくさせることができる.例えば,故意にウェブブラウザを停止 させたり,TCP/IP 経路の反応を遅くできる.安全にすべきプログラムは,こ ういった場合にも対処できなければならない.特に,サービス妨害攻撃の余地 を与えないために,(できれば応答を返す前に) 素早くロック状態から抜け出 せるべきである.ネットワーク向けの書き出しリクエストには,必ずタイムア ウトを設定すべきである. 9. 特殊な話題 9.1. ロッキング 何かを行う権利をプログラムに対して排他的に保証しなければならない状況は よく起こる. POSIX システムの慣習では,これはロック状態を示すファイル を作ることによって行われる.なぜなら,この方法は多くのシステムに移植で きるからである. しかし,避けなければならない罠もいくつかある.まず,root 権限を持つプ ログラムは,O_EXCL モードでオープンされているファイル(普通は既にファイ ルがあれば失敗する)であってもオープンできる.こうなる可能性がある場合 には,open(2) ではなく link(2) を使ってファイルを作成すること.一つの マシンで同時に複数の同じサーバを動作させたくないだけであれば, /var/log/NAME.pid というロックファイルを作り,その中に pid を書き込む 方法も検討すること.この方法には,プログラムが途中で止まるとロックファ イルが残ってしまうという欠点もあるが,よく使われる方法であり,他のシス テムツールでも簡単に扱うことができる. 次に,ロックファイルが NFS マウントしたファイルシステム上に置かれる可 能性がある場合には,NFS は通常のファイルに対する操作を完全にはサポート していないという問題が起こる.open(2) のマニュアルには,このようなケー スに対処する方法が書かれている(root のプログラムの問題の扱い方も説明さ れている): …ロック処理を行う際に [open(2) の O_CREAT フラグと O_EXCL フラグ] に 頼っているプログラムは,競合状態になる可能性がある.ロックファイルを 使って atomic な(不可分な)ファイルのロックを行う方法では,同じファイル システム上にユニークなファイル(例えばホスト名と pid を組み合わせたも の)を作ることである.これを行うには,link(2) を使ってロックファイルへ のリンクを作り,stat(2) を使ってそのリンクカウントが 2 に増えたかどう かを調べること. link(2) システムコールの戻り値を使ってはならない. 9.2. パスワード 可能であれば,パスワードを処理するコードは書かないほうがよい.特に,ア プリケーションがローカルで使うものであれば,ユーザによる通常のログイン 認証に頼るようにすること.アプリケーションが CGI スクリプトならば,防 御はウェブサーバに任せるとよい.ネットワーク上で動作するアプリケーショ ンの場合は,パスワードを平文で送るのは(できるなら)避けるべきである.な ぜなら,平文でネットワークを流れたパスワードはネットワーク盗聴ツールで 簡単に拾えるため,後で使われるおそれがあるからである.ネットワークの場 合には少なくともダイジェストパスワードの使用を検討すること(これは能動 的な攻撃には弱いが,受動的なネットワーク盗聴は防ぐことができる). 作成するアプリケーションでパスワードを扱う場合には,パスワードが明らか になるのをできるだけ避けるため,使用後にはすぐパスワードを書き潰すこ と. Java の場合は,パスワードの保持に String 型を使ってはならない.な ぜなら,String 型の内容は変化しないからである(ガベージコレクションが行 われて再利用されるまでは書き潰されることはなく,そうなるのもおそらく ずっと時間が経ってからである).パスワードの保持には String 型ではなく char[] 型を用いること.そうすれば,すぐに書き潰すことができる. ユーザがパスワードを設定できるアプリケーションの場合は,パスワードを確 認し,「良い」パスワードだけを受け付けること(例: 辞書にない言葉であっ たり,ある程度の長さがある等).良いパスワードの選び方については 等の情報も調 べるとよいだろう. 9.3. 乱数 Linux カーネル(1.3.30 以降)には乱数生成器が入っている.乱数生成器は, 環境ノイズをデバイスドライバなどの入力源から集めてエントロピープールに 入れる. /dev/random がアクセスされると,エントロピープール内の乱雑さ の度合を推定したビット数に限られた範囲でランダムなバイト列が返される (エントロピープールが空の時は,この呼び出しは新しく環境ノイズが集まる まではブロックされる). /dev/urandom としてアクセスされた場合は,たと えエントロピープールが空であっても,要求された数だけのバイト列が返され る.暗号に使うため(例: 鍵の生成)にランダムな値を使うのであれば, /dev/random を使うこと.さらに詳しい情報については,マニュアルの random(4) を参照すること. 9.4. 暗号のアルゴリズムとプロトコル 暗号のアルゴリズムとプロトコルはシステムの安全を確保するために必要なこ とが多い.特に,インターネットのように信頼できないネットワークを通じて 通信する時はそうである.可能であれば,セッションのハイジャックを防ぎ, 認証情報を隠し,さらにプライバシーを守るために通信セッションの暗号化を 行うべきである. 暗号のアルゴリズムとプロトコルを正しく作るのは困難なので,自分では作ら ない方がよい.その代わりに,SSL, SSH, IPSec, GnuPG/PGP, Kerberos と いった既存の標準準拠のプロトコルを使うとよい.オープンに公開されてい て,長年の攻撃にも耐えた暗号アルゴリズム(これには triple DES も含まれ る.このアルゴリズムにも特許の制限は付いていない) だけを使うこと.特 に,暗号の専門家であり何をやっているのかがわかるのでなければ,自分独自 の暗号アルゴリズムを作るべきではない.こういったアルゴリズムを作るのは 専門家だけがすべき作業である. 9.5. Java Linux の一部のセキュリティ関連プログラムは Java 言語や Java 仮想マシン (Java Virtual Machine, JVM)を使って作られている. Java を使った安全な プログラムの開発については,Gong [1999] などが実例を挙げて詳しく説明し ている.以下に,重要な点をいくつか Gong [1999] から引用する: o pulic なフィールドや変数を使わない.フィールドや変数は private で宣 言してアクセス側に提供すること.そうすればアクセスに制限をかけるこ とができる. o private にしない十分な理由がなければ,メソッドは private にする. o static なフィールド変数を使わない.このような変数は(クラスのインス タンスではなく)クラスに割り当てられるが,クラスは他の全てのクラスか ら見つけることができる.その結果,static なフィールド変数は他のクラ スに見つけられるので,安全にするのがずっと難しくなる. o mutable なオブジェクトは,悪意を持っているかもしれないコードに返し てはならない(そのコードが変更されるかもしれないから). 9.6. PAM ほとんどの Linux ディストリビューションには PAM (Pluggable Authentication Modules)という柔軟なユーザ認証機構が入っている.バー ジョン 2.2 の時点では,PAM は RedHat Linux, Caldera, Debian に入ってい る.バージョン 3.1 の時点で FreeBSD も PAM をサポートしている. PAM を 用いると,作成するプログラムを認証方法(パスワード,SmartCard 等) と独 立にできる.基本的には,プログラムが PAM を呼ぶと,PAM はローカルのシ ステム管理者が用意した設定の組を調べ,どの「認証モジュール」が必要かを 実行時に決定する.認証(パスワードの入力など)を必要とするプログラムを作 成する場合には, PAM に対応させるべきである. Linux-PAM プロジェクトに 関する情報は で 見つけられる. 9.7. その他の話題 プログラムの動作に何らかの前提がある場合,少なくともチェックできるもの は実際に使う前に(例えばプログラムの最初で)プログラムでチェックするこ と.例えば,指定されたディレクトリに ``sticky'' ビットが設定されている ことにプログラムが依存しているならば,それを確かめること.このような確 認はほとんど時間はかからないが,重大な問題を回避できる.呼び出す度に確 認すると実行時間が不安なテストについては,少なくともインストール時にテ ストすること. プログラムの起動時,セッションの開始時,動作が疑わしい時には,監査ログ を出力すること.考えられる情報としては,日付,時刻,UID, EUID, GID, EGID, 端末に関する情報,プロセス ID, コマンドラインの値などがある.監 査ログを実装する際には,syslog(3) 関数が役立つだろう. インストール用スクリプトには,できる限り安全にプログラムのインストール を行わせること.デフォルトでは,全てのファイルは root または別のシステ ムユーザの所有にしておき,他のユーザからは書き込めないようにしておくこ と.これにより,root 以外のユーザによるウィルスのインストールを防ぐこ とができる.可能な場所については root 以外のユーザによるインストールも 許すこと.そうすれば,root 権限を持たないユーザや,インストーラを完全 には信用していない管理者でもそのプログラムを使用できる. 可能であれば,root に setuid/setgid したプログラムは作成しないこと.そ の代わりに,ユーザに root としてログインさせればよい. コードに署名しておくこと.そうすれば,入手したものと用意されたものが同 じかどうかを確かめることができる. 安全にする必要があるプログラムは,静的にリンクすることも検討するとよ い.そうすれば動的リンクのライブラリは使われないので,動的リンク機構を 狙った攻撃を防ぐことができる. コードを読む時には,どの条件も一致しない場合も含めて全て考慮すること. 例えば switch 文がある場合にどの case にも一致しないとどうなるだろう か? ``if'' 文がある場合には,条件が偽ならどうなるだろうか? プログラムを動作させる時はコンパイル時のチェックと実行時のチェックは必 ず有効にし,実用的に動くならばチェックオプションはそのままにしておくこ と. Perl のプログラムでは警告フラグ(-w)を有効にしておくべきである.こ のフラグを有効にしていると,危険性がある文や古い仕様の文に対して警告を 出してもらえる.また,たぶん taint フラグ(-T)も有効にしておくべきだろ う.このフラグを有効にしていると,信頼できない入力を何らかの処理に通さ ずに直接使用することができなくなる.セキュリティ関連のプログラムは,全 ての警告オプションを有効にした状態でも警告メッセージが出ないようにコン パイルできるようにすべきである. gcc を使って C や C++ をコンパイルす る時は,少なくとも以下のコンパイルフラグを使用し(ほとんどの警告メッセ ージが有効になる),できれば全ての警告を出ないようにすること: gcc -Wall -Wpointer-arith -Wstrict-prototypes 10. 結論 本当に安全なプログラムの設計と実装を Linux 上で行うことは本当に難しい 作業である.この難しさは,本当に安全なプログラムは敵になる可能性がある ユーザが操作できる全ての入力と環境に対して適切に応答できなければならな い点にある.これは Linux 固有の問題ではなく,他の汎用の OS (UNIX や WindowsNT 等) でも開発者達は同様の試みを行っている.安全でなければなら ないプログラム開発者は,プラットフォームを深く理解し, (本論文のよう な)ガイドラインをよく読んで利用し,(ピアレビューなどの) 保証のための工 程を実施することによりプログラムの弱点を減らさなければならない. 11. 参考文献 ウェブで入手可能な技術記事を特に強調している点に注意すること.なぜな ら,この種の技術情報のほとんどはウェブを使って入手できるからである. [Al-Herbish 1999] Al-Herbish, Thamer. 1999. Secure Unix Programming FAQ. . [Aleph1 1996] Aleph1. November 8, 1996. ``Smashing The Stack For Fun And Profit.'' Phrack Magazine. Issue 49, Article 14. [Anonymous unknown] SETUID(7) . [AUSCERT 1996] Australian Computer Emergency Response Team (AUSCERT) and O'Reilly. May 23, 1996 (rev 3C). A Lab Engineers Check List for Writing Secure Unix Code. [Bach 1986] Bach, Maurice J. 1986. The Design of the Unix Operating System. Englewood Cliffs, NJ: Prentice-Hall, Inc. ISBN 0-13-201799-7 025. [Bellovin 1994] Bellovin, Steven M. December 1994. Shifting the Odds -- Writing (More) Secure Software. Murray Hill, NJ: AT&T Research. [Bishop 1996] Bishop, Matt. May 1996. ``UNIX Security: Security in Programming.'' SANS '96. Washington DC (May 1996). [Bishop 1997] Bishop, Matt. October 1997. ``Writing Safe Privileged Programs.'' Network Security 1997 New Orleans, LA. [CC 1999] The Common Criteria for Information Technology Security Evaluation (CC). August 1999. Version 2.1. Technically identical to International Standard ISO/IEC 15408:1999. [CERT 1998] Computer Emergency Response Team (CERT) Coordination Center (CERT/CC). February 13, 1998. Sanitizing User-Supplied Data in CGI Scripts. CERT Advisory CA-97.25.CGI_metachar. . [CMU 1998] Carnegie Mellon University (CMU). February 13, 1998 Version 1.4. ``How To Remove Meta-characters From User-Supplied Data In CGI Scripts.'' . [Cowan 1999] Cowan, Crispin, Perry Wagle, Calton Pu, Steve Beattie, and Jonathan Walpole. ``Buffer Overflows: Attacks and Defenses for the Vulnerability of the Decade.'' Proceedings of DARPA Information Survivability Conference and Expo (DISCEX), To appear at SANS 2000, . For a copy, see . [Fenzi 1999] Fenzi, Kevin, and Dave Wrenski. April 25, 1999. Linux Security HOWTO. Version 1.0.2. [FreeBSD 1999] FreeBSD, Inc. 1999. ``Secure Programming Guidelines.'' FreeBSD Security Information. [FSF 1998] Free Software Foundation. December 17, 1999. Overview of the GNU Project. [Galvin 1998a] Galvin, Peter. April 1998. ``Designing Secure Software''. Sunworld. . [Galvin 1998b] Galvin, Peter. August 1998. ``The Unix Secure Programming FAQ''. Sunworld. [Garfinkel 1996] Garfinkel, Simson and Gene Spafford. April 1996. Practical UNIX & Internet Security, 2nd Edition. ISBN 1-56592-148-8. Sebastopol, CA: O'Reilly & Associates, Inc. [Gong 1999] Gong, Li. June 1999. Inside Java 2 Platform Security. Reading, MA: Addison Wesley Longman, Inc. ISBN 0-201-31000-7. [Gundavaram Unknown] Gundavaram, Shishir, and Tom Christiansen. Date Unknown. Perl CGI Programming FAQ. [Kim 1996] Kim, Eugene Eric. 1996. CGI Developer's Guide. SAMS.net Publishing. ISBN: 1-57521-087-8 [Miller 1999] Miller, Todd C. and Theo de Raadt. ``strlcpy and strlcat -- Consistent, Safe, String Copy and Concatenation'' Proceedings of Usenix '99. and [OSI 1999]. Open Source Initiative. 1999. The Open Source Definition. . [Pfleeger 1997] Pfleeger, Charles P. 1997. Security in Computing. Upper Saddle River, NJ: Prentice-Hall PTR. ISBN 0-13-337486-6. [Phillips 1995] Phillips, Paul. September 3, 1995. Safe CGI Programming. [Raymond 1997] Raymond, Eric. 1997. The Cathedral and the Bazaar. [Raymond 1998] Raymond, Eric. April 1998. Homesteading the Noosphere. [Ranum 1998] Ranum, Marcus J. 1998. Security-critical coding for programmers - a C and UNIX-centric full-day tutorial. . [RFC 822] August 13, 1982 Standard for the Format of ARPA Internet Text Messages. IETF RFC 822. . [rfp 1999]. rain.forest.puppy. ``Perl CGI problems.'' Phrack Magazine. Issue 55, Article 07. . [Saltzer 1974] Saltzer, J. July 1974. ``Protection and the Control of Information Sharing in MULTICS.'' Communications of the ACM. v17 n7. pp. 388-402. [Saltzer 1975] Saltzer, J., and M. Schroeder. September 1975. ``The Protection of Information in Computing Systems.'' Proceedings of the IEEE. v63 n9. pp. 1278-1308. Summarized in [Pfleeger 1997, 286]. [Schneier 1999] Schneier, Bruce. September 15, 1999. ``Open Source and Security.'' Crypto-Gram. Counterpane Internet Security, Inc. [Seifried 1999] Seifried, Kurt. October 9, 1999. Linux Administrator's Security Guide. . [Shostack 1999] Shostack, Adam. June 1, 1999. Security Code Review Guidelines. . [Sitaker 1999] Sitaker, Kragen. Feb 26, 1999. How to Find Security Holes and [SSE-CMM 1999] SSE-CMM Project. April 1999. System Security Engineering Capability Maturity Model (SSE CMM) Model Description Document. Version 2.0. [Stein 1999]. Stein, Lincoln D. September 13, 1999. The World Wide Web Security FAQ. Version 2.0.1 [Thompson 1974] Thompson, K. and D.M. Richie. July 1974. ``The UNIX Time-Sharing System.'' Communications of the ACM Vol. 17, No. 7. pp. 365-375. [Torvalds 1999] Torvalds, Linus. February 1999. ``The Story of the Linux Kernel.'' Open Sources: Voices from the Open Source Revolution. Edited by Chris Dibona, Mark Stone, and Sam Ockman. O'Reilly and Associates. ISBN 1565925823. [Webber 1999] Webber Technical Services. February 26, 1999. Writing Secure Web Applications. . [Wood 1985] Wood, Patrick H. and Stephen G. Kochan. 1985. Unix System Security. Indianapolis, Indiana: Hayden Books. ISBN 0-8104-6267-2. [Wreski 1998] Wreski, Dave. August 22, 1998. Linux Security Administrator's Guide. Version 0.98. 12. 文書のライセンス この文書は 1999 David A. Wheeler の著作物であり(Copyright (C) 1999 David A. Wheeler), GNU General Public License (GPL) によって保護され ている.再配布は自由に行って構わない.文書のソーステキストを「プログラ ム」と解釈して,以下の条件に従うこと. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 13. 日本語訳について 日本語訳は Linux Japanese FAQ Project が行いました.翻訳に関するご意見 は JF プロジェクト 宛に連絡してください. 改訂履歴を以下に示します. v1.23j, 3 Feburary 2000 翻訳: 藤原輝嘉 校正/技術チェック: o 荒木靖宏 o 土屋哲 o 中野武雄 o 早川仁 o 武井伸光