diff options
author | Andrew Morgan <morgan@kernel.org> | 2008-02-04 22:29:42 -0800 |
---|---|---|
committer | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2008-02-05 09:44:20 -0800 |
commit | e338d263a76af78fe8f38a72131188b58fceb591 (patch) | |
tree | f3f046fc6fd66de43de7191830f0daf3bc4ec8eb /kernel/capability.c | |
parent | 8f6936f4d29aa14e54a2470b954a2e1f96322988 (diff) | |
download | lwn-e338d263a76af78fe8f38a72131188b58fceb591.tar.gz lwn-e338d263a76af78fe8f38a72131188b58fceb591.zip |
Add 64-bit capability support to the kernel
The patch supports legacy (32-bit) capability userspace, and where possible
translates 32-bit capabilities to/from userspace and the VFS to 64-bit
kernel space capabilities. If a capability set cannot be compressed into
32-bits for consumption by user space, the system call fails, with -ERANGE.
FWIW libcap-2.00 supports this change (and earlier capability formats)
http://www.kernel.org/pub/linux/libs/security/linux-privs/kernel-2.6/
[akpm@linux-foundation.org: coding-syle fixes]
[akpm@linux-foundation.org: use get_task_comm()]
[ezk@cs.sunysb.edu: build fix]
[akpm@linux-foundation.org: do not initialise statics to 0 or NULL]
[akpm@linux-foundation.org: unused var]
[serue@us.ibm.com: export __cap_ symbols]
Signed-off-by: Andrew G. Morgan <morgan@kernel.org>
Cc: Stephen Smalley <sds@tycho.nsa.gov>
Acked-by: Serge Hallyn <serue@us.ibm.com>
Cc: Chris Wright <chrisw@sous-sol.org>
Cc: James Morris <jmorris@namei.org>
Cc: Casey Schaufler <casey@schaufler-ca.com>
Signed-off-by: Erez Zadok <ezk@cs.sunysb.edu>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'kernel/capability.c')
-rw-r--r-- | kernel/capability.c | 113 |
1 files changed, 104 insertions, 9 deletions
diff --git a/kernel/capability.c b/kernel/capability.c index efbd9cdce132..39e8193b41ea 100644 --- a/kernel/capability.c +++ b/kernel/capability.c @@ -22,6 +22,37 @@ static DEFINE_SPINLOCK(task_capability_lock); /* + * Leveraged for setting/resetting capabilities + */ + +const kernel_cap_t __cap_empty_set = CAP_EMPTY_SET; +const kernel_cap_t __cap_full_set = CAP_FULL_SET; +const kernel_cap_t __cap_init_eff_set = CAP_INIT_EFF_SET; + +EXPORT_SYMBOL(__cap_empty_set); +EXPORT_SYMBOL(__cap_full_set); +EXPORT_SYMBOL(__cap_init_eff_set); + +/* + * More recent versions of libcap are available from: + * + * http://www.kernel.org/pub/linux/libs/security/linux-privs/ + */ + +static void warn_legacy_capability_use(void) +{ + static int warned; + if (!warned) { + char name[sizeof(current->comm)]; + + printk(KERN_INFO "warning: `%s' uses 32-bit capabilities" + " (legacy support in use)\n", + get_task_comm(name, current)); + warned = 1; + } +} + +/* * For sys_getproccap() and sys_setproccap(), any of the three * capability set pointers may be NULL -- indicating that that set is * uninteresting and/or not to be changed. @@ -42,12 +73,21 @@ asmlinkage long sys_capget(cap_user_header_t header, cap_user_data_t dataptr) pid_t pid; __u32 version; struct task_struct *target; - struct __user_cap_data_struct data; + unsigned tocopy; + kernel_cap_t pE, pI, pP; if (get_user(version, &header->version)) return -EFAULT; - if (version != _LINUX_CAPABILITY_VERSION) { + switch (version) { + case _LINUX_CAPABILITY_VERSION_1: + warn_legacy_capability_use(); + tocopy = _LINUX_CAPABILITY_U32S_1; + break; + case _LINUX_CAPABILITY_VERSION_2: + tocopy = _LINUX_CAPABILITY_U32S_2; + break; + default: if (put_user(_LINUX_CAPABILITY_VERSION, &header->version)) return -EFAULT; return -EINVAL; @@ -71,14 +111,47 @@ asmlinkage long sys_capget(cap_user_header_t header, cap_user_data_t dataptr) } else target = current; - ret = security_capget(target, &data.effective, &data.inheritable, &data.permitted); + ret = security_capget(target, &pE, &pI, &pP); out: read_unlock(&tasklist_lock); spin_unlock(&task_capability_lock); - if (!ret && copy_to_user(dataptr, &data, sizeof data)) - return -EFAULT; + if (!ret) { + struct __user_cap_data_struct kdata[_LINUX_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]; + } + + /* + * Note, in the case, tocopy < _LINUX_CAPABILITY_U32S, + * we silently drop the upper capabilities here. This + * has the effect of making older libcap + * implementations implicitly drop upper capability + * bits when they perform a: capget/modify/capset + * sequence. + * + * This behavior is considered fail-safe + * behavior. Upgrading the application to a newer + * version of libcap will enable access to the newer + * capabilities. + * + * An alternative would be to return an error here + * (-ERANGE), but that causes legacy applications to + * unexpectidly fail; the capget/modify/capset aborts + * before modification is attempted and the application + * fails. + */ + + if (copy_to_user(dataptr, kdata, tocopy + * sizeof(struct __user_cap_data_struct))) { + return -EFAULT; + } + } return ret; } @@ -167,6 +240,8 @@ static inline int cap_set_all(kernel_cap_t *effective, */ asmlinkage long sys_capset(cap_user_header_t header, const cap_user_data_t data) { + struct __user_cap_data_struct kdata[_LINUX_CAPABILITY_U32S]; + unsigned i, tocopy; kernel_cap_t inheritable, permitted, effective; __u32 version; struct task_struct *target; @@ -176,7 +251,15 @@ asmlinkage long sys_capset(cap_user_header_t header, const cap_user_data_t data) if (get_user(version, &header->version)) return -EFAULT; - if (version != _LINUX_CAPABILITY_VERSION) { + switch (version) { + case _LINUX_CAPABILITY_VERSION_1: + warn_legacy_capability_use(); + tocopy = _LINUX_CAPABILITY_U32S_1; + break; + case _LINUX_CAPABILITY_VERSION_2: + tocopy = _LINUX_CAPABILITY_U32S_2; + break; + default: if (put_user(_LINUX_CAPABILITY_VERSION, &header->version)) return -EFAULT; return -EINVAL; @@ -188,10 +271,22 @@ asmlinkage long sys_capset(cap_user_header_t header, const cap_user_data_t data) if (pid && pid != task_pid_vnr(current) && !capable(CAP_SETPCAP)) return -EPERM; - if (copy_from_user(&effective, &data->effective, sizeof(effective)) || - copy_from_user(&inheritable, &data->inheritable, sizeof(inheritable)) || - copy_from_user(&permitted, &data->permitted, sizeof(permitted))) + if (copy_from_user(&kdata, data, tocopy + * sizeof(struct __user_cap_data_struct))) { return -EFAULT; + } + + for (i = 0; i < tocopy; i++) { + effective.cap[i] = kdata[i].effective; + permitted.cap[i] = kdata[i].permitted; + inheritable.cap[i] = kdata[i].inheritable; + } + while (i < _LINUX_CAPABILITY_U32S) { + effective.cap[i] = 0; + permitted.cap[i] = 0; + inheritable.cap[i] = 0; + i++; + } spin_lock(&task_capability_lock); read_lock(&tasklist_lock); |