最近ゴルフ場を新しいマシンに引越したので、前回の記述より色々とよくなった部分もあるので、それについて書いてみます。といっても sandbox についてだけですが。
http://github.com/shinh/ags/blob/master/be/modules/sandbox.c
新しい sandbox は、 arcus-judge のシステムのやってることをまんまパクった部分が多いです。 kernel いじっちゃうとメンテ大変そうだなーと思ったので module でやってるのと、全体的に色々雑なのが大きな違いです。
http://code.google.com/p/arcus-judge/wiki/ArcusSandbox
kernel module での system call のフックは、 /boot/System.map-2.6.26-2-xen-686 を見て sys_call_table の入ってるアドレスを埋め込むことでフックを実現してます。
execve
ゴルフ場では規定数以上の execve を呼んだプログラムは記録に残さないモードをサポートしています。例えば C 言語でいきなり Perl プログラムを呼ぶようなコードは面白くないもんですから。
というわけで execve は許しつつ数を数える的なことをやってるのですが、まぁこれはシステムコールフックしてる以上簡単。
fork
なんか setrlimit のプロセス数上限はこう、ちゃんとユーザ分けたりしてないとサーバ側プロセスが死ぬという話があって、 fork bomb は適当に野放しにしてたのですが、サーバが勘定作業をするまでに 100 回以上 fork をした子は fork を失敗させるようにしてみました。
これは fork と vfork と clone に対して適用しました。まぁこれはたぶんどうでも良いです。
set*id
正直こう、なんでこんなに UID の類が多いんだと思いました。 real, effective, saved, filesystem と UID の種類があるらしく、その違いはまぁ色々あるけど結局どれにせよ 0 (root) になられてたらヤバいという結論のようでした。
でまぁ種々の手段で root 取られる脆弱性がある時のリスク軽減策というかなんというかとして、まぁ一応このへんの API はだいたい呼んだら EPERM が帰るようにしておきました。
ただここで問題があって、 dosemu はなんか起動時に自分がどんな UID だろうと自分の UID を非 root にセットしようとするらしく、まぁ現在の UID と一致してるなら許すとかその程度のなにか。
setsid, setpgid
デーモンを作られちゃう可能性があるシステムコールですが、 kill の一掃がうまいこといってる気がするのでとりあえず止めてません。
socketcall
外部とのネットワークを遮断してしまうと github につなげないなど色々めんどいので、できればつながってる方がいいのですが、まぁとりあえず従来のゴルフ場と同様に遮断しておきました。
一応 unix domain socket 以外の connect, sendto, recvfrom, sendmsg, recvmsg は殺すような sandbox にはなっていますが、まぁこれで十分かはよくわかってません。
getpriority, setpriority
上記のような記録はグローバル変数に保存しているんですが、それを取得する方法が欲しいということで、普通はデバイスとか使うんでしょうけど、めんどくさいので適当に getpriority と setpriority を乗っ取ることにしました。これらの第一引数は PRIO_PROCESS, PRIO_PGRP, PRIO_USER のどれかしかないらしいので、適当な値ならゴルフ場 sandbox が奪う、とかが手軽にできるのでした。
でまぁ例えば getpriority(1764, SYS_execve) とかで execve の呼び出し回数をチェックできるようにしておきました。実行ごとにリセットしてやるとやりやすいので、 setpriority(1764, SYS_execve, 0) とかでセットすることも可能です。 setpriority 自体の呼び出し回数もチェックしているので、これを使って騙すのもできなくなってるんじゃないかなぁと思います。
あと、 setproperty(1764, SYS_getpid, 3000) とかすると現在の PID を 3000 にセットするようにしておきました。これで setpid(2) が実現できたので、無数の投稿をして PID を狙った値にする涙ぐましい試みをしなくて良いようになりました。
ファイルの一掃
arcus-judge のマネで、実行するディレクトリや、 /tmp や /home/golf を tmpfs でマウントしておいて、実行するごとに unmount することでファイルの一掃をするようにしました。
やってないこと
user を 2 つ作ってサーバ回すヤツと実行するヤツで分担した方がどう考えてもいいとか言われたのですが、面倒だったのでやってない感じです。
追記: テスト
そういえば今回、今まで割と適当だったテストをそれなりに書き直したり追加したりしました。適当なコードを投げてみて期待した文字列が返事に入ってるかを調べるという、 regression test 的なものです。ステートレスだとこういうテストはこの程度でもそれなりにチェックできるからラクだなぁと思いました。
http://github.com/shinh/ags/blob/master/test/test.rb
一応サポートしてる全言語に関してテストがあるので、各言語の Hello のサンプルとしても使えるかも。