kmod: prevent kmod_loop_msg overflow in __request_module()
[linux-2.6.git] / kernel / kmod.c
index b750675..a4bea97 100644 (file)
@@ -24,8 +24,8 @@
 #include <linux/unistd.h>
 #include <linux/kmod.h>
 #include <linux/slab.h>
-#include <linux/mnt_namespace.h>
 #include <linux/completion.h>
+#include <linux/cred.h>
 #include <linux/file.h>
 #include <linux/fdtable.h>
 #include <linux/workqueue.h>
 #include <linux/suspend.h>
 #include <asm/uaccess.h>
 
+#include <trace/events/module.h>
+
 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
 
 /*
@@ -85,6 +94,10 @@ int __request_module(bool wait, const char *fmt, ...)
        if (ret >= MODULE_NAME_LEN)
                return -ENAMETOOLONG;
 
+       ret = security_kernel_module_request(module_name);
+       if (ret)
+               return ret;
+
        /* If modprobe needs a service that is in a module, we get a recursive
         * loop.  Limit the number of running kmod threads to max_threads/2 or
         * MAX_KMOD_CONCURRENT, whichever is the smaller.  A cleaner method
@@ -101,73 +114,41 @@ int __request_module(bool wait, const char *fmt, ...)
        atomic_inc(&kmod_concurrent);
        if (atomic_read(&kmod_concurrent) > max_modprobes) {
                /* We may be blaming an innocent here, but unlikely */
-               if (kmod_loop_msg++ < 5)
+               if (kmod_loop_msg < 5) {
                        printk(KERN_ERR
                               "request_module: runaway loop modprobe %s\n",
                               module_name);
+                       kmod_loop_msg++;
+               }
                atomic_dec(&kmod_concurrent);
                return -ENOMEM;
        }
 
-       ret = call_usermodehelper(modprobe_path, argv, envp,
-                       wait ? UMH_WAIT_PROC : UMH_WAIT_EXEC);
+       trace_module_request(module_name, wait, _RET_IP_);
+
+       ret = call_usermodehelper_fns(modprobe_path, argv, envp,
+                       wait ? UMH_WAIT_PROC : UMH_WAIT_EXEC,
+                       NULL, NULL, NULL);
+
        atomic_dec(&kmod_concurrent);
        return ret;
 }
 EXPORT_SYMBOL(__request_module);
 #endif /* CONFIG_MODULES */
 
-struct subprocess_info {
-       struct work_struct work;
-       struct completion *complete;
-       struct cred *cred;
-       char *path;
-       char **argv;
-       char **envp;
-       enum umh_wait wait;
-       int retval;
-       struct file *stdin;
-       void (*cleanup)(char **argv, char **envp);
-};
-
 /*
  * This is the task which runs the usermode application
  */
 static int ____call_usermodehelper(void *data)
 {
        struct subprocess_info *sub_info = data;
+       struct cred *new;
        int retval;
 
-       BUG_ON(atomic_read(&sub_info->cred->usage) != 1);
-
-       /* Unblock all signals */
        spin_lock_irq(&current->sighand->siglock);
        flush_signal_handlers(current, 1);
-       sigemptyset(&current->blocked);
-       recalc_sigpending();
        spin_unlock_irq(&current->sighand->siglock);
 
-       /* Install the credentials */
-       commit_creds(sub_info->cred);
-       sub_info->cred = NULL;
-
-       /* Install input pipe when needed */
-       if (sub_info->stdin) {
-               struct files_struct *f = current->files;
-               struct fdtable *fdt;
-               /* no races because files should be private here */
-               sys_close(0);
-               fd_install(0, sub_info->stdin);
-               spin_lock(&f->file_lock);
-               fdt = files_fdtable(f);
-               FD_SET(0, fdt->open_fds);
-               FD_CLR(0, fdt->close_on_exec);
-               spin_unlock(&f->file_lock);
-
-               /* and disallow core files too */
-               current->signal->rlim[RLIMIT_CORE] = (struct rlimit){0, 0};
-       }
-
        /* We can run anywhere, unlike our parent keventd(). */
        set_cpus_allowed_ptr(current, cpu_all_mask);
 
@@ -177,9 +158,33 @@ static int ____call_usermodehelper(void *data)
         */
        set_user_nice(current, 0);
 
-       retval = kernel_execve(sub_info->path, sub_info->argv, sub_info->envp);
+       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);
+
+       if (sub_info->init) {
+               retval = sub_info->init(sub_info, new);
+               if (retval) {
+                       abort_creds(new);
+                       goto fail;
+               }
+       }
+
+       commit_creds(new);
+
+       retval = kernel_execve(sub_info->path,
+                              (const char *const *)sub_info->argv,
+                              (const char *const *)sub_info->envp);
 
        /* Exec failed? */
+fail:
        sub_info->retval = retval;
        do_exit(0);
 }
