Capabilityを抑制するPAM

kernel-2.6.24から2.6.25?にかけて、POSIX Capabilityに関する動きが大きい。

まず、kernel-2.6.24 では File POSIX Capability の機能が入る。Fedora rawhideでは既にEnabledになっている(汗
これは従来のSetUIDプログラムを置き換える可能性がある。

例えば、/bin/ping は cap_net_raw 特権だけが必要だが、SetUID=0により、全てのroot特権を与えていた。
File POSIX Capabilityにより、一般ユーザが/bin/pingを実行した時に、cap_net_raw特権だけを与えることができるようになる。

実はこの機能、ずいぶん昔に私が作っていたのだが、
http://www.kaigai.gr.jp/index.php?FrontPage#b556e50d
それと同じアイデアでIBMのSerge君が実装したものがマージされている。ただ、それを設定するためのツールは私のものをポーティングしており、libcap-2.01に含まれている。
http://git.kernel.org/?p=libs/libcap/libcap.git;a=commit;h=7fc5f38e2156201580bab15b22fa581b42f5da91

その他、kernel-2.6.24には間に合わないが、Capabilityの64bit化と、Per-Process Capability Bounding Setの機能がある。

64bit化というのは読んで字の如く。

Per-Process Capability Bounding Set というのは、Serge君が投げた以下のパッチで、プロセスに適用可能なCapabilityの範囲を事前に狭くしておくことができる。
http://marc.info/?t=119082124300001&r=1&w=2

元々、こういった機構は存在したが、これをプロセス単位で設定できるようにしたと言う点がミソ。

【ルール】
1.プロセスのCapabilityは、Capability Bounding Setでマスクされる。
2.Capability Bounding Setは、子プロセスに継承される
3.Capability Bounding Setは、落とすことしかできない

セッションの開始時に、Capability Bounding Setをいくつか落としておくと、そこからfork()した子プロセスが仮にSetUID=0なプログラムを実行しても、全てのroot権限を持つわけではなく、あくまでCapability Bounding Setでマスクされた権限しか持たない。

Serge君はSecurity Containerでの利用を意図しているように見えるが、一般的な機能なので、別にどこで使っても構わない。
例えばPAMモジュールで利用するというのも良いだろう・・・と言うことで、実際に作ってみた。

[ビルド&インストール]
# gcc -Wall -c pam_cap_drop.c
# gcc -Wall -shared -Xlinker -x -o pam_cap_drop.so pam_cap_drop.o -lpam
# cp pam_cap_drop.so /lib/security
[設定]
<b>/etc/passwdを編集</b>
tak:x:1004:100:cap_drop=cap_net_raw:/home/tak:/bin/bash
               ^^^^^^^^^^^^^^^^^^^^ /bin/ping できなくする
<b>/etc/pam.d/system-authを編集</b>
session     required      pam_cap_drop.so を加える
[実行例]
[kaigai@masu ~]$ ssh tak@localhost
tak@localhost's password:       ← takでログイン
Last login: Sat Dec  1 10:09:47 2007 from masu.myhome.cx
[tak@masu ~]$ ping 192.168.1.1  ← pingできない
ping: icmp open socket: 許可されていない操作です
[tak@masu ~]$ su
パスワード:
pam_cap_bset[9254]: user root does not have 'cap_drop=' property
[root@masu tak]# cat /proc/self/status | grep ^Cap
CapInh: 0000000000000000
CapPrm: 00000000ffffdfff  ← rootになっても、Capabilityは
CapEff: 00000000ffffdfff     落ちたまま
[root@masu tak]#

小さなプログラムなので、そのままベタッと貼り付けてみる。

/*
 * pam_cap_drop.c module -- drop capabilities bounding set
 * 
 * Copyright: 2007 KaiGai Kohei &lt;kaigai@kaigai.gr.jp>
 */

#include &lt;errno.h>
#include &lt;pwd.h>
#include &lt;stdlib.h>
#include &lt;stdio.h>
#include &lt;string.h>
#include &lt;syslog.h>
#include &lt;sys/prctl.h>
#include &lt;sys/types.h>

#include &lt;security/pam_modules.h>

#ifndef PR_CAPBSET_DROP
#define PR_CAPBSET_DROP 24
#endif

static char *captable[] = {
    "cap_chown",
    "cap_dac_override",
    "cap_dac_read_search",
    "cap_fowner",
    "cap_fsetid",
    "cap_kill",
    "cap_setgid",
    "cap_setuid",
    "cap_setpcap",
    "cap_linux_immutable",
    "cap_net_bind_service",
    "cap_net_broadcast",
    "cap_net_admin",
    "cap_net_raw",
    "cap_ipc_lock",
    "cap_ipc_owner",
    "cap_sys_module",
    "cap_sys_rawio",
    "cap_sys_chroot",
    "cap_sys_ptrace",
    "cap_sys_pacct",
    "cap_sys_admin",
    "cap_sys_boot",
    "cap_sys_nice",
    "cap_sys_resource",
    "cap_sys_time",
    "cap_sys_tty_config",
    "cap_mknod",
    "cap_lease",
    "cap_audit_write",
    "cap_audit_control",
    "cap_setfcap",
    NULL,
};

PAM_EXTERN int
pam_sm_open_session(pam_handle_t *pamh, int flags,
                    int argc, const char **argv)
{
    struct passwd *pwd;
    char *pos, *buf;
    char *username = NULL;

    /* open system logger */
    openlog("pam_cap_bset", LOG_PERROR | LOG_PID, LOG_AUTHPRIV);

    /* get the unix username */
    if (pam_get_item(pamh, PAM_USER, (void *) &username)!=PAM_SUCCESS
        || !username)
        return PAM_USER_UNKNOWN;

    /* get the passwd entry */
    pwd = getpwnam(username);
    if (!pwd)
        return PAM_USER_UNKNOWN;

    /* Is there "cap_drop=" ? */
    pos = strstr(pwd->pw_gecos, "cap_drop=");
    if (pos) {
        buf = strdup(pos + sizeof("cap_drop=") - 1);
        if (!buf)
            return PAM_SESSION_ERR;
        pos = strtok(buf, ",");
        while (pos) {
            int rc, i;

            for (i=0; captable[i]; i++) {
                if (!strcmp(pos, captable[i])) {
                    rc = prctl(PR_CAPBSET_DROP, i);
                    if (rc < 0) {
                        syslog(LOG_NOTICE,
                               "user %s could not drop %s (%s)",
                               username, captable[i],
                               strerror(errno));
                        break;
                    }
                    syslog(LOG_NOTICE, "user %s drops %s\n",
                           username, captable[i]);
                    goto next;
                }
            }
            break;
        next:
            pos = strtok(NULL, ",");
        }
        free(buf);
    } else {
        syslog(LOG_NOTICE,
               "user %s does not have 'cap_drop=' property",
               username);
    }
    return PAM_SUCCESS;
}

PAM_EXTERN int
pam_sm_close_session(pam_handle_t *pamh, int flags,
                     int argc, const char **argv)
{
    /* do nothing */
    return PAM_SUCCESS;
}