Logo address

市場からショップへ (Ⅱ)

目次

ブレークスルー

wrapper 方式の問題点

Unix/Linux の wrapper 方式には次のような問題が含まれていた。

これらの問題を全て一挙に解決できる可能性が生まれた。Bell-Labs からリリースされた Plan 9 である。

Plan 9 from Bell-Labs

Bell-Labs は Unix を生み出したことでも知られている。その同じグループが Unix の問題点を克服するために Plan 9 を生み出した。Plan 9 は Unix の良い面を継承し発展させながら、Unix とは異なる革新的な技術に支えられている。その一つが per-process name space (プロセス毎の名前空間)である。

name space とはファイルの名前のなす空間であり、これがプロセス毎に異なっていても構わないようにしたのである。
(Unix など、これまでのOSでは、名前空間はどのプロセスから見ても同じである。もっとも Unix には 、その例外として、chroot の仕組みがあるが特権を必要とする。)
他のプロセスからは新たに生成された名前空間は見えない。つまり影響を与えない。そして、新しい名前空間の構成には特権が要らない!

さらに Plan 9 には名前空間を自由に構成するための道具が備わってる。仕切の無い「市場(いちば)」から、壁で区切られている「ショップ」方式で Web サーバーを構成できる可能性が開けたのである。

Pegasus

Pegasus とは Plan 9 で動く Web サーバーである。
2006年の IWP9(International Workshop for Plan 9) で発表された。
実際の運用は 2002年1月1日から始まっている。Pegasus の名前は、その年の干支(えと)である午(馬)に由来する。
Pegasus はサーバサイドのサンドボック(sandbox)化を実現した最初の(そして現在に至るも唯一の) Web サーバーである。これは Plan 9 の「プロセス毎の名前空間」を活用する事によって成し遂げられた。

怪我を避けるために砂を敷いた遊び場

ここで言うサンドボックとはセキュリティ用語であり、悪意を含んだプログラムの次のような動作

を防ぐための、保護された環境のことである。
通常は次のような方法が採られる。

「アクセス空間の限定」に関して、Pegasus では次の図のようになっている。

図で、real space とは、本来の名前空間である。httpd space とは httpd が見る名前空間であり、それは CGI プログラムが見る名前空間でもある。Plan 9 以外の OS では real space と httpd space は一致する。Pegasus では httpd space は、ユーザ毎に異なる。
例えばクライアントが alice のドキュメントにアクセスしている場合には httpd が見る名前空間は、alice がホームページを公開するために必要とするファイルに限定されている。bob についても同様である。

なお、ここで言う「ユーザ」とは Web ドキュメントを管理するシステムのユーザのことである。彼らが管理するドキュメントは、例えば筆者のサーバーであれば、

のように多様である。

Pegasus が見る名前空間の基本構造

Pegasus が見る名前空間の基本構造は、real host, virtual host, user page を問わず共通である。
これによってサーバーコードが簡単になり、また CGI プログラムのポータビリティを可能にしている。
名前空間の基本構造を同じにできるのは、Pegasus の場合にはサンドボックス化を前提として設計されているからである。
この事情は Apache と対照的である。Apache は継ぎはぎ的な設計によって現在に至っている。real host,
virtual host, user page の構造はどれも互いに異なっている。この複雑さはシステム設定を難しくし、一方を立てれば他方が立たなくなるというジレンマを抱え込んでいる。

サンドボックス化の難易度

Web サーバのサンドボックス化の難易度をOS毎に整理し、表に示す。
OS 難易度 メモ
Windows できない? 隠し仕様があるとか?
Unix/Linux 原理的には可能
実際的な困難性
chroot (特権必要)
環境準備が大変
Plan 9 やさしい
必要なものが揃っている
bind (特権不要)
Unix/Linux ではサンドボックス化に手間取っているが、それは原理的は可能ではあるけれども、実際の困難性を伴うからである。対照的に、Plan 9 では易しい。必要な道具立てが全て整っているからである。

環境準備

補足: Linux の mount --bind は負担を軽減してくれる。しかし Plan 9 の bind ほど器用ではない。

プロセスの権限比較

user alice の CGI が実行される場合

