User Tools

Site Tools


lk_usermodehelper

Overview

Fundamentals of Linux kernel usermodehelper functionality.

Links (coming soon):

Files

include/linux/umh.h

Types of execs

#define UMH_NO_WAIT     0       /* don't wait at all */
#define UMH_WAIT_EXEC   1       /* wait for the exec, but not the process */
#define UMH_WAIT_PROC   2       /* wait for the process to complete */
#define UMH_KILLABLE    4       /* wait for EXEC/PROC killable */

struct subprocess_info

struct subprocess_info {
        struct work_struct work;
        struct completion *complete;
        const char *path;
        char **argv;
        char **envp;
        struct file *file;
        int wait;
        int retval;
        pid_t pid;
        int (*init)(struct subprocess_info *info, struct cred *new);
        void (*cleanup)(struct subprocess_info *info);
        void *data;
} __randomize_layout;

Standard calls

Common combination:

extern struct subprocess_info *
call_usermodehelper_setup(const char *path, char **argv, char **envp,
                          gfp_t gfp_mask,
                          int (*init)(struct subprocess_info *info, struct cred *new),
                          void (*cleanup)(struct subprocess_info *), void *data);
                          
extern int
call_usermodehelper_exec(struct subprocess_info *info, int wait);

or combination of the above:

extern int
call_usermodehelper(const char *path, char **argv, char **envp, int wait);

kernel/umh.c

call_usermodehelper_setup()

struct subprocess_info *call_usermodehelper_setup(const char *path, char **argv,
		char **envp, gfp_t gfp_mask,
		int (*init)(struct subprocess_info *info, struct cred *new),
		void (*cleanup)(struct subprocess_info *info),
		void *data)
{
	struct subprocess_info *sub_info;
	sub_info = kzalloc(sizeof(struct subprocess_info), gfp_mask);
	if (!sub_info)
		goto out;

	INIT_WORK(&sub_info->work, call_usermodehelper_exec_work);

#ifdef CONFIG_STATIC_USERMODEHELPER
	sub_info->path = CONFIG_STATIC_USERMODEHELPER_PATH;
#else
	sub_info->path = path;
#endif
	sub_info->argv = argv;
	sub_info->envp = envp;

	sub_info->cleanup = cleanup;
	sub_info->init = init;
	sub_info->data = data;
  out:
	return sub_info;
}

call_usermodehelper_exec()

int call_usermodehelper_exec(struct subprocess_info *sub_info, int wait)
{
	DECLARE_COMPLETION_ONSTACK(done);
	int retval = 0;

	if (!sub_info->path) {
		call_usermodehelper_freeinfo(sub_info);
		return -EINVAL;
	}
	helper_lock();
	if (usermodehelper_disabled) {
		retval = -EBUSY;
		goto out;
	}

	/*
	 * If there is no binary for us to call, then just return and get out of
	 * here.  This allows us to set STATIC_USERMODEHELPER_PATH to "" and
	 * disable all call_usermodehelper() calls.
	 */
	if (strlen(sub_info->path) == 0)
		goto out;

	/*
	 * Set the completion pointer only if there is a waiter.
	 * This makes it possible to use umh_complete to free
	 * the data structure in case of UMH_NO_WAIT.
	 */
	sub_info->complete = (wait == UMH_NO_WAIT) ? NULL : &done;
	sub_info->wait = wait;

	queue_work(system_unbound_wq, &sub_info->work);
	if (wait == UMH_NO_WAIT)	/* task has freed sub_info */
		goto unlock;

	if (wait & UMH_KILLABLE) {
		retval = wait_for_completion_killable(&done);
		if (!retval)
			goto wait_done;

		/* umh_complete() will see NULL and free sub_info */
		if (xchg(&sub_info->complete, NULL))
			goto unlock;
		/* fallthrough, umh_complete() was already called */
	}

	wait_for_completion(&done);
wait_done:
	retval = sub_info->retval;
out:
	call_usermodehelper_freeinfo(sub_info);
unlock:
	helper_unlock();
	return retval;
}

call_usermodehelper()

Combination of the above two:

int call_usermodehelper(const char *path, char **argv, char **envp, int wait)
{
	struct subprocess_info *info;
	gfp_t gfp_mask = (wait == UMH_NO_WAIT) ? GFP_ATOMIC : GFP_KERNEL;

	info = call_usermodehelper_setup(path, argv, envp, gfp_mask,
					 NULL, NULL, NULL);
	if (info == NULL)
		return -ENOMEM;

	return call_usermodehelper_exec(info, wait);
}

STATIC_USERMODEHELPER

security/Kconfig

