capgetシステムコール
cap_header.versionは_LINUX_CAPABILITY_VERSION_1/_LINUX_CAPABILITY_VERSION_2/_LINUX_CAPABILITY_VERSION_3のいずれかで、設定可能CAP数となります。cap_header.pidは取得するスレッドIDで、0ならカレントスレッドのCAPを取得します。
.cap_effectiveがスレッドの実際の権限属性です。.cap_permittedはCAP更新する時の許可属性です。.cap_inheritableはroot権限のプロセスがexecした時のプロセスへ継承する属性ですが、これはセキュアLinuxに依存し、そうでない場合、継承する属性は、.cap_bsetと.cap_inheritableの論理和となり、.cap_bsetはFULL故.cap_inheritableは実際の有効性はありません。
init_credは初期プロセスのCAPで、.cap_inheritableはEMPT、.cap_permitted/.cap_effective/.cap_bsetはFULLで、root権限のプロセスがexecしたプロセスへ継承されます。
Linux2では、capsetシステムコールでcap_header.pidのスレッドのCAPを設定可能となっていましたが、Linux3では、root権限であってもカレントスレッドのみのCAPしか更新できないようになっており、運用面として思うに、root権限で動作させるコマンド等のプロセスで、プログラミングミスによる脆弱性を回避させる故かと思います。
#include <sys/syscall.h> #include <stdio.h> #define _LINUX_CAPABILITY_VERSION_1 0x19980330 #define _LINUX_CAPABILITY_U32S_1 1 #define _LINUX_CAPABILITY_VERSION_2 0x20071026 #define _LINUX_CAPABILITY_U32S_2 2 #define _LINUX_CAPABILITY_VERSION_3 0x20080522 #define _LINUX_CAPABILITY_U32S_3 2 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; 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); } }実行結果
[root@localhost test]# ./a.out effective :ffffffff:ffffffff permitted :ffffffff:ffffffff inheritable:00000000:00000000 [kitamura@localhost test]$ ./a.out effective :00000000:00000000 permitted :00000000:00000000 inheritable:00000000:00000000Linux 3では35ケのCAPをサポートしています。実装できるCAP数は、_LINUX_CAPABILITY_VERSION_1なら32、_LINUX_CAPABILITY_VERSION_2/_LINUX_CAPABILITY_VERSION_3なら64です。
.cap_effectiveがスレッドの実際の権限属性です。.cap_permittedはCAP更新する時の許可属性です。.cap_inheritableはroot権限のプロセスがexecした時のプロセスへ継承する属性ですが、これはセキュアLinuxに依存し、そうでない場合、継承する属性は、.cap_bsetと.cap_inheritableの論理和となり、.cap_bsetはFULL故.cap_inheritableは実際の有効性はありません。
init_credは初期プロセスのCAPで、.cap_inheritableはEMPT、.cap_permitted/.cap_effective/.cap_bsetはFULLで、root権限のプロセスがexecしたプロセスへ継承されます。
#define CAP_WAKE_ALARM 35 #define CAP_LAST_CAP CAP_WAKE_ALARM #define cap_valid(x) ((x) >= 0 && (x) <= CAP_LAST_CAP) # define CAP_EMPTY_SET ((kernel_cap_t){{ 0, 0 }}) # define CAP_FULL_SET ((kernel_cap_t){{ ~0, ~0 }}) struct cred init_cred = { .usage = ATOMIC_INIT(4), #ifdef CONFIG_DEBUG_CREDENTIALS .subscribers = ATOMIC_INIT(2), .magic = CRED_MAGIC, #endif .securebits = SECUREBITS_DEFAULT, .cap_inheritable = CAP_EMPTY_SET, .cap_permitted = CAP_FULL_SET, .cap_effective = CAP_FULL_SET, .cap_bset = CAP_FULL_SET, .user = INIT_USER, .user_ns = &init_user_ns, .group_info = &init_groups, #ifdef CONFIG_KEYS .tgcred = &init_tgcred, #endif }; SYSCALL_DEFINE2(capget, cap_user_header_t, header, cap_user_data_t, dataptr) { int ret = 0; pid_t pid; unsigned tocopy; kernel_cap_t pE, pI, pP; ret = cap_validate_magic(header, &tocopy); if ((dataptr == NULL) || (ret != 0)) return ((dataptr == NULL) && (ret == -EINVAL)) ? 0 : ret; if (get_user(pid, &header->pid)) return -EFAULT; if (pid < 0) return -EINVAL; ret = cap_get_target_pid(pid, &pE, &pI, &pP); if (!ret) { struct __user_cap_data_struct kdata[_KERNEL_CAPABILITY_U32S]; unsigned i; for (i = 0; i < tocopy; i++) { kdata[i].effective = pE.cap[i]; kdata[i].permitted = pP.cap[i]; kdata[i].inheritable = pI.cap[i]; } if (copy_to_user(dataptr, kdata, tocopy * sizeof(struct __user_cap_data_struct))) { return -EFAULT; } } return ret; } static int cap_validate_magic(cap_user_header_t header, unsigned *tocopy) { __u32 version; if (get_user(version, &header->version)) return -EFAULT; switch (version) { case _LINUX_CAPABILITY_VERSION_1: warn_legacy_capability_use(); *tocopy = _LINUX_CAPABILITY_U32S_1; break; case _LINUX_CAPABILITY_VERSION_2: warn_deprecated_v2(); case _LINUX_CAPABILITY_VERSION_3: *tocopy = _LINUX_CAPABILITY_U32S_3; break; default: if (put_user((u32)_KERNEL_CAPABILITY_VERSION, &header->version)) return -EFAULT; return -EINVAL; } return 0; } static inline int cap_get_target_pid(pid_t pid, kernel_cap_t *pEp, kernel_cap_t *pIp, kernel_cap_t *pPp) { int ret; if (pid && (pid != task_pid_vnr(current))) { struct task_struct *target; rcu_read_lock(); target = find_task_by_vpid(pid); if (!target) ret = -ESRCH; else ret = security_capget(target, pEp, pIp, pPp); rcu_read_unlock(); } else ret = security_capget(current, pEp, pIp, pPp); return ret; } static inline int security_capget(struct task_struct *target, kernel_cap_t *effective, kernel_cap_t *inheritable, kernel_cap_t *permitted) { return cap_capget(target, effective, inheritable, permitted); } int cap_capget(struct task_struct *target, kernel_cap_t *effective, kernel_cap_t *inheritable, kernel_cap_t *permitted) { const struct cred *cred; rcu_read_lock(); cred = __task_cred(target); *effective = cred->cap_effective; *inheritable = cred->cap_inheritable; *permitted = cred->cap_permitted; rcu_read_unlock(); return 0; }
補足
suコマンドでのuidを変更は、シェルからforkでプロセスを作成し、かかるuidを設定した後、execでシェルを起動することになり、この時、forkでのプロセスのCAPに応じたCAPが設定されるのですが、rootでない場合、無条件にすべてのCAPはクリアされます。Linux2では、capsetシステムコールでcap_header.pidのスレッドのCAPを設定可能となっていましたが、Linux3では、root権限であってもカレントスレッドのみのCAPしか更新できないようになっており、運用面として思うに、root権限で動作させるコマンド等のプロセスで、プログラミングミスによる脆弱性を回避させる故かと思います。