何かを行う権利をプログラムに対して排他的に保証しなければならない状況は よく起こる. 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) システムコールの戻り値を使ってはならない.
可能であれば,パスワードを処理するコードは書かないほうがよい. 特に,アプリケーションがローカルで使うものであれば,ユーザによる通常の ログイン認証に頼るようにすること. アプリケーションが CGI スクリプトならば,防御はウェブサーバに任せると よい. ネットワーク上で動作するアプリケーションの場合は,パスワードを平文で送 るのは(できるなら)避けるべきである.なぜなら,平文でネットワークを流れ たパスワードはネットワーク盗聴ツールで簡単に拾えるため,後で使われるお それがあるからである. ネットワークの場合には少なくともダイジェストパスワードの使用を検討する こと(これは能動的な攻撃には弱いが,受動的なネットワーク盗聴は防ぐこと ができる).
作成するアプリケーションでパスワードを扱う場合には,パスワードが明らか になるのをできるだけ避けるため,使用後にはすぐパスワードを書き潰すこと. Java の場合は,パスワードの保持に String 型を使ってはならない.なぜな ら,String 型の内容は変化しないからである(ガベージコレクションが行われ て再利用されるまでは書き潰されることはなく,そうなるのもおそらくずっと 時間が経ってからである). パスワードの保持には String 型ではなく char[] 型を用いること.そうすれ ば,すぐに書き潰すことができる.
ユーザがパスワードを設定できるアプリケーションの場合は,パスワードを 確認し,「良い」パスワードだけを受け付けること(例: 辞書にない言葉であっ たり,ある程度の長さがある等). 良いパスワードの選び方については http://consult.cern.ch/writeup/security/security_3.html 等の情報も調べるとよいだろう.
Linux カーネル(1.3.30 以降)には乱数生成器が入っている. 乱数生成器は,環境ノイズをデバイスドライバなどの入力源から集めて エントロピープールに入れる. /dev/random がアクセスされると,エントロピープール内の乱雑さの度合を 推定したビット数に限られた範囲でランダムなバイト列が返される (エントロピープールが空の時は,この呼び出しは新しく環境ノイズが集まる まではブロックされる). /dev/urandom としてアクセスされた場合は,たとえエントロピープールが空 であっても,要求された数だけのバイト列が返される. 暗号に使うため(例: 鍵の生成)にランダムな値を使うのであれば, /dev/random を使うこと. さらに詳しい情報については,マニュアルの random(4) を参照すること.
暗号のアルゴリズムとプロトコルはシステムの安全を確保するために必要なこ とが多い.特に,インターネットのように信頼できないネットワークを通じて 通信する時はそうである. 可能であれば,セッションのハイジャックを防ぎ,認証情報を隠し,さらに プライバシーを守るために通信セッションの暗号化を行うべきである.
暗号のアルゴリズムとプロトコルを正しく作るのは困難なので,自分では作ら ない方がよい. その代わりに,SSL, SSH, IPSec, GnuPG/PGP, Kerberos といった既存の標準 準拠のプロトコルを使うとよい. オープンに公開されていて,長年の攻撃にも耐えた暗号アルゴリズム(これに は triple DES も含まれる.このアルゴリズムにも特許の制限は付いていない) だけを使うこと. 特に,暗号の専門家であり何をやっているのかがわかるのでなければ, 自分独自の暗号アルゴリズムを作るべきではない.こういったアルゴリズムを 作るのは専門家だけがすべき作業である.
Linux の一部のセキュリティ関連プログラムは Java 言語や Java 仮想マシン (Java Virtual Machine, JVM)を使って作られている. Java を使った安全なプログラムの開発については,Gong [1999] などが実例 を挙げて詳しく説明している. 以下に,重要な点をいくつか Gong [1999] から引用する:
ほとんどの Linux ディストリビューションには PAM (Pluggable Authentication Modules)という柔軟なユーザ認証機構が入ってい る.バージョン 2.2 の時点では,PAM は RedHat Linux, Caldera, Debian に 入っている.バージョン 3.1 の時点で FreeBSD も PAM をサポートしている. PAM を用いると,作成するプログラムを認証方法(パスワード,SmartCard 等) と独立にできる. 基本的には,プログラムが PAM を呼ぶと,PAM はローカルのシステム管理者 が用意した設定の組を調べ,どの「認証モジュール」が必要かを実行時に決定 する. 認証(パスワードの入力など)を必要とするプログラムを作成する場合には, PAM に対応させるべきである. Linux-PAM プロジェクトに関する情報は http://www.kernel.org/pub/linux/libs/pam/index.html で見つけられる.
プログラムの動作に何らかの前提がある場合,少なくともチェックできるもの は実際に使う前に(例えばプログラムの最初で)プログラムでチェックすること. 例えば,指定されたディレクトリに ``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