プロセス Apache Pegasus
httpd as http (可変)
弱い権限
as web (固定)
弱い権限
wrapper
(suid を使う)
as root
絶対的特権
CGI as alice
強い権限
as web (固定)
弱い権限
wrapper: suexec, cgiwrap, sbox

注1: httpweb も仮想ユーザ

注2: サービスは可能な限り弱い権限で行うのが原則 (破滅的な事故を防ぐ)

注3: alice として実行される CGI は alice の全てのファイルを破壊する威力を持っている

注4: Pegasus のシンプルな仕組みが設定ミスを防ぐ

補足

Pegasus が CGI を web として実行してもセキュリティ上の問題が発生しないのは、httpd から見える空間をユーザ毎に閉じ込めているからである。Apache も閉じ込めを行えば同様に http として CGI が実行可能なはずである。その方が注3で述べた問題が発生しない。

Unix/Linux では閉じ込めには特権が必要になり、wrapper は欠かせない。しかし実際問題として閉じ込めを困難にしているのは、閉じ込めた空間の環境準備である。

閉じ込めた空間には、CGI を実行する Perl や Python などのプログラムの他に、ライブラリなど(さらにダイナミックリンクライブラリなど)を含める必要がある。必要なものはスクリプト言語毎に異なる。Python に至っては /etc/passwd が必要である! もっとも必要としているのは、その一部である。

Stein の SBOX の時代と異なり、現在ではディレクトリのハードリンクはサポートされていない。そのために、必要なファイルはユーザ毎にコピーをして準備しなくてはならない。これは現実的な方法ではない。

セキュリティモデルの比較

ここでは管理者プロセスの権限の面から OS ごとのセキュリティモデルを比較する。

* Windows Unix/Linux Plan 9
管理者アカウント administrator
(変更可能)
root glenda
(変更可能)
特権の範囲 device
file
device
file
device
注2
run as
(no password)
任意のユーザ 任意のユーザ 任意の仮想ユーザ
注1: この表は Windows に関しては単純化されているかもしれない

注2: ファイルサーバーにおいては、(記憶デバイスにファイルが作成されるので)特権は file に及ぶ。ただし特権を与える方法が Unix/Linux と異なる。ファイルサーバーのコンソールから特別に許可するのである。CPU サーバー(サービスを行っている) にはファイルに及ぶ特権プロセスは存在しない。

Plan 9 は CPU サーバーとファイルサーバーを分離することによって、root のような特権プロセスを排除したのである。

chrootbind

次に Unix/Linux の chroot と Plan 9 の bind の機能を比較する。

chroot bind
名前空間の編成はできない 必要に応じて動的に新しい名前空間を編成する
名前空間の一部にプロセスを閉じ込める 名前空間の一部にプロセスを閉じ込めることもできる
管理者権限が必要 管理者権限は不要
他のプロセスは影響は受けない 他のプロセスは影響は受けない
編成された名前空間は、他のプロセスからは見えない
Linux の
	mount --bind dir1 dir2
は(特権は必要であるが) Plan 9 の bind の機能の一部を実現している。これは Plan 9 では
	bind dir1 dir2
に相当する。これが実行されると dir2dir1 と同じ内容が見える。(dir1 のサブディレクトリを含めて)
Linux の場合には、dir2 は空のディレクトリである。空でない場合には、その内容が消去されるが(注1)、Plan 9 の bind ではそのようなことはない。

注1: 警告も無く消すのはバグだろうと思われる。さらに、右と左を間違えると大事になる! dir2 が空で無い場合にはエラーにすべきであろう。

Plan 9 の bind はさらに多才である。

	bind -a dir1 dir2
とすれば、dir2dir1 がミックスされるが、同じ名前があれば、dir2 が採用される。
	bind -b dir1 dir2
の場合には、 dir2dir1 に同じ名前があれば、dir1 が採用される。
オプション a は after、b は before の意味である。その他のオプションも存在するが、ここでは採り上げない。
特殊な使い方として
	bind dir2 / && cd /
とすると dir2 の中に閉じ込められる。

Unix/Linux の現状

次に、Apache を例に Unix/Linux の現状を整理する。

言語プロセッサレベルの対策としては、Perl の taint モードが存在するが、完全にガードを固めるのは難しいであろう。ユーザは自分の好みの言語プロセッサを使いたいであろう。そして、好みは多様である。従って言語プロセッサに依存しない対策が欲しい。願わくば機械語レベルでの対応である。

