OpenBSD の chroot jail httpd で CGI を動かす(1)

chroot jail

OpenBSDhttpd がデフォルトでは /var/www ( Apache の ServerRoot ) に chroot されている( "chrooted" )ことはこないだ書いたとおりですが、ではその中で CGI を実行する場合どんなことが起こるのか、身をもって試してみました。

CGI

OpenBSD 4.6 のデフォルト…かどうかわかりませんが、 ARP Networks さんに入れてもらった状態では /var/www/cgi-bin に CGI のサンプルが3つ入っていました。

bgplg
BGP のための bgpd のステータスを覗けたりするらしいコンパイル済みバイナリ実行ファイル
test-cgi
環境変数を出力するシェルスクリプト
printenv
環境変数を出力する Perl スクリプト

上から下へ、どんどん chroot 環境で動かすのが面倒になっていきますが、色々苦労してみたところ、基本は変わらないようです。というか実はオフィシャルの FAQ にやり方、というより「取り組み方」が書いてありますので、 chroot 環境で CGI を動かしたい、動かすことに価値があると考える方は一読するといいと思いました。

10.16 - Tell me about this chroot(2) Apache?

こんな感じで、直接のやり方だけでなく方針や考え方まで伝えようとする OpenBSD のやり方が大好きです。テオ様のものは、いつでもしゃぶる準備があるといえる。

chroot しない場合

ちなみにこれも FAQ やらマニュアルに書いてありますが、デフォルトの httpdchroot せずに使うには -u オプションを使って起動します。 /usr/sbin/apachectl スクリプトの内容を読むと分かりますが、 /etc/rc.conf の httpd_flags という変数の内容がパラメータとして機能しています。

# use -u to disable chroot, see httpd(8)
httpd_flags=NO          # for normal use: "" (or "-DSSL" after reading ssl(8))

コメントに思いっきり書いてありました。というわけでこれを、

httpd_flags="-u"

こうすればごくふつうに httpd が上がります。ただし、 apachectl のバグといっていいのか意図したものなのか、 restart ではこのオプションの変更は反映されないので、 stop して start します。

# apachectl stop                                                               
/usr/sbin/apachectl stop: httpd stopped
# apachectl start                                                              
/usr/sbin/apachectl start: httpd started

以上。と、書いた時はすっかり忘れていたのですが /etc/rc.conf は編集しない方がいいとされてます(アップグレード時に上書きされるので)。編集したい行だけを抽出した /etc/rc.conf.local を作ってそちらに書くのがより良い生き方です。テオ様がそう言ってるんですから、そうしてください。僕もこの後、そうしました。

