Re: [PATCH] capabilites: allow the application of capability limitsto usermode helpers
From: Andrew G. Morgan
Date: Thu Mar 31 2011 - 20:22:19 EST
Acked-by: Andrew G. Morgan <morgan@xxxxxxxxxx>
Cheers
Andrew
On Wed, Mar 30, 2011 at 10:04 AM, Serge E. Hallyn
<serge.hallyn@xxxxxxxxxx> wrote:
> Quoting Eric Paris (eparis@xxxxxxxxxx):
>> There is no way to limit the capabilities of usermodehelpers. This problem
>> reared its head recently when someone complained that any user with
>> cap_net_admin was able to load arbitrary kernel modules, even though the user
>> didn't have cap_sys_module. The reason is because the actual load is done by
>> a usermode helper and those always have the full cap set. This patch addes new
>> sysctls which allow us to bound the permissions of usermode helpers.
>>
>> /proc/sys/kernel/usermodehelper/bset
>> /proc/sys/kernel/usermodehelper/inheritable
>>
>> You must have CAP_SYS_MODULE and CAP_SETPCAP to change these (changes are
>> &= ONLY). When the kernel launches a usermodehelper it will do so with these
>> as the bset and pI.
>>
>> -v2: make globals static
>> create spinlock to protect globals
>>
>> -v3: require both CAP_SETPCAP and CAP_SYS_MODULE
>> -v4: fix the typo s/CAP_SET_PCAP/CAP_SETPCAP/ because I didn't commit
>> Signed-off-by: Eric Paris <eparis@xxxxxxxxxx>
>> No-objection-from: Serge E. Hallyn <serge.hallyn@xxxxxxxxxxxxx>
>
> Acked-by: Serge E. Hallyn <serge.hallyn@xxxxxxxxxxxxx>
>
> thanks,
> -serge
>
>> Acked-by: David Howells <dhowells@xxxxxxxxxx>
>> ---
>>
>> include/linux/kmod.h | 3 ++
>> kernel/kmod.c | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++
>> kernel/sysctl.c | 6 +++
>> 3 files changed, 109 insertions(+), 0 deletions(-)
>>
>> diff --git a/include/linux/kmod.h b/include/linux/kmod.h
>> index 6efd7a7..79bb98d 100644
>> --- a/include/linux/kmod.h
>> +++ b/include/linux/kmod.h
>> @@ -24,6 +24,7 @@
>> #include <linux/errno.h>
>> #include <linux/compiler.h>
>> #include <linux/workqueue.h>
>> +#include <linux/sysctl.h>
>>
>> #define KMOD_PATH_LEN 256
>>
>> @@ -109,6 +110,8 @@ call_usermodehelper(char *path, char **argv, char **envp, enum umh_wait wait)
>> NULL, NULL, NULL);
>> }
>>
>> +extern struct ctl_table usermodehelper_table[];
>> +
>> extern void usermodehelper_init(void);
>>
>> extern int usermodehelper_disable(void);
>> diff --git a/kernel/kmod.c b/kernel/kmod.c
>> index 9cd0591..06fdea2 100644
>> --- a/kernel/kmod.c
>> +++ b/kernel/kmod.c
>> @@ -25,6 +25,7 @@
>> #include <linux/kmod.h>
>> #include <linux/slab.h>
>> #include <linux/completion.h>
>> +#include <linux/cred.h>
>> #include <linux/file.h>
>> #include <linux/fdtable.h>
>> #include <linux/workqueue.h>
>> @@ -43,6 +44,13 @@ extern int max_threads;
>>
>> static struct workqueue_struct *khelper_wq;
>>
>> +#define CAP_BSET (void *)1
>> +#define CAP_PI (void *)2
>> +
>> +static kernel_cap_t usermodehelper_bset = CAP_FULL_SET;
>> +static kernel_cap_t usermodehelper_inheritable = CAP_FULL_SET;
>> +static DEFINE_SPINLOCK(umh_sysctl_lock);
>> +
>> #ifdef CONFIG_MODULES
>>
>> /*
>> @@ -132,6 +140,7 @@ EXPORT_SYMBOL(__request_module);
>> static int ____call_usermodehelper(void *data)
>> {
>> struct subprocess_info *sub_info = data;
>> + struct cred *new;
>> int retval;
>>
>> spin_lock_irq(¤t->sighand->siglock);
>> @@ -153,6 +162,19 @@ static int ____call_usermodehelper(void *data)
>> goto fail;
>> }
>>
>> + retval = -ENOMEM;
>> + new = prepare_kernel_cred(current);
>> + if (!new)
>> + goto fail;
>> +
>> + spin_lock(&umh_sysctl_lock);
>> + new->cap_bset = cap_intersect(usermodehelper_bset, new->cap_bset);
>> + new->cap_inheritable = cap_intersect(usermodehelper_inheritable,
>> + new->cap_inheritable);
>> + spin_unlock(&umh_sysctl_lock);
>> +
>> + commit_creds(new);
>> +
>> retval = kernel_execve(sub_info->path,
>> (const char *const *)sub_info->argv,
>> (const char *const *)sub_info->envp);
>> @@ -418,6 +440,84 @@ unlock:
>> }
>> EXPORT_SYMBOL(call_usermodehelper_exec);
>>
>> +static int proc_cap_handler(struct ctl_table *table, int write,
>> + void __user *buffer, size_t *lenp, loff_t *ppos)
>> +{
>> + struct ctl_table t;
>> + unsigned long cap_array[_KERNEL_CAPABILITY_U32S];
>> + kernel_cap_t new_cap;
>> + int err, i;
>> +
>> + if (write && (!capable(CAP_SETPCAP) ||
>> + !capable(CAP_SYS_MODULE)))
>> + return -EPERM;
>> +
>> + /*
>> + * convert from the global kernel_cap_t to the ulong array to print to
>> + * userspace if this is a read.
>> + */
>> + spin_lock(&umh_sysctl_lock);
>> + for (i = 0; i < _KERNEL_CAPABILITY_U32S; i++) {
>> + if (table->data == CAP_BSET)
>> + cap_array[i] = usermodehelper_bset.cap[i];
>> + else if (table->data == CAP_PI)
>> + cap_array[i] = usermodehelper_inheritable.cap[i];
>> + else
>> + BUG();
>> + }
>> + spin_unlock(&umh_sysctl_lock);
>> +
>> + t = *table;
>> + t.data = &cap_array;
>> +
>> + /*
>> + * actually read or write and array of ulongs from userspace. Remember
>> + * these are least significant 32 bits first
>> + */
>> + err = proc_doulongvec_minmax(&t, write, buffer, lenp, ppos);
>> + if (err < 0)
>> + return err;
>> +
>> + /*
>> + * convert from the sysctl array of ulongs to the kernel_cap_t
>> + * internal representation
>> + */
>> + for (i = 0; i < _KERNEL_CAPABILITY_U32S; i++)
>> + new_cap.cap[i] = cap_array[i];
>> +
>> + /*
>> + * Drop everything not in the new_cap (but don't add things)
>> + */
>> + spin_lock(&umh_sysctl_lock);
>> + if (write) {
>> + if (table->data == CAP_BSET)
>> + usermodehelper_bset = cap_intersect(usermodehelper_bset, new_cap);
>> + if (table->data == CAP_PI)
>> + usermodehelper_inheritable = cap_intersect(usermodehelper_inheritable, new_cap);
>> + }
>> + spin_unlock(&umh_sysctl_lock);
>> +
>> + return 0;
>> +}
>> +
>> +struct ctl_table usermodehelper_table[] = {
>> + {
>> + .procname = "bset",
>> + .data = CAP_BSET,
>> + .maxlen = _KERNEL_CAPABILITY_U32S * sizeof(unsigned long),
>> + .mode = 0600,
>> + .proc_handler = proc_cap_handler,
>> + },
>> + {
>> + .procname = "inheritable",
>> + .data = CAP_PI,
>> + .maxlen = _KERNEL_CAPABILITY_U32S * sizeof(unsigned long),
>> + .mode = 0600,
>> + .proc_handler = proc_cap_handler,
>> + },
>> + { }
>> +};
>> +
>> void __init usermodehelper_init(void)
>> {
>> khelper_wq = create_singlethread_workqueue("khelper");
>> diff --git a/kernel/sysctl.c b/kernel/sysctl.c
>> index c0bb324..965134b 100644
>> --- a/kernel/sysctl.c
>> +++ b/kernel/sysctl.c
>> @@ -56,6 +56,7 @@
>> #include <linux/kprobes.h>
>> #include <linux/pipe_fs_i.h>
>> #include <linux/oom.h>
>> +#include <linux/kmod.h>
>>
>> #include <asm/uaccess.h>
>> #include <asm/processor.h>
>> @@ -616,6 +617,11 @@ static struct ctl_table kern_table[] = {
>> .child = random_table,
>> },
>> {
>> + .procname = "usermodehelper",
>> + .mode = 0555,
>> + .child = usermodehelper_table,
>> + },
>> + {
>> .procname = "overflowuid",
>> .data = &overflowuid,
>> .maxlen = sizeof(int),
>>
>
> -----BEGIN PGP SIGNATURE-----
> Version: GnuPG v1.4.10 (GNU/Linux)
>
> iQEcBAEBAgAGBQJNk2K6AAoJEHmllQITXQdF8AgH/RDWKdOXjySrBXaCc7LQR29M
> tPxdgl+/w7aKxsWF9iXxoIsMSKQXGhY1+J9QyoWpP9LWJ3njXcofZjg3x7uX94Ex
> JNnCrhjMFHhI6evPz00l0jqXA5lrg/LVqaDmpoTwJs+oKNj+3kHMQtdLpMrCKqjO
> RX6fNIthfYgBEdHPVN1h4nHdNurRRfsxXsrHso1Ljkc4DhaUsEKZaDRbf+mIM6zP
> Z+e6a/IwsYrfJ5tbA37tppActh3tcVcIEYj31g/peeZV1L5MHlkn0jemPIWyQJ8I
> zauWoBsWj5/uuIcGJ7tU6ae7iFLo1kWI1x03TwWSf6k2xeRfx3uwdf04mgrS1pY=
> =40oa
> -----END PGP SIGNATURE-----
>
>
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/