セキュアな 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 の流出を防ぐため