制限
paludarium は、まだ若いプロジェクトです。次のことは、今はできないか、確かめていません。
できないこと
- 動的リンクのプログラム:静的にリンクしたプログラムだけを動かせます。
- 32-bit の x86、AVX、AVX-512、x87、MMX:対象外です。
- ネットワーク:許可の一覧(
--allow-net、Config::with_network、ブラウザでは 中継 の要る JavaScript のnetworkの指定)がなければ、socket()はどのファミリーにもEAFNOSUPPORTを返します。一覧があっても、使えるのは TCP のクライアント、ゲスト自身のソケットのための TCP と UDP のサーバー、名前解決だけです (ネットワーク)。- ゲストがサーバーになれるのは、ループバックの上で自身のソケットに対してだけです (ループバックのサーバー)。ホストもほかのマシンも 接続できず、
bindが取るのはループバックのアドレスと未指定のアドレスだけです (ほかはEADDRNOTAVAIL。Linux ではマシンのどのアドレスも取れます)。 - ループバックでは、閉じた接続は
TIME_WAITを残さず、ポートはすぐに空きます。 Linux では、先に閉じた側のポートを 60 秒持ちます(SO_REUSEADDRがなければEADDRINUSE)。待ち受けの列がいっぱいのときの接続は、acceptが空きを作るまで 待ちます。Linux のクライアントは 1、3、7…秒後に試し直し、最後はETIMEDOUTで あきらめます。SO_REUSEPORTでポートを共有する待ち受けは接続を分け合わず、 すべて最初の待ち受けに届きます。バイトはすぐに届くので、0 でないSO_LINGERの 時間でcloseが待つことはありません。 - UDP のソケットが届くのは、DNS の代わりと、ループバックの上のゲスト自身のソケット だけです。ほかのアドレスへのデータグラムは
ENETUNREACHで、ほかへ接続した ソケットはポートを取りません(Linux は取ります)。データグラムは、そのバイトと 768 バイトをSO_RCVBUFから取ります。Linux はsk_buffの分を数えるので、 いっぱいになって捨てる点が違います。マルチキャストとブロードキャストの データグラムは、どこにも届きません。raw ソケットはEPERM、ICMP のソケットはEACCES、ほかの種類(Linux がモジュールを読み込んで作る SCTP のSOCK_SEQPACKETなど)はESOCKTNOSUPPORTです。 socket(AF_UNIX)はこれまでどおりEAFNOSUPPORTで、ソケットのペアへのconnectとbindはENOSYSです。- DNS の代わりが答えるのは
AとAAAAだけで、EDNS は読まないので、データグラムの 答えは 512 バイトまでです。ホストが引けない名前はSERVFAILです。53 番ポートへの TCP の接続は、いつも DNS の代わりに届き、本物の DNS サーバーには届きません。 - ホストを通した接続が開く前の
getsocknameは、bindしたアドレス、なければ 未指定のアドレスとポート 0 を返します。Linux では取ったポートを返します。bindしたポートはホストに届かず、ホストの接続は自分のポートから出ます。SIOCOUTQは、相手がまだ受け取りを確かめていない分では なく、ホストがまだ送りに取っていない分を数えます。SO_SNDBUFとSO_RCVBUFはgetsockoptのために覚えますが、バッファの大きさは変えません(どちらの向きも 256 KiB です)。TCP_NODELAY以外のオプションはホストのソケットに届きません。 MSG_OOBは緊急データを見つけず(EINVAL)、SIGURGも来ません。
- ゲストがサーバーになれるのは、ループバックの上で自身のソケットに対してだけです (ループバックのサーバー)。ホストもほかのマシンも 接続できず、
- ファイルのマッピング:ファイルのマッピングは Linux と同じくファイルのページ キャッシュを見せます(
MAP_PRIVATEのものは、まだ書いていないページで)。ただし、MAP_SHAREDのマッピングへの書き込みがホストのファイルに届くのは、ゲストが そのファイルを読むかstatしたとき、msyncかfsyncを呼んだとき、ファイルの最後のマッピングがなくなった ときだけです(Linux は自分の都合で書き戻します)。また、ホストのほかのプログラムが ファイルを変えても、マッピングには届きません。Windows のホストでは、O_APPENDで 開いたディスクリプタから作った共有マッピングへの書き込みは、ファイルに届きません。 マッピングしたファイルの終わり(その先のページはSIGBUSになります)は、ゲスト自身の ファイルの操作で決まる大きさです。ホストのプログラムが大きさを変えても、終わりは 動きません。 - 一部のシステムコール:実装していないシステムコールは
ENOSYSを返すので、 プログラムによってはその機能なしで続けられます。 - ファイルの持ち主:マウントしたホストのディレクトリのファイルは root のままなので、 そこで別の持ち主やグループに
chownするとEPERMを返します。set-user-ID や set-group-ID のプログラムを実行しても、ゲストは root のままです。Linux なら ファイルの持ち主やグループになります。 - ソケットのメッセージ:
sendmsgとrecvmsgは、Linux と同じようにSCM_RIGHTSでディスクリプタを、SCM_CREDENTIALSで資格情報を受け渡します。 違いは次のとおりです。- ゲストは root なので、
SCM_CREDENTIALSにはどのプロセス、ユーザー、グループでも 指定できます。Linux では、特権のないプロセスが送れるのは自分のものだけです。 - 読まれていないディスクリプタが、そのディスクリプタしか開いたままにしていない ソケット(たとえば自分自身)の中で待っていると、そのソケットは閉じられません。 Linux ではガベージコレクタが両方を閉じます。
- ゲストは root なので、
- ソケットのオプション:
setsockoptは、効き目を再現していないオプションをENOPROTOOPTで断ります。Linux は受け付けます。SO_PASSSEC、SO_TIMESTAMPINGと、 フィルターなど Linux にある ほかのオプションです。SO_RCVTIMEOとSO_SNDTIMEOは 1000 Hz の刻みに丸めます。 Linux は自分のCONFIG_HZに丸めます。SO_SNDBUFとSO_RCVBUFは値を 返しますが、組のバッファは変わりません。バッファはデータのバイト数だけを数えます。Linux は メッセージや分けた分ごとの付帯分も数えるので、小さなメッセージは Linux より多く入ってから いっぱいになります。SIOCOUTQも、相手がまだ読んでいないバイト数だけを数えます。 ゲストは root なので、CAP_NET_ADMINの 要るオプション(SO_DEBUG、SO_SNDBUFFORCEなど)も設定でき、SO_PEERCREDの uid と gid は 0 です。 - シグナル駆動の入出力:
O_ASYNCが効くのはパイプとソケットだけで、標準入力や端末はSIGIOを送らず、FIOASYNCで付けようとするとENOTTYです。Linux では送ります。 - 名前空間:名前空間のフラグ(
CLONE_NEWNS、CLONE_NEWUTS、CLONE_NEWIPC、CLONE_NEWUSER、CLONE_NEWPID、CLONE_NEWNET、CLONE_NEWCGROUP、clone3ではCLONE_NEWTIMEも)を付けたcloneとclone3は、子を作らずにEINVALで失敗します。 Linux では、root で動くゲストは名前空間を作れます。unshareとsetnsはENOSYSを 返します。 - Linux と macOS 以外のホストでマウントしたディレクトリのファイル:
SEEK_DATAやSEEK_HOLEのlseekはファイル全体をデータとして扱います。Linux では穴を 見つけます。Windows ではst_blocksも大きさを 512 バイトのブロックに切り上げた 値で、Linux ではホストのファイルシステムが確保した分を数えます。ほかの Unix の ホストは自分の値を返します。Linux と macOS のホストでは、どちらもホストから得ます。FIOQSIZEはどのホストでもst_blocksをバイトにした値です。Linux ではファイルシステムが その時点で確保した分で、遅延割り当ての ext4 や overlayfs ではst_blocksより遅れて 数えます。 - ブロックの大きさ:
st_blksize、stx_blksize、FIGETBSZは、どのファイルでも 4096 です。Linux ではファイルのあるファイルシステムのブロックの大きさで、/procと 端末(devpts)は 1024、マウントしたディレクトリはホストのファイルシステムのもの (たいていは 4096)です。マウントしたディレクトリのstatfsは、ここでもホストの値を 返します。ブロックの大きさのないファイルシステム(多くのコンテナの overlayfs など) では、Linux のFIGETBSZはEINVALです。パイプ、ソケット、memfdなどのメモリの 上のファイルは、Linux でも 4096 です。 - Windows の標準入力:Windows のホストは入力が届いているかを見分けられないので、
poll・select・epollはホストの標準入力をいつも読めると見ます。Linux と macOS の ホストと WebAssembly では、入力があるときか終わったときだけ読めます。 - ホストの標準入出力への
FIONREADとSIOCOUTQ:どちらもENOTTYを返します。 Linux では、その先にあるものについて答えます。FIONREADはパイプ、端末、ソケット、 通常のファイルについて、SIOCOUTQは端末とソケットについてです。ゲスト自身のパイプ、 ソケット、通常のファイルは Linux と同じに数えます。 - コアファイル:コアは、カーネルの既定の
core_pattern(作業ディレクトリのcore)と同じように書きます。コアをクラッシュの処理系(systemd-coredump、apport) に渡すホストの設定は再現しません。コアにはレジスタ、致命的なシグナル、補助ベクトル、 メモリが入りますが、Linux が書くノートのすべて(NT_FILE、NT_X86_XSTATE)は 入りません。また、無名のマッピングはすべて入れます。Linux は一度も書かれていない ものを入れません。 - メモリの上限:上限を超える書き込みをしたプロセスが終わります。Linux の OOM killer は、いちばんメモリを使っているプロセスを選びます。
- ネイティブの JIT:JIT があるのは WebAssembly 版だけです。ネイティブ版は インタプリタで動きます。
- stable の Rust:nightly の Rust が必要です。
確かめていないこと
- 確かめたのは、対応している機能 に挙げたプログラムと、命令ごとのテストです。 ほかのプログラムが完全に同じように動くことは、まだ保証していません。
- ファジングは、決まった時間の中で問題が見つからなかったことを示すもので、どんな入力でも 安全だという証明ではありません。
- 差分テストの基準を取った CPU は、記録してある一部の AMD と Intel の CPU です。 まだ観測していない CPU のプロファイルでは、テストがわざと止まるようにしてあります。
- メモリ、タスク(スレッドとプロセス)、開けるファイルの数は、埋め込む側が上限を 指定しない限り無制限です。上限がなければ、信頼できないプログラムはホストのメモリを 使い切るおそれがあります。メモリの上限が数えるのはゲストのページで、ファイルの 中身、パイプやソケットのバッファ、エミュレーター自身のメモリは数えません(コマンドライン、Rust、 JavaScript)。
- Safari の自動テストは、ブラウザの起動を待つ間に時間切れになることがありました。 対策を入れて、10 回続けて成功していますが、今後揺れないことの保証ではありません。
不具合を見つけたら
CPU のフォールトでゲストが終わったときは、シグナル、アドレス、命令のバイト列が 表示されます。--report-enosys を付けて動かし直すと、ENOSYS を返したシステムコールも わかります(コマンドライン)。それらを添えて、 Issue で知らせてください。