@@ -187,9 +192,7 @@ static int ____call_usermodehelper(void *data)
 void call_usermodehelper_freeinfo(struct subprocess_info *info)
 {
        if (info->cleanup)
-               (*info->cleanup)(info->argv, info->envp);
-       if (info->cred)
-               put_cred(info->cred);
+               (*info->cleanup)(info);
        kfree(info);
 }
 EXPORT_SYMBOL(call_usermodehelper_freeinfo);
@@ -200,16 +203,16 @@ static int wait_for_helper(void *data)
        struct subprocess_info *sub_info = data;
        pid_t pid;
 
-       /* Install a handler: if SIGCLD isn't handled sys_wait4 won't
-        * populate the status, but will return -ECHILD. */
-       allow_signal(SIGCHLD);
+       /* If SIGCLD is ignored sys_wait4 won't populate the status. */
+       spin_lock_irq(&current->sighand->siglock);
+       current->sighand->action[SIGCHLD-1].sa.sa_handler = SIG_DFL;
+       spin_unlock_irq(&current->sighand->siglock);
 
        pid = kernel_thread(____call_usermodehelper, sub_info, SIGCHLD);
        if (pid < 0) {
                sub_info->retval = pid;
        } else {
-               int ret;
-
+               int ret = -ECHILD;
                /*
                 * Normally it is bogus to call wait4() from in-kernel because
                 * wait4() wants to write the exit code to a userspace address.
@@ -230,10 +233,7 @@ static int wait_for_helper(void *data)
                        sub_info->retval = ret;
        }
 
-       if (sub_info->wait == UMH_NO_WAIT)
-               call_usermodehelper_freeinfo(sub_info);
-       else
-               complete(sub_info->complete);
+       complete(sub_info->complete);
        return 0;
 }
 
@@ -242,15 +242,13 @@ static void __call_usermodehelper(struct work_struct *work)
 {
        struct subprocess_info *sub_info =
                container_of(work, struct subprocess_info, work);
-       pid_t pid;
        enum umh_wait wait = sub_info->wait;
-
-       BUG_ON(atomic_read(&sub_info->cred->usage) != 1);
+       pid_t pid;
 
        /* CLONE_VFORK: wait until the usermode helper has execve'd
         * successfully We need the data structures to stay around
         * until that is done.  */
-       if (wait == UMH_WAIT_PROC || wait == UMH_NO_WAIT)
+       if (wait == UMH_WAIT_PROC)
                pid = kernel_thread(wait_for_helper, sub_info,
                                    CLONE_FS | CLONE_FILES | SIGCHLD);
        else
@@ -259,26 +257,26 @@ static void __call_usermodehelper(struct work_struct *work)
 
        switch (wait) {
        case UMH_NO_WAIT:
+               call_usermodehelper_freeinfo(sub_info);
                break;
 
        case UMH_WAIT_PROC:
                if (pid > 0)
                        break;
-               sub_info->retval = pid;
                /* FALLTHROUGH */
-
        case UMH_WAIT_EXEC:
+               if (pid < 0)
+                       sub_info->retval = pid;
                complete(sub_info->complete);
        }
 }
 
-#ifdef CONFIG_PM_SLEEP
 /*
  * If set, call_usermodehelper_exec() will exit immediately returning -EBUSY
  * (used for preventing user land processes from being created after the user
  * land has been frozen during a system-wide hibernation or suspend operation).
  */
-static int usermodehelper_disabled;
+static int usermodehelper_disabled = 1;
 
 /* Number of helpers running */
 static atomic_t running_helpers = ATOMIC_INIT(0);
@@ -328,6 +326,15 @@ void usermodehelper_enable(void)
        usermodehelper_disabled = 0;
 }
 
+/**
+ * usermodehelper_is_disabled - check if new helpers are allowed to be started
+ */
+bool usermodehelper_is_disabled(void)
+{
+       return usermodehelper_disabled;
+}
+EXPORT_SYMBOL_GPL(usermodehelper_is_disabled);
+
 static void helper_lock(void)
 {
        atomic_inc(&running_helpers);
@@ -339,12 +346,6 @@ static void helper_unlock(void)
        if (atomic_dec_and_test(&running_helpers))
                wake_up(&running_helpers_waitq);
 }
