使用 Linux 内核 call_usermodehelper 在用户模式下运行用户空间进程

Posted

技术标签:

【中文标题】使用 Linux 内核 call_usermodehelper 在用户模式下运行用户空间进程【英文标题】:Running a userspace process in user mode with Linux kernel call_usermodehelper 【发布时间】:2020-10-15 03:42:21 【问题描述】:

我尝试使用call_usermodehelper 在 Linux 内核模块中执行用户空间二进制文件。我发现启动的应用程序正在以 root 模式运行。是否可以在用户模式下运行应用程序,例如命名为user1

int alter_uid_gid(uid_t uid, gid_t gid, struct cred *new)

//      struct cred *new;
        new = prepare_creds();
        if (!new)
                return -ENOMEM;
        new->uid = new->euid = new->suid = new->fsuid = KUIDT_INIT(uid);
        new->gid = new->egid = new->sgid = new->fsgid = KGIDT_INIT(gid);
        return commit_creds(new);


static int init_func(struct subprocess_info *info, struct cred *new)

        printk("[%d]\n", current->pid);
        alter_uid_gid(1000, 1000, new);
        return 0;


static int user_process_fork(void *data)

    struct subprocess_info *sub_info;
    int ret = 0;
    char *path = (char *)data;
    char *argv[] = path, NULL;
    static char *envp[] = "HOME=/", "TERM=linux",
        "PATH=/sbin:/bin:/usr/sbin:/usr/bin", NULL;

    sub_info = call_usermodehelper_setup(argv[0], argv, envp, GFP_ATOMIC,
            init_func, NULL, NULL);
    if (sub_info == NULL) return -ENOMEM;

    ret = call_usermodehelper_exec(sub_info, UMH_KILLABLE);
    pr_info("%s: ret %d\n", __func__, ret);
    do_exit(0);

    return ret;


根据 Milag 的评论,我尝试使用 prepare_creds()commit_creds(new) 更新 init_func() 中的 u[g/e/s]id。在内核日志中,我可以看到current->uid 已更改为1000。但是当我运行ps aux 时,进程仍处于root 模式。知道为什么吗?

【问题讨论】:

您的内核版本是否将 (struct cred *) 传递给您的 init_func() @Milag,我不确定我是否明白你所说的。这是初始化函数int (*init)(struct subprocess_info *info, struct cred *new),作为call_usermodehelper_setup函数中的参数的声明。 看看相关的内核源码;似乎在提供的 init 例程中修改 (struct cred *new) 成员,如您的 init_func() 可能 是可以的,以及在设置 CONFIG_SECURITY_SELINUX 并满足其他条件时可能出现的恐慌情况。 你好@Milag,在init_func()我更新了cred'uid/euid/gid 为1000,但启动的用户空间进程仍处于root模式。我正在使用new = prepare_creds(); new->uid = KUIDT_INIT(uid); commit_creds(new);。你知道为什么吗? 上周的想法是修改传递给您的 init 例程的 (struct cred *) 的成员。不建议分配另一个结构;考虑这个顺序:内核分配+初始化一个struct cred,使用对该结构的引用调用您的初始化例程,然后在您的初始化例程返回之后提交那个结构。在 init 例程中不需要另一个 prep+commit ;这些凭据将被内核的提交替换。 【参考方案1】:

在我阅读了一些内核源代码并在上面发布了 cmets 之后,OP 稍后显示了已开发 kmod 的更新。

简短回答:是的,可以为从 kmod 启动的用户进程设置不同的 ID。

在将一个init 例程传递给call_usermodehelper_setup() 后,相关的内核服务使用(struct cred *) 调用该init 例程;可以在那里更改各种uid和gid成员。更多详情请见call_usermodehelper_exec_async()

关于kmod通用性有相关建议:

为一组默认的uid和gid添加一对#define

添加对模块参数的支持以设置其他uid和gid

在加载模块时可选择提供命令行参数

例如,请参阅this link。

【讨论】:

【参考方案2】:

根据@Milag 的评论,以下代码使新的用户空间进程以用户模式运行(使用ps aux 进行检查):

int alter_uid_gid(uid_t uid, gid_t gid, struct cred *new)

        new->uid = new->euid = new->suid = new->fsuid = KUIDT_INIT(uid);
        new->gid = new->egid = new->sgid = new->fsgid = KGIDT_INIT(gid);
        return 0;


static int init_func(struct subprocess_info *info, struct cred *new)

        printk("[%d]\n", current->pid);
        alter_uid_gid(1000, 1000, new);
        return 0;


static int user_process_fork(void *data)

    struct subprocess_info *sub_info;
    int ret = 0;
    char *path = (char *)data;
    char *argv[] = path, NULL;
    static char *envp[] = "HOME=/", "TERM=linux",
        "PATH=/sbin:/bin:/usr/sbin:/usr/bin", NULL;

    sub_info = call_usermodehelper_setup(argv[0], argv, envp, GFP_ATOMIC,
            init_func, NULL, NULL);
    if (sub_info == NULL) return -ENOMEM;

    ret = call_usermodehelper_exec(sub_info, UMH_KILLABLE);
    pr_info("%s: ret %d\n", __func__, ret);
    do_exit(0);

    return ret;

【讨论】:

以上是关于使用 Linux 内核 call_usermodehelper 在用户模式下运行用户空间进程的主要内容,如果未能解决你的问题,请参考以下文章

当前linux所使用的内核在哪个文件夹,如何看当前使用的内核版本情况。

linux为啥要编译内核

Linux内核通用队列的使用笔记(读linux内核设计与实现)

如何重新编译linux内核

Linux内核模块编程可以使用的内核组件

linux用户空间和内核空间(内核高端内存)_转