seccompはlinuxカーネルの3.5から入った機能でFedora 18の新機能"Syscall Filters"に該当するものなのですが、これがどんなものかなっていう程度のめもです。
Syscall Filtersの機能は名前から想像できる通りシステムコールのフィルターでどのシステムコールが実行できる/できないを設定できる機能です。FedoraのwikiページによるとBerkley Packet Filter (BPF)と同じような感じだと言っています。
設定ファイルを読んでフィルター設定するとかは当たり前といえば当たり前ですがカーネルではなくてユーザーランドのライブラリ(libseccomp)のほうでファイルをパースしてカーネルのseccompのAPIで実際に設定するという形です。
Fedoraは現在の最新版のFedora 17はすでに3.5系のカーネルになっていて、seccompの機能もカーネルレベルではデフォルトで有効になっていますが、ユーザーランドのライブラリのlibseccompとlibseccomp-develパッケージはFedora 18にしかないので(2012/08/14時点)、Fedora 18のレポジトリからsrpmを持ってきてリビルドする必要があります。
[masami@rune:~]$ grep SECCOMP /boot/config-3.5.1-1.fc17.x86_64 CONFIG_HAVE_ARCH_SECCOMP_FILTER=y CONFIG_SECCOMP_FILTER=y CONFIG_SECCOMP=y
リビルドはkojiのパッケージ画面からsrpmをダウンロードして普通にリビルドするだけです。
rpmbuild --rebuild libseccomp-1.0.0-0.fc18.src.rpm
.specファイルにはBuildRequiresが無いのでgcc、make等の基本的な開発ツールのパッケージがあればビルド可能なはずです。インストールにはRequiresのところでカーネル3.5系もしくはそれ以上を要求されますがFedora 17もしくは、自前でカーネルビルドしていれば特に問題無いかと。
Requires: kernel >= 3.5
それではカーネルのほうを見てみますかね。ソースはkernel/seccomp.cとinclude/linux/seccomp.hです。どちらもそんなに大きくないので割と読みやすいですね( ´∀`)bグッ!
他にはsamples/seccompにカーネルのAPIを直接使う形でのサンプルコードがいくつかあります。このサンプルではルールのセットにprctl(2)が使われています。
prctlの処理はseccomp.cの最後の関数prctl_set_seccomp()ですね。
474long prctl_set_seccomp(unsigned long seccomp_mode, char __user *filter)
フィルターのルールはtask_struct毎に設定されるようでtask_structにも変更が入っています。このseccompという変数が追加されたものでこれにフィルターのルールが設定されて行きます。
1411#ifdef CONFIG_AUDITSYSCALL 1412 uid_t loginuid; 1413 unsigned int sessionid; 1414#endif 1415 struct seccomp seccomp;
この変数はseccomp.hでこのように定義されています。
65struct seccomp { 66 int mode; 67 struct seccomp_filter *filter; 68};
となっていて、seccomp_filter構造体はseccomp.cで定義されてます。
54struct seccomp_filter { 55 atomic_t usage; 56 struct seccomp_filter *prev; 57 unsigned short len; /* Instruction count */ 58 struct sock_filter insns[]; 59}; 60
seccomp_filterは一方向のリンクリストになっていて、1ルールごとに1構造体となるようです。
さて、seccomp.cを見ていきます…
まずはルールを追加する所から。seccomp_attach_filter()はseccomp_attach_user_filter()から呼ばれてくるのですが、ユーザーランドから渡された引数をチェックしたり、カーネル空間にコピーしてからseccomp_attach_filter()を呼ぶという感じなのあまり読まなくても良いですね。
新しくルールを追加したらcurrentのフィルターにルールをセットします。
276 /* 277 * If there is an existing filter, make it the prev and don't drop its 278 * task reference. 279 */ 280 filter->prev = current->seccomp.filter; 281 current->seccomp.filter = filter;
フィルターはプロセスの終了時にfork.cのfree_task()からput_seccomp_filter()を呼んで削除します。
204 205void free_task(struct task_struct *tsk) 206{ 207 account_kernel_stack(tsk->stack, -1); 208 free_thread_info(tsk->stack); 209 rt_mutex_debug_task_free(tsk); 210 ftrace_graph_exit_task(tsk); 211 put_seccomp_filter(tsk); 212 free_task_struct(tsk); 213}
put_seccomp_filter()はリンクリストを辿ってメモリを解放していくだけです。
325/* put_seccomp_filter - decrements the ref count of tsk->seccomp.filter */ 326void put_seccomp_filter(struct task_struct *tsk) 327{ 328 struct seccomp_filter *orig = tsk->seccomp.filter; 329 /* Clean up single-reference branches iteratively. */ 330 while (orig && atomic_dec_and_test(&orig->usage)) { 331 struct seccomp_filter *freeme = orig; 332 orig = orig->prev; 333 kfree(freeme); 334 } 335}
使用されたシステムコールがフィルターされているかのチェックや、フィルターされている場合の処理は__secure_computing()が行っています。関数プロトタイプは以下の通りで、システムコールの番号が渡されます。
375int __secure_computing(int this_syscall)
最初のswtich文ではフィルターの設定をチェックしてSECCOMP_MODE_FILTERならさらにswtich文でプロセスをkillする、実行を許可する、SIGSYSを投げる等の処理が行われます。
396#ifdef CONFIG_SECCOMP_FILTER 397 case SECCOMP_MODE_FILTER: { 398 int data; 399 ret = seccomp_run_filters(this_syscall); 400 data = ret & SECCOMP_RET_DATA; 401 ret &= SECCOMP_RET_ACTION; 402 switch (ret) { 403 case SECCOMP_RET_ERRNO: 404 /* Set the low-order 16-bits as a errno. */ 405 syscall_set_return_value(current, task_pt_regs(current), 406 -data, 0); 407 goto skip; 408 case SECCOMP_RET_TRAP: 409 /* Show the handler the original registers. */ 410 syscall_rollback(current, task_pt_regs(current)); 411 /* Let the filter pass back 16 bits of data. */ 412 seccomp_send_sigsys(this_syscall, data); 413 goto skip; 414 case SECCOMP_RET_TRACE: 415 /* Skip these calls if there is no tracer. */ 416 if (!ptrace_event_enabled(current, PTRACE_EVENT_SECCOMP)) 417 goto skip; 418 /* Allow the BPF to provide the event message */ 419 ptrace_event(PTRACE_EVENT_SECCOMP, data); 420 /* 421 * The delivery of a fatal signal during event 422 * notification may silently skip tracer notification. 423 * Terminating the task now avoids executing a system 424 * call that may not be intended. 425 */ 426 if (fatal_signal_pending(current)) 427 break; 428 return 0; 429 case SECCOMP_RET_ALLOW: 430 return 0; 431 case SECCOMP_RET_KILL: 432 default: 433 break; 434 } 435 exit_sig = SIGSYS; 436 break; 437 } 438#endif
SECCOMP_RET_KILLやSECCOMP_RET_TRAPなどはlibseccompのseccomp_init()、seccomp_rule_add()で使用するSCMP_ACT_KILLやSCMP_ACT_TRAPに対応していると思います。
seccomp_run_filters()を呼ぶときにthis_syscallを渡してますが、これってseccomp_run_filters()では使ってないのですね…
194/** 195 * seccomp_run_filters - evaluates all seccomp filters against @syscall 196 * @syscall: number of the current system call 197 * 198 * Returns valid seccomp BPF response codes. 199 */ 200static u32 seccomp_run_filters(int syscall) 201{ 202 struct seccomp_filter *f; 203 u32 ret = SECCOMP_RET_ALLOW; 204 205 /* Ensure unexpected behavior doesn't result in failing open. */ 206 if (WARN_ON(current->seccomp.filter == NULL)) 207 return SECCOMP_RET_KILL; 208 209 /* 210 * All filters in the list are evaluated and the lowest BPF return 211 * value always takes priority (ignoring the DATA). 212 */ 213 for (f = current->seccomp.filter; f; f = f->prev) { 214 u32 cur_ret = sk_run_filter(NULL, f->insns); 215 if ((cur_ret & SECCOMP_RET_ACTION) < (ret & SECCOMP_RET_ACTION)) 216 ret = cur_ret; 217 } 218 return ret; 219}
それはともかく(で良いかはありますが)、seccomp_run_filters()の戻り値からフィルターの動作を取得して、トラップする場合はSIGSYSを投げる、許可の場合はシステムコールを実行と各処理を行っています。
seccompはこのような感じで大雑把な動作はこれらの関数を見ておけば大体事足りるので無いでしょうか。Fedora 18のSyscall Filtersとして考えるとlibseccompも抑える必要があると思いますがカーネル側の動作としてはこの位かなと。