Apache について言えば、サーバーレベルで

がある。mod_chroot はどうやら廃止の方向らしい。現在では代わりに mod_unixd がある。興味深いのは、これを使う時に chroot のオプションがある点である。これは Apache が特権を抱え込んで動く事を意味する。

クライアントからのリクエストを特権モードを抱え込んだままで処理するのはあまりにも危険である。Apache は太りすぎであり、どこに深刻なバグが含まれているかわからない。Apache が採り得る方法は、クライアントのリクエストを受け取る前に特権を必要とする chroot を行い、その後で特権モードを解除するぐらいであろう。この場合、ユーザ毎の閉じ込めはできないが、我慢するしかないであろう。実際に、Apache の仕様では、chroot を行うディレクトリは1つに固定されている。(もっとも Apache2.4.9 は、これすらバグがあって働いていない)

mod_privilege は、suexec の代わりのはずであるが、これはまだ実験的な位置づけである。mod_privilege の存在は、Apache は将来 wrapper 方式から脱却しようとしていることを示唆している。

問題解決の歴史(2)

SBOX と Pegasus

SBOX は全体として Unix 系の wrapper としては良くできている。もっとも wrapper と CGI の置き場所との関係は cgiwrap の方が良い。(SBOX は wrapper と CGI を共に cgi-bin に置かせるので、ややこしくなる)

筆者は Pegasus が完成してから SBOX の存在を知ったのであるが、両者を比較してみるとディレクトリの基本構造に関する考え方が驚く程よく似ている。

SBOX の構想

Stein が論文の中で構想している SBOX の(ユーザー毎の)ディレクトリ構成は次のようなものである。
	$home/www/bin
	$home/www/cgi-bin
	$home/www/etc
	$home/www/lib
	$home/www/html
	$home/www/tmp
	$home/www/…
$home と書いたのは Pegasus と比較するためで、Unix では $HOME を意味する。サンドボックス化した場合には httpd$home/www 以下しか見えないので、この中に CGI の実行に必要な全てのファイルを押し込む必要がある。bin は実行ファイル、etc/etc の一部である。html が閉じ込められた空間でのドキュメントルートである。

Pegasus の構成

Pegasus の場合には、ユーザーのディレクトリには通常は次の3つが置かれる。
	$home/www/bin
	$home/www/etc
	$home/www/doc
ここに doc が閉じ込められた名前空間でのドキュメントルートであり、SBOX の html に相当する。
bin は SBOX と同様に実行ファイルの置き場所である(ただしユーザが追加したいもののみを置けばよい)。
しかし etc の位置づけは全くことなる。Plan 9 には /etc は存在しない。Pegasus の etchttpd のための管理データの置き場所である。
パスワード(もちろんハッシュ値だけ)などアクセス制御のデータはここに置かれる。何に対してアクセス制限を掛けるべきかは Web ドキュメントの管理者にしか分からない。従ってアクセス制限は Web ドキュメントの管理者の仕事であり、サーバー管理者の仕事ではない。
rewrite ルールや 名前空間の再編成のためのファイルも etc に置けるようになっている。つまりドキュメントの管理者も追加的に行えるようになっている。
セキュリティを確かなものにするために etc は CGI からは見えないようになっている。
binetc は必要に応じて作成する。
なお cgi-bin を持たないのは、筆者はああいうのは嫌いだからである。プログラムが複雑になるばかりで、それに見合うメリットは無い。(逆にディメリットはいろいろ考えられる)

CGI の実行に必要な他のファイル、例えば

	$home/www/lib
	$home/www/tmp
	$home/www/…
は自動的に(動的に)構成される。

作業スペース

tmp は CGI が作業するためのディレクトリで、Stein は注意深くユーザ毎に割り付けている。他のユーザと作業スペースを共有するとセキュリティ上の問題が発生するからである。
では Pegasus は作業スペースを共有するのか? Pegasus ではさらに注意深い扱いがされているのである。