-#else /* CONFIG_PM_SLEEP */
-#define usermodehelper_disabled        0
-
-static inline void helper_lock(void) {}
-static inline void helper_unlock(void) {}
-#endif /* CONFIG_PM_SLEEP */
 
 /**
  * call_usermodehelper_setup - prepare to call a usermode helper
@@ -369,78 +370,37 @@ struct subprocess_info *call_usermodehelper_setup(char *path, char **argv,
        sub_info->path = path;
        sub_info->argv = argv;
        sub_info->envp = envp;
-       sub_info->cred = prepare_usermodehelper_creds();
-       if (!sub_info->cred)
-               return NULL;
-
   out:
        return sub_info;
 }
 EXPORT_SYMBOL(call_usermodehelper_setup);
 
 /**
- * call_usermodehelper_setkeys - set the session keys for usermode helper
- * @info: a subprocess_info returned by call_usermodehelper_setup
- * @session_keyring: the session keyring for the process
- */
-void call_usermodehelper_setkeys(struct subprocess_info *info,
-                                struct key *session_keyring)
-{
-#ifdef CONFIG_KEYS
-       struct thread_group_cred *tgcred = info->cred->tgcred;
-       key_put(tgcred->session_keyring);
-       tgcred->session_keyring = key_get(session_keyring);
-#else
-       BUG();
-#endif
-}
-EXPORT_SYMBOL(call_usermodehelper_setkeys);
-
-/**
- * call_usermodehelper_setcleanup - set a cleanup function
+ * call_usermodehelper_setfns - set a cleanup/init function
  * @info: a subprocess_info returned by call_usermodehelper_setup
  * @cleanup: a cleanup function
+ * @init: an init function
+ * @data: arbitrary context sensitive data
+ *
+ * The init function is used to customize the helper process prior to
+ * exec.  A non-zero return code causes the process to error out, exit,
+ * and return the failure to the calling process
  *
- * The cleanup function is just befor ethe subprocess_info is about to
+ * The cleanup function is just before ethe subprocess_info is about to
  * be freed.  This can be used for freeing the argv and envp.  The
  * Function must be runnable in either a process context or the
  * context in which call_usermodehelper_exec is called.
  */
-void call_usermodehelper_setcleanup(struct subprocess_info *info,
-                                   void (*cleanup)(char **argv, char **envp))
+void call_usermodehelper_setfns(struct subprocess_info *info,
+                   int (*init)(struct subprocess_info *info, struct cred *new),
+                   void (*cleanup)(struct subprocess_info *info),
+                   void *data)
 {
        info->cleanup = cleanup;
+       info->init = init;
+       info->data = data;
 }
-EXPORT_SYMBOL(call_usermodehelper_setcleanup);
-
-/**
- * call_usermodehelper_stdinpipe - set up a pipe to be used for stdin
- * @sub_info: a subprocess_info returned by call_usermodehelper_setup
- * @filp: set to the write-end of a pipe
- *
- * This constructs a pipe, and sets the read end to be the stdin of the
- * subprocess, and returns the write-end in *@filp.
- */
-int call_usermodehelper_stdinpipe(struct subprocess_info *sub_info,
-                                 struct file **filp)
-{
-       struct file *f;
-
-       f = create_write_pipe(0);
-       if (IS_ERR(f))
-               return PTR_ERR(f);
-       *filp = f;
-
-       f = create_read_pipe(f, 0);
-       if (IS_ERR(f)) {
-               free_write_pipe(*filp);
-               return PTR_ERR(f);
-       }
-       sub_info->stdin = f;
-
-       return 0;
-}
-EXPORT_SYMBOL(call_usermodehelper_stdinpipe);
+EXPORT_SYMBOL(call_usermodehelper_setfns);
 
 /**
  * call_usermodehelper_exec - start a usermode application
@@ -460,8 +420,6 @@ int call_usermodehelper_exec(struct subprocess_info *sub_info,
        DECLARE_COMPLETION_ONSTACK(done);
        int retval = 0;
 
-       BUG_ON(atomic_read(&sub_info->cred->usage) != 1);
-
        helper_lock();
        if (sub_info->path[0] == '\0')
                goto out;
@@ -488,38 +446,83 @@ unlock:
 }
 EXPORT_SYMBOL(call_usermodehelper_exec);
 
-/**
- * call_usermodehelper_pipe - call a usermode helper process with a pipe stdin
- * @path: path to usermode executable
- * @argv: arg vector for process
- * @envp: environment for process
- * @filp: set to the write-end of a pipe
- *
- * This is a simple wrapper which executes a usermode-helper function
- * with a pipe as stdin.  It is implemented entirely in terms of
- * lower-level call_usermodehelper_* functions.
- */
-int call_usermodehelper_pipe(char *path, char **argv, char **envp,
-                            struct file **filp)
+static int proc_cap_handler(struct ctl_table *table, int write,
+                        void __user *buffer, size_t *lenp, loff_t *ppos)
 {
-       struct subprocess_info *sub_info;
-       int ret;
+       struct ctl_table t;
+       unsigned long cap_array[_KERNEL_CAPABILITY_U32S];
+       kernel_cap_t new_cap;
+       int err, i;
 
-       sub_info = call_usermodehelper_setup(path, argv, envp, GFP_KERNEL);
-       if (sub_info == NULL)
-               return -ENOMEM;
+       if (write && (!capable(CAP_SETPCAP) ||
+                     !capable(CAP_SYS_MODULE)))
+               return -EPERM;
 
-       ret = call_usermodehelper_stdinpipe(sub_info, filp);
-       if (ret < 0)
-               goto out;
+       /*
+        * 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);
 
-       return call_usermodehelper_exec(sub_info, UMH_WAIT_EXEC);
+       t = *table;
+       t.data = &cap_array;
 
-  out:
-       call_usermodehelper_freeinfo(sub_info);
-       return ret;
+       /*
+        * 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;
 }
-EXPORT_SYMBOL(call_usermodehelper_pipe);
+
+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)
 {