seccompめも( ..)φカキカキ

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.cinclude/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も抑える必要があると思いますがカーネル側の動作としてはこの位かなと。