セキュアな XS ローダー
perl のプロセス内でサンドボックスを作ろうと思うと、少なくとも以下の2点が必要です。
- オプコードの制限
- DynaLoader::dl_install_xsub を利用したネイティブコード注入
このうち、オプコードの制限については、ops モジュールで行うことが可能です。一方、DynaLoader::dl_install_xsub 関数については、これを単純に使えなくしてしまうと、XS モジュールをロードできなくなってしまうので、一定の条件下でのみ、これが実行されるような仕組みを作ってやる必要があります。
というわけで私案。クロージャーを使って、以下のような形にするのはどうでしょう?
「strict::import とか書き換えられちゃうとダメじゃね?」ということで没ネタですorz
#! /usr/bin/perl use strict; use warnings; use DynaLoader; BEGIN { no warnings qw(redefine); # 信頼できるモジュールの一覧 my %trusted = map { $_ => 1 } qw(Encode.pm IO.pm IO/Seekable.pm IO/Socket.pm Socket.pm XSLoader.pm); my $ix = \&DynaLoader::dl_install_xsub; my $fake = sub { die "no xs:$_[0]\n" }; *DynaLoader::dl_install_xsub = $fake; my @trueINC; unshift @INC, sub { shift; my $module = "$_[0]"; # overload による攻撃を防ぐため文字列化 die "\@INC has been modified\n" unless join("\0", @INC) eq join("\0", @trueINC); my $wanted = $trusted{$module} ? $ix : $fake; return undef if $wanted == \&DynaLoader::dl_install_xsub; do { local *DynaLoader::dl_install_xsub = $wanted; require $module; }; pipe my $rfh, my $wfh or die $!; print $wfh 1; close $wfh; $rfh; }; @trueINC = @INC; }; # ここから信頼できないコード ...
このようにして、外部から指定した XS モジュールのみ読み込みを許可しつつ (上野例では IO::File や IO::Socket が利用できます) 、信頼できないコードからは dl_install_xsub へのアクセス手段がない環境を構築できるのではないでしょうか *1。
あと、足りない点としては、モジュールロード中にシグナルハンドラを経由して dl_install_xsub が流出しないよう、dl_install_xsub を元の関数に差し替えている間、全シグナルハンドラをブロックすべきでしょう。
thanks to id:lestrrat, id:tokuhirom
思いつきベースなので、穴があるかもしれませんが... have fun!
21:25 追記: id:tokuhirom の指摘により SvREADONLY から @INC の比較に変更
22:04 追記: redefined warning が出ないよう、"1" へのファイルハンドルを返すよう修正
22:13 追記: dl_install_stub の置き換えに local を使用、eval block を廃止
23:02 追記: XS モジュールが依存する PurePerl モジュール読み込み時に dl_install_stub を隠蔽するよう修正
*1:@INC に変更がないかチェックしているのは、trusted な XS モジュールが依存するモジュールの差し替えによる dl_install_xsub の流出を防ぐため