Perlでroot権限で動作させる場合の注意点

root権限で動作させたい常駐プログラムを作って動作させてみたものの、うまくいく場合とうまくいかない場合があって、かなりはまってしまった。
シェル上でrootユーザでperlプログラムを実行した場合と、なんらかのトリガー(メールや、xmlrpcとかでコマンドを受け取って)を経て自動で実行させるときの違いを調べてみた。

perlsec - Perl のセキュリティ
http://perldoc.jp/docs/perl/5.6.1/perlsec.pod

Perl は、そのプログラムが異なる実ユーザー ID、実効ユーザー ID、実グループ ID、実効グループ ID を使って実行されることを検出したときに、自動的に 汚染モード (taint mode) と呼ばれる特別なセキュリティチェックのセットを有効にします。 UNIX パーミッションにおける setuid ビットはモード 04000 で、 setgid ビットはモード 02000 です。これらは重複してセットすることもできます。汚染モードは、コマンドラインフラグ -T を使って陽に有効にすることもできます。このフラグはサーバープログラムであるとか、 CGI スクリプトのような、他の誰かにすりかわって実行されるプログラムに使うことを 強く 勧めます。

おそらくroot権限で動作しない場合には汚染モードになってパスだとか、いろいろなチェックに引っかかっているんだろうと予想。

自分のプログラムの外側から来たデータをプログラムの外の何かに影響を及ぼすために使うことは、少なくともアクシデントででもなければ、できません。すべてのコマンドライン引数、環境変数、ロケール情報(perllocale を参照)、幾つかのシステムコールの結果(readdir(), readlink(), shmread() の変数、 msgrcv() が返したメッセージ、パスワード、getpwxxx() 呼び出しが返した gecos フィールドとシェルフィールド)、すべてのファイル入力といったものは “汚染された”(tainted) と目印が付けられます。汚染されたデータは直接、間接を問わずサブシェルを起動するコマンドに使うことも、ファイルやディレクトリ、プロセスに変更を加えるようなコマンドに使うこともできません。但し 以下の例外 があります。

  • system あるいは exec に対する引数リストの要素として渡した場合には、その要素に対する汚染検査は 行われません。(``も含む)
  • print と syswrite の引数に対する汚染検査は 行われません。

例を示します:

    $arg = shift;		# $arg は汚染された
    $hid = $arg, 'bar';		# $hid も汚染された
    $line = <>;			# 汚染された
    $line = <STDIN>;		# これも汚染された
    open FOO, "/home/me/bar" or die $!;
    $line = <FOO>;		# まだ汚染されている
    $path = $ENV{'PATH'};	# 汚染されているが、下記を参照のこと
    $data = 'abc';		# 汚染されていない

    system "echo $arg";		# 安全ではない
    system "/bin/echo", $arg;	# 安全 (shを使いません)
    system "echo $hid";		# 安全ではない
    system "echo $data";	# PATHを設定するまでは安全ではない

    $path = $ENV{'PATH'};	# $path が汚染された

    $ENV{'PATH'} = '/bin:/usr/bin';
    delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};

    $path = $ENV{'PATH'};	# $path は汚染されていない
    system "echo $data";	# これで安全!

    open(FOO, "< $arg");	# OK - 読み込みのみのファイル
    open(FOO, "> $arg"); 	# Not OK - 書き込みしようとしている

    open(FOO,"echo $arg|");	# Not OK, but...
    open(FOO,"-|")
	or exec 'echo', $arg;	# OK

    $shout = `echo $arg`;	# 安全でない。$shoutは汚染された。

    unlink $data, $arg;		# 安全でない
    umask $arg;			# 安全でない

    exec "echo $arg";		# 安全でない
    exec "echo", $arg;		# 安全 (シェルを使いません)
    exec "sh", '-c', $arg;	# 安全と解釈される。ああ!

    @files = <*.c>;		# 安全でない (readdir() のようなものを使う)
    @files = glob('*.c');	# 安全でない (reっsaddir() のようなものを使う)

どう汚染されているかを調べるには

(汚染されたデータの検出と洗浄)

ある変数が汚染されたデータを保持しているかどうかを検査するため、そして、 "Insecure dependency" メッセージの引き金になる可能性があるかどうかを検査するために、あなたの最も身近にある CPAN のミラーサイトで Taint.pm モジュールを探してみてください。これは 1997 年の 11 月に入手できるようになるでしょう。あるいは、以下のような関数 is_tainted() を使うことができます。

    sub is_tainted {
	return ! eval {
	    join('',@_), kill 0;
	    1;
	};
    }

コマンド実行は注意して書くのは当たり前なんだけど、はまった部分はファイル上書き、追加などのファイル操作でした。
というか単にIO::Allでファイルを追加していたんだけど、これではだめっぽい(少なくとも汚染モードでは)。
で普通にopenする訳なんだけど、いろいろ調べてみた。

参考になったサイト

perl - open my $fh, "comand |"; # はモダンじゃない

http://blog.livedoor.jp/dankogai/archives/51176081.html

モダンPerl入門にある記述で

open(my $fh, '| cat -v');

上記の部分なんだけど、下記のように書く

open my $fh, '|-', qw/cat -v/ or die $!;
http://www.bioinfo.jp/tips.html#setuid

上記はとてもわかりやすく書いてくれています。 引数

以下はperlsecにある例で、

    exec "echo $arg";		# 安全でない
    exec "echo", $arg;		# 安全 (シェルを使いません)

systemやopenなんかを使うときは引数で与えるのがポイント。 セキュリティ的にも安心だし、モダンだぜ!って事のようだ。
余談(perlsecは5.6の時のものみたいだけど、だいぶ前からモダンな(なんだろう?)書き方はちゃんと示されていたわけだ)

http://www.ipa.go.jp/security/awareness/vendor/programmingv1/a04_03.html
にもperlsecのソースが書いてあります。

汚染除去

$ENV{'PATH'} = '/bin:/usr/bin';                  ← PATH 環境変数を設定
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; ← IFS,CDPATH,ENV,BASH_ENV環境
                                                       変数を空にする

この汚染チェックについて調べた理由はファイルの追加がうまくいかなかった事。
具体的にはperlでテストプログラムを書いてシェル上でperl test_01.tなんかを実行した場合にはうまくいくんだけど、xmlrpcサーバとかにコマンドを投げてプログラムを実行しようとしたら、なぜかうまくいかない・・・ なんでだーとだいぶ悩みました。

うまくいかなかったのはPerlさんが気を利かせて汚染モードチェックをしてくれていたから。 危険な状態だったからうまく実行できなくしてくれていたわけかな。 汚染モード自体については何となく知ってはいたものの、ちゃんと調べるとよくわかっていなかったわけで。 環境変数の事とか、モダンなopenな書き方とかって、意味があるんだなあと言うのがわかった。 前から思っていたモダンって何?の意味が少しではあるけど身についた気がする。
あとperlのドキュメントはとてもいい文章だなと思ったのと、日本語訳してくれてる人たちには再度感謝ですわー