OpenBSD版ApacheとオリジナルApacheの違い/驟雨のカーネル探検隊(只今遭難中w

しかしですね、このスクリプトファイル名をご覧になった通り、 OpenBSD デフォルトの httpdApache 1 ベースです。しかも上記のサイトさまの記事など拝見すると、本家 Apache とはもはや結構違うみたい。

そもそも僕らのテオ様が、なんか Apache 2 系のライセンスがどうも気にくわなかったらしいです。テオ様の機嫌を損ねてしまっては OpenBSD で生きていく道はないので、仕方ありません。というわけで、 chroot しないんだったら Apache 2 系を素直に別にコンパイルしてインストールするのがベストプラクティスだと思いますしみんなそうしてるみたいです。

でも、だったらそもそも OpenBSD なんて使ってないよ!。 Ubuntu 使うよ!。と僕は考えていますので、ここは気合で chroot のまま行ってみたいと思います。まずはバイナリ実行ファイル。これは簡単。

バイナリ実行ファイルを CGI 経由で実行する

/var/www/cgi-bin/bgplg です。基本、インストールしたままの状態では、この cgi-bin ディレクトリの内容はこうなっています。

# ls -lsa
total 292
  4 drwxr-xr-x   2 root  daemon     512 May  1 15:56 .
  4 drwxr-xr-x  10 root  daemon     512 May  1 16:58 ..
276 ----------   1 root  bin     140408 Jul 10  2009 bgplg
  4 ----------   1 root  bin        268 Jul 10  2009 printenv
  4 ----------   1 root  bin        787 May  1 17:02 test-cgi

どうです。壮観ですねえ。さすが OpenBSD と言える眺めです。美しい。というわけで、

# chmod 001 bgplg

唐突ですがこうします。 cgi-bin ディレクトリは Apache 設定ファイル中で ScriptAlias になっていますので、これで、 http://xxx.xxx.xxx.xxx/cgi-bin/bgplg は実行可能です。気持ちいいですね。表示される BGP のことは僕は一切分かりませんが!

とはいえ、 001 にするくらいだったら 100 ですよね。 Apache は www ユーザの www グループで上がる設定になっていますので、

# chown www:www bgplg
# chmod 100 bgplg

こっちがベターでしょう。 100 ですよ。みたことないです。クールです。特に他のライブラリに依存していないバイナリ実行ファイルを CGI 経由で実行するのは、これだけで済みます。まあぶっちゃけ、こんな場面ほとんどないでしょうけどね!

シェルスクリプトCGI 経由で実行する

現実的にはこれもあんまり無いと思うんですが、何事も練習です。次にシェルスクリプトCGI 経由で実行してみます。まずこの test-cgi くんですが、中身はこんな感じ。

#!/bin/sh

# disable filename globbing
set -f

echo Content-type: text/plain
echo

echo CGI/1.0 test script report:
echo

echo unko

echo

echo argc is $#. argv is "$*".
echo

echo SERVER_SOFTWARE = $SERVER_SOFTWARE
echo SERVER_NAME = $SERVER_NAME
echo GATEWAY_INTERFACE = $GATEWAY_INTERFACE
echo SERVER_PROTOCOL = $SERVER_PROTOCOL
(以下略)

おっと、何か汚い言葉が混じっていますが、それは動かなくて試行錯誤してむかっぱらを立てた僕が入れたメッセージですね。というわけで見た感じすごい簡単なスクリプトです。ふつうにシェルで実行してみますと、

# chmod 100 test-cgi
# ./test-cgi
Content-type: text/plain

CGI/1.0 test script report:

unko

argc is 0. argv is .

SERVER_SOFTWARE =
SERVER_NAME =
GATEWAY_INTERFACE =
SERVER_PROTOCOL =
(以下略)

こういう出力です。当たり前です。最初は所有権を 100 で試しています。

で、今、こいつを /var/www に chroot された状態かつ www ユーザで実行したいわけです。コマンドでいえば

# chroot -u www /var/www /cgi-bin/test-cgi 
chroot: /cgi-bin/test-cgi: No such file or directory

が動作すればいいのではないかと推測します。スクリプトのパスは、本来 /var/www/cgi-bin/test-cgi ですが、 /var/www に chroot されるので /cgi-bin/test-cgi になります…って、え、ちょっとまって No such file or directory って何。

びっくりしますが、これ、 shebang 行で指定したシェルスクリプトインタプリタである /bin/sh が No such file だって言ってるんです!。いやー、 /bin/sh が「無い」環境なんかオラ初めてだ。さすが chroot

したがって、ここは FAQ の通り次の手順を行います。

  1. 「無い」実行ファイル・ライブラリを chroot jail 中に持ってきて大丈夫か検討(クラックされた httpd によってそれが使われて大丈夫か?/ちゃんと設計されたプログラムされた実行ファイルか?)
  2. ldd で、そのファイルの依存(動的リンク)ファイルを調べる
  3. 依存ファイルがあれば、それについて 1 に戻って繰り返し
  4. すべてのファイルを chroot 内の chroot された後の世界において正しいパスに配置する

で完了。言っておくけど、めんどいよ!

とはいえ、今回はただのシェルスクリプト。機内に持ち込みたいものも /bin/sh ひとつです。さすがに sh くらい機内に持ち込んだってテオ様は怒らないでしょう。テオ様のコード監査をくぐり抜けた実行ファイルであろうし。

# ldd /bin/sh
ldd: /bin/sh: not a dynamic executable
# cd /var/www
# mkdir bin
# cp /bin/sh /var/www/bin/

動的には何にもリンクしていないことを確認して( bash とかだと結構リンクしてます)、持ち込み。パスは頭を巡らせれば上記の通りになるはず。

これでもう一度実行。

# chroot -u www /var/www /cgi-bin/test-cgi 
chroot: /cgi-bin/test-cgi: Permission denied

はい、メッセージ変わりました。パーミッションの問題だけになったようです。

方針としては、バイナリ実行ファイルの時と同じように、所有者を www に変更した上で、実行権だけつけてあげるのが( OpenBSD 的に)ベストだろうと小生考えました。

# chown www:www /var/www/cgi-bin/test-cgi
# chmod 100 /var/www/cgi-bin/test-cgi     
# chroot -u www /var/www /cgi-bin/test-cgi 
/bin/sh: /dev/fd/3: No such file or directory

はい、でました。謎のファイルディスクリプタアクセス不能エラー!。これが、通常だったら 500 (いやもっというと 700 )にするところを 100 から始めたために小生大混乱の元になった部分です。ああ怖い。ちなみに、この状態で CGI 経由でブラウザからアクセスすると、ブラウザには ISE が返ってきた上に、 /var/www/logs/error_log には次の通りのエラーが出ます。

# cat /var/www/logs/error_log
(前略)
/bin/sh: /dev/fd/5: No such file or directory
[Sun May  2 15:19:14 2010] [error] [client XXX.XXX.XXX.XXX] Premature end of script headers: /cgi-bin/test-cgi

なんて禍々しいエラー!。 /dev/fd/5 とかって何みたいな。

その時、理屈もわからない状態で、所有権を 500 にしてみました。読み取り権をつけてあげたわけです。

# chmod 500 /var/www/cgi-bin/test-cgi  
# chroot -u www /var/www /cgi-bin/test-cgi 
Content-type: text/plain

CGI/1.0 test script report:

unko
(以下略)

実行できました。ブラウザからも表示できます。読み取り権が必要なんです。当たり前ですけど。当たり前?なぜ?だって OpenBSD だと、パーミッションが 100 でオーナーが自分のシェルスクリプトを叩いて実行できるんですよ!?

といった感じに盛り上がってきたところで、もう今日は飽きた!ここまで書くのだって丸1時間かかってんだよ!こちとら!ちくしょうめ!ということで全国の OpenBSD ワナビー達よ、待て!次号!(そんな奴いるわけねえよ)