config STATIC_USERMODEHELPER
        bool "Force all usermode helper calls through a single binary"
        help
          By default, the kernel can call many different userspace
          binary programs through the "usermode helper" kernel
          interface.  Some of these binaries are statically defined
          either in the kernel code itself, or as a kernel configuration
          option.  However, some of these are dynamically created at
          runtime, or can be modified after the kernel has started up.
          To provide an additional layer of security, route all of these
          calls through a single executable that can not have its name
          changed.

          Note, it is up to this single binary to then call the relevant
          "real" usermode helper binary, based on the first argument
          passed to it.  If desired, this program can filter and pick
          and choose what real programs are called.

          If you wish for all usermode helper programs are to be
          disabled, choose this option and then set
          STATIC_USERMODEHELPER_PATH to an empty string.

config STATIC_USERMODEHELPER_PATH
        string "Path to the static usermode helper binary"
        depends on STATIC_USERMODEHELPER
        default "/sbin/usermode-helper"
        help
          The binary called by the kernel when any usermode helper
          program is wish to be run.  The "real" application's name will
          be in the first argument passed to this program on the command
          line.

          If you wish for all usermode helper programs to be disabled,
          specify an empty string here (i.e. "").

kernel/umh.c

#ifdef CONFIG_STATIC_USERMODEHELPER
        sub_info->path = CONFIG_STATIC_USERMODEHELPER_PATH;
#else
        sub_info->path = path;
#endif

Examples

kernel/kmod.c

static int call_modprobe(char *module_name, int wait)
{
        struct subprocess_info *info;
        static char *envp[] = {
                "HOME=/",
                "TERM=linux",
                "PATH=/sbin:/usr/sbin:/bin:/usr/bin",
                NULL
        };

        char **argv = kmalloc(sizeof(char *[5]), GFP_KERNEL);
        if (!argv)
                goto out;

        module_name = kstrdup(module_name, GFP_KERNEL);
        if (!module_name)
                goto free_argv;

        argv[0] = modprobe_path;
        argv[1] = "-q";
        argv[2] = "--";
        argv[3] = module_name;  /* check free_modprobe_argv() */
        argv[4] = NULL;

        info = call_usermodehelper_setup(modprobe_path, argv, envp, GFP_KERNEL,
                                         NULL, free_modprobe_argv, NULL);
        if (!info)
                goto free_module_name;

        return call_usermodehelper_exec(info, wait | UMH_KILLABLE);

free_module_name:
        kfree(module_name);
free_argv:
        kfree(argv);
out:
        return -ENOMEM;
}

drivers/video/fbdev/uvesafb.c

static char v86d_path[PATH_MAX] = "/sbin/v86d";

...

static int uvesafb_helper_start(void)
{
        char *envp[] = {
                "HOME=/",
                "PATH=/sbin:/bin",
                NULL,
        };

        char *argv[] = {
                v86d_path,
                NULL,
        };

        return call_usermodehelper(v86d_path, argv, envp, UMH_WAIT_PROC);
}

drivers/macintosh/windfarm_core.c

static int wf_critical_overtemp(void)
{
        static char const critical_overtemp_path[] = "/sbin/critical_overtemp";
        char *argv[] = { (char *)critical_overtemp_path, NULL };
        static char *envp[] = { "HOME=/",
                                "TERM=linux",
                                "PATH=/sbin:/usr/sbin:/bin:/usr/bin",
                                NULL };

        return call_usermodehelper(critical_overtemp_path,
                                   argv, envp, UMH_WAIT_EXEC);
}

init/do_mounts_initrd.c

static void __init handle_initrd(void)
{
        struct subprocess_info *info;
        static char *argv[] = { "linuxrc", NULL, };
        extern char *envp_init[];
        int error;

        real_root_dev = new_encode_dev(ROOT_DEV);
        create_dev("/dev/root.old", Root_RAM0);
        /* mount initrd on rootfs' /root */
        mount_block_root("/dev/root.old", root_mountflags & ~MS_RDONLY);
        ksys_mkdir("/old", 0700);
        ksys_chdir("/old");

        /*
         * In case that a resume from disk is carried out by linuxrc or one of
         * its children, we need to tell the freezer not to wait for us.
         */
        current->flags |= PF_FREEZER_SKIP;

        info = call_usermodehelper_setup("/linuxrc", argv, envp_init,
                                         GFP_KERNEL, init_linuxrc, NULL, NULL);
        if (!info)
                return;
        call_usermodehelper_exec(info, UMH_WAIT_PROC);
        
        ...

kernel/reboot.c

char poweroff_cmd[POWEROFF_CMD_PATH_LEN] = "/sbin/poweroff";
static const char reboot_cmd[] = "/sbin/reboot";

static int run_cmd(const char *cmd)
{
        char **argv;
        static char *envp[] = {
                "HOME=/",
                "PATH=/sbin:/bin:/usr/sbin:/usr/bin",
                NULL
        };
        int ret;
        argv = argv_split(GFP_KERNEL, cmd, NULL);
        if (argv) {
                ret = call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
                argv_free(argv);
        } else {
                ret = -ENOMEM;
        }

        return ret;
}
lk_usermodehelper.txt · Last modified: 2019/02/14 15:18 by rpjday