ModSuid2AndModRuidAndLinuxCapability

Apache の suEXEC では setuid/setgid を使って、httpd 実行ユーザとは異なるユーザ権限で CGI プログラムや SSI プログラムを実行できるわけですが、mod_php で処理される PHP プログラムのように、httpd 内で処理されるプログラムは、httpd 実行ユーザの権限で実行されてしまいます。こういったものまで httpd とは異なるユーザ権限で実行するためのモジュールとして、mod_suid2mod_ruid といったものがあります。

mod_suid2 は、httpd を root で起動しておいて、リクエストに応じて(VirtualHost 単位等で)setuid/setgid して実行ユーザを切り替える、という動作をします。そのため、以下のような問題があります。

これに対し、modruid は Linux に実装されている POSIX 1003.1e で定義されたケーパビリティ を利用して、root で httpd を起動することなく、setuid/segid できる権限のみ与えて、プロセス/スレッドの実行ユーザを切り替えています。そのため、modsuid2 が抱える問題点の多くを解消しています。(また、mod_suid2 と違い、参照するファイルやディレクトリのユーザ/グループに実行権限を切り替える機能もあります。)

ここで気になったのは、「setuid/setgid って、プロセス単位じゃなくてスレッド単位でもできるの?」ということ。もしできないのであれば、worker ではなく prefork で動かす必要もある、ということになる。で、結論からいうと「できる」でした。マルチスレッドのコンテキスト切り替えに伴うコスト - naoyaのはてなダイアリー をご覧になると分かるように、スレッドを生成する毎に duptaskstruct(current) して、各スレッドが個別にプロセスディスクリプタを持つので、当然と言えば当然なのですが、modruid を有効にした Apache のプロセス状態を表示することによって、スレッドごとに実行ユーザがちゃんと異なっていることを確認しました。(modruid はリクエスト処理後に元のユーザに戻してしまうため、確認のため戻さないようにソースを少しいじってます。)

$ ps -efL
UID        PID  PPID   LWP  C NLWP STIME TTY          TIME CMD 
daemon    4156  4153  4188  0   27 20:48 ?        00:00:00 /usr/local/apache2/bin/httpd -k start 
miya      4156  4153  4189  0   27 20:48 ?        00:00:00 /usr/local/apache2/bin/httpd -k start
puppet    4156  4153  4225  0   27 20:48 ?        00:00:00 /usr/local/apache2/bin/httpd -k start

ちなみに、Linux での setuid の処理は、kernel/sys.c で以下のようになっています。

#!c
asmlinkage long sys_setuid(uid_t uid)
{
    int old_euid = current->euid;
    int old_ruid, old_suid, new_suid;
    int retval;

    retval = security_task_setuid(uid, (uid_t)-1, (uid_t)-1, LSM_SETID_ID);
    if (retval)
        return retval;

    old_ruid = current->uid;
    old_suid = current->suid;
    new_suid = old_suid;

    if (capable(CAP_SETUID)) {
        if (uid != old_ruid && set_user(uid, old_euid != uid) < 0)
            return -EAGAIN;
        new_suid = uid;
    } else if ((uid != current->uid) && (uid != new_suid))
        return -EPERM;

    if (old_euid != uid) {
        set_dumpable(current->mm, suid_dumpable);
        smp_wmb();
    }
    current->fsuid uid;
    current->suid = new_suid;

    key_fsuid_changed(current);
    proc_id_connector(current, PROC_EVENT_UID);

    return security_task_post_setuid(old_ruid, old_euid, old_suid, LSM_SETID_ID);
}

task_struct 構造体の、fsuid, euid, suid あたりを書き換えているようですね。

まとまりのないエントリになってしまいましたが、ケーパビリティとか、スレッド単位で setuid/setgid できるとか、はじめて知ることが多かったのでとりあえずメモ。