クライアントはサーバーの(通常は80番窓口に)処理を依頼する。込み合った場合には、前の処理が終わらないうちに、次の処理依頼が来る。その結果、ユーザ alice の処理が複数の CGI によって同時並行的に行われる事がありえる。SBOX のように作業スペースを共有していると、CGI が乗っ取られた場合にセキュリティ上の問題が発生する。

幸いな事に Plan 9 では RAM ディスクを自由に生成できる。Pegasus では作業スペースとして RAM ディスクが使われている。従って初めから準備する必要はなく、しかも、その内容は他のプロセスからは見えない。
いや、それ以上に多くのメリットを持っている。

RAM ディスクは(それを作成した)プロセスの消滅と共に消える。CGI が生成した作業ファイルを、わざわざ CGI 自ら消しに行かなくても、自動的に消えてくれるのである。不要な作業ファイルの削除は Unix/Linux では頭の痛い問題であるが、Plan 9 ではプログラムのエラーによって CGI がクラッシュした場合にも、RAM ディスクで作業をしていれば自動的に消える!

補足

自動的に作業ファイルを削除するには、Plan 9 では必ずしも RAM ディスクに頼る必要はない。open() システムコールのオプションに ORCLOSE (remove on close) が存在し、これを使えばファイルがクローズされると自動的にファイルが消去される。
プロセスが終了すれば(クラッシュした場合も)自動的にファイルがクローズされるので、作業ファイルは削除されることになる。もっとも他のプロセスがその作業ファイルを参照している間は削除されないので注意が必要である。
(Plan 9 の RAM ディスクなら、その場合にもプライバシーは保たれる)
この問題はある程度アプリケーション側(スクリプト言語であれば、その言語プロセッサ側)で対応可能ではあるが、完全ではない。削除する前にクラッシュするケースを考えなくてはならないからである。

CGI の自由が欲しい! 他の解決法

Apache に代表される Web サーバーのサンドボックス化に関して、この10年間、Unix/Linux では遅々として進歩が無かったのであるが、対照的に仮想マシンに関しては大きな進歩があった。

仕組みは、Xen と、その他(QEMU など)は異なるが、いずれもソフトウェアでパソコンの疑似環境を構築する。
レンタルサーバーとして、1ユーザに1台のハードウェアを与えるのはコストが掛かりすぎるので、擬似ハードウェアを与えるのである。コストを押さえられる反面、当然ながら遅い。しかしセキュリティを保ちながら CGI の自由が欲しいのであれば、(Unix/Linux の現状では)この選択肢しか無いであろう。
Xen は Amazon がホスティングサービスの中で採用している。

補足: Unix/Linux の Real UID と Effective UID

ruideuid

alice の UID(user ID) を 1000 とすると、alice が実行したプログラムは(通常は)
  (ruid,euid) = (1000,1000)
となる。
こに ruid とは read UID、euid とは effective UID のことであり、C で書かれたプログラムの中で使用可能な名前である。

管理者(root) が実行したプログラムでは(通常は)

	(ruid,euid) = (0,0)
である。(0 は root の UID である。)

この意味について解説する。

状態 意味 補足
euid=0 管理者特権を発揮しながら動く アクセス権の設定を無視
ruid=0 管理者特権をリザーブしている euid を 0 にできる
つまり、いずれかの uid が 0 のプロセスは特権を発揮可能である。例えば
	(ruid,euid) = (0,1000)
のプロセスは外見的には alice が実行したプログラムのように振る舞うが(つまり alice のアクセス権に従う)
	(ruid,euid) = (0,0)
のプロセスに化けることが可能である。

setuid 属性を持つ実行可能プログラム

alice がこれを実行すると
	(ruid,euid) = (1000, X)
となる。ここに X はこのプログラムの所有者 uid である。
root 所有の実行可能プログラムの setuid 属性は特別に危険!

ありえる間違い

昔、まだモデム通信の時代に、筆者は tip+ をリリースしたことがある。これは FreeBSD に添付されていた tip を改良したものである。改良の主な目標は日本語を扱えるようにすることにあった。
ベースにした tip のソースコードを吟味すると、酷いものであった。
なんと、ruid=0 のままで、ユーザに(ユーザが書いた)プログラムの実行を許していたのである。
特権を抱えたプログラムが、他のプログラムを呼び出す場合には、必ず毒を抜いてからにしなくてはならない。