コマンドのcap
コマンド実行は、do_execve()からprepare_binprm()を、そこでSEC linuxでないならcap_bprm_set_creds()でCAPが設定されます。
get_file_caps()では、まずcap_permitted/cap_inheritable/cap_effectiveがクリアされ、ファイルのxattrに設定してあるCAPが設定されます。通常未設定故、従って非rootならcap_permitted/cap_inheritable/cap_effective=0となります。
実ユーザ(uid)または、実効ユーザ(euid)がrootなら、new->cap_permitted=old->cap_bset | old->cap_inheritableとし、euidがrootの時のみ、new->cap_effective = new->cap_permittedとなります。
デフォルト実装では、cap_bset=~0で、従って、old->cap_inheritableに関係なくcap_permitted=~0となり、struct cred init_credの.cap_bset = CAP_FULL_SETを.cap_bset = CAP_EMPTY_SETとして構築すれば、cap_inheritable機能継承としての意味合いの実装ができます。
issecure(SECURE_NOROOT)は、rootプロセスであってもCAPを設定できないようにするためで、credのケーパビリティみたいなものです。current->cred->securebitsに設定してあり、他に SECURE_NO_SETUID_FIXUP(uid変更時、capabilityは更新しない。)SECURE_KEEP_CAPS(rootから非root変更時、CAPを更新しない。)があります。デフォルトではcurrent->cred->securebits=0で、全て無効でprctlシステムコールで設定できます。
・uid=0/ueid!=0のケース
・uid=0/ueid=0のケース
get_file_caps()では、まずcap_permitted/cap_inheritable/cap_effectiveがクリアされ、ファイルのxattrに設定してあるCAPが設定されます。通常未設定故、従って非rootならcap_permitted/cap_inheritable/cap_effective=0となります。
実ユーザ(uid)または、実効ユーザ(euid)がrootなら、new->cap_permitted=old->cap_bset | old->cap_inheritableとし、euidがrootの時のみ、new->cap_effective = new->cap_permittedとなります。
デフォルト実装では、cap_bset=~0で、従って、old->cap_inheritableに関係なくcap_permitted=~0となり、struct cred init_credの.cap_bset = CAP_FULL_SETを.cap_bset = CAP_EMPTY_SETとして構築すれば、cap_inheritable機能継承としての意味合いの実装ができます。
issecure(SECURE_NOROOT)は、rootプロセスであってもCAPを設定できないようにするためで、credのケーパビリティみたいなものです。current->cred->securebitsに設定してあり、他に SECURE_NO_SETUID_FIXUP(uid変更時、capabilityは更新しない。)SECURE_KEEP_CAPS(rootから非root変更時、CAPを更新しない。)があります。デフォルトではcurrent->cred->securebits=0で、全て無効でprctlシステムコールで設定できます。
int cap_bprm_set_creds(struct linux_binprm *bprm) { const struct cred *old = current_cred(); struct cred *new = bprm->cred; bool effective, has_cap = false; int ret; effective = false; ret = get_file_caps(bprm, &effective, &has_cap); if (ret < 0) return ret; if (!issecure(SECURE_NOROOT)) { if (has_cap && new->uid != 0 && new->euid == 0) { warn_setuid_and_fcaps_mixed(bprm->filename); goto skip; } if (new->euid == 0 || new->uid == 0) { /* pP' = (cap_bset & ~0) | (pI & ~0) */ new->cap_permitted = cap_combine(old->cap_bset, old->cap_inheritable); } if (new->euid == 0) effective = true; } skip: if (!cap_issubset(new->cap_permitted, old->cap_permitted)) bprm->per_clear |= PER_CLEAR_ON_SETID; if ((new->euid != old->uid || new->egid != old->gid || !cap_issubset(new->cap_permitted, old->cap_permitted)) && bprm->unsafe & ~LSM_UNSAFE_PTRACE_CAP) { /* downgrade; they get no more than they had, and maybe less */ if (!capable(CAP_SETUID)) { new->euid = new->uid; new->egid = new->gid; } new->cap_permitted = cap_intersect(new->cap_permitted, old->cap_permitted); } new->suid = new->fsuid = new->euid; new->sgid = new->fsgid = new->egid; if (effective) new->cap_effective = new->cap_permitted; else cap_clear(new->cap_effective); bprm->cap_effective = effective; if (!cap_isclear(new->cap_effective)) { if (!cap_issubset(CAP_FULL_SET, new->cap_effective) || new->euid != 0 || new->uid != 0 || issecure(SECURE_NOROOT)) { ret = audit_log_bprm_fcaps(bprm, new, old); if (ret < 0) return ret; } } new->securebits &= ~issecure_mask(SECURE_KEEP_CAPS); return 0; }検証サンプル
#include <sys/syscall.h> #include <stdio.h> #include <string.h> #include <unistd.h> #define _LINUX_CAPABILITY_VERSION_1 0x19980330 #define _LINUX_CAPABILITY_U32S_1 1 typedef unsigned int __u32; struct cap_header { __u32 version; int pid; }; struct cap_data { __u32 effective; __u32 permitted; __u32 inheritable; }; void get_cap(char *arg); int main(int argc, char *argv[]) { struct cap_header hdrp; struct cap_data datap; int ret = 0; int pid; get_cap("original process"); hdrp.pid = 0; hdrp.version = _LINUX_CAPABILITY_VERSION_1; datap.effective =1; datap.permitted =1; datap.inheritable =1; if (!syscall(SYS_capset, &hdrp, &datap)) { get_cap("capsetted process"); sleep(1); execl("./getcap", "getcap", "exec", (char *) 0); } else { printf("capset err\n"); } } void get_cap(char *arg) { struct cap_header hdrp; struct cap_data datap; int ret = 0; hdrp.pid = 0; hdrp.version = _LINUX_CAPABILITY_VERSION_1; ret = syscall(SYS_capget, &hdrp, &datap); printf("[%s]\n", arg); if (!ret) { printf(" effective :%08x\n", datap.effective); printf(" permitted :%08x\n", datap.permitted); printf(" inheritable:%08x\n", datap.inheritable); } else { printf("err:%d\n", ret); } }検証結果
・uid=0/ueid!=0のケース
[root@localhost test]# chmod u+s getcap [root@localhost test]# ls -l getcap -rwsr-xr-x 1 kitamura root 5177 9月 20 08:31 getcap
[root@localhost test]# ./a.out [original process] effective :ffffffff permitted :ffffffff inheritable:00000000 [capsetted process] effective :00000001 permitted :00000001 inheritable:00000001 exec effective :00000000:00000000 permitted :ffffffff:ffffffff inheritable:00000000:00000001
・uid=0/ueid=0のケース
[root@localhost test]# chmod u-s getcap [root@localhost test]# ls -l getcap -rwxr-xr-x 1 kitamura root 5177 9月 20 08:31 getcap
[root@localhost test]# ./a.out [original process] effective :ffffffff permitted :ffffffff inheritable:00000000 [capsetted process] effective :00000001 permitted :00000001 inheritable:00000001 exec effective :ffffffff:ffffffff permitted :ffffffff:ffffffff inheritable:00000000:00000001getcapコマンド
#include <sys/syscall.h> #include <stdio.h> #define _LINUX_CAPABILITY_VERSION_2 0x20071026 typedef unsigned int __u32; struct cap_header { __u32 version; int pid; }; struct cap_data { __u32 effective; __u32 permitted; __u32 inheritable; }; void main(int argc, char *argv[]) { struct cap_header hdrp; struct cap_data datap[2]; int i, ret; if (argc == 2) { printf("%s\n", argv[1]); } hdrp.pid = 0; hdrp.version = _LINUX_CAPABILITY_VERSION_2; ret = syscall(SYS_capget, &hdrp, &datap); if (!ret) { printf(" effective :%08x:%08x\n", datap[1].effective, datap[0].effective); printf(" permitted :%08x:%08x\n", datap[1].permitted, datap[0].permitted); printf(" inheritable:%08x:%08x\n", datap[1].inheritable, datap[0].inheritable); } else { printf("err:%d\n", ret); } }