如何在内核 space 中分配用户 space 内存?

how to alloc user space memory in kernel space?

我在 Linux 上挂接了一个系统调用(打开),并想打印这个打开的文件名。 然后我调用 syscall(getcwd) 来获取绝对路径。

这是源代码:

void *memndup_from_user(const void __user *src, long len)
{
    void *kbuf = NULL;
    if(src == NULL) {
        return kbuf;
    }
    kbuf = kmalloc(len + 1, GFP_KERNEL);
    if(kbuf != NULL) {
        if (copy_from_user(kbuf, src, len)) {
            printk(KERN_ALERT "%s\n", "copy_from_user failed.");
            kfree(kbuf);
            kbuf = NULL;
        }
        else {
            ((char *)kbuf)[len] = '[=12=]';
        }
    } else {
        printk(KERN_ALERT "%s\n", "kmalloc failed.");
    }
    return kbuf;
}
void *memdup_from_user(const void __user *src)
{
    long len = 0;
    if(src == NULL) {
        return NULL;
    }
    len = strlen_user(src);
    return memndup_from_user(src, len);
}

asmlinkage long fake_getcwd(char __user *buf, unsigned long size)
{
    return real_getcwd(buf, size);
}

asmlinkage long
fake_open(const char __user *filename, int flags, umode_t mode)
{
    if(flags & O_CREAT) {
        char *k_filename = (char *)memdup_from_user(filename);
        char *u_path = (char *)kmalloc(PAGE_SIZE, GFP_USER);
        if(k_filename != NULL) {
            printk(KERN_ALERT "ano_fake_open pid:%ld create : %s\n", ano_fake_getpid(), k_filename);
            kfree(k_filename);
        }

        if(u_path != NULL) {
            long retv;
            retv = fake_getcwd(u_path, PAGE_SIZE);
            if(retv > 0) {
                printk(KERN_ALERT "getcwd ret val: %ld, path: %s\n", retv, u_path);
            } else {
                printk(KERN_ALERT "getcwd ret val: %ld, error...\n", retv);
            }

            kfree(u_path);
        }
    }
    return real_open(filename, flags, mode);
}

sys_getcwd 需要用户 space 内存,我用 GFP_USER 调用 kmalloc。 但是 sys_getcwd 总是 return -EFAULT(错误地址)...

这是 dmesg 日志:

[344897.726061] fake_open pid:70393 create : sssssssssssssssss
[344897.726065] getcwd ret val: -14, error...
[344897.727431] fake_open pid:695 create : /var/lib/rsyslog/imjournal.state.tmp
[344897.727440] getcwd ret val: -14, error...

所以我在 sys_getcwd 中找到了工具,他找到了

# define __user     __attribute__((noderef, address_space(1)))
# define __kernel   __attribute__((address_space(0)))
#define __getname()     kmem_cache_alloc(names_cachep, GFP_KERNEL)

SYSCALL_DEFINE2(getcwd, char __user *, buf, unsigned long, size)
{
    char *page = __getname();
    get_fs_root_and_pwd_rcu(current->fs, &root, &pwd);
    ...
    // char *cwd = page + xxx; (xxx < PAGE_SIZE)
    // len = PAGE_SIZE + page - cwd;
    ...
    if (len <= size) {
        error = len;
        if (copy_to_user(buf, cwd, len))
            error = -EFAULT;
    }
}

显然,getcwd 分配带有标志 GFP_KERNEL 的内存,然后从 (GFP_KERNEL) !!!

复制到我的缓冲区 ( __user *buf )

__user宏不是GFP_USER吗?

标志 GFP_USER 简介是 https://elixir.bootlin.com/linux/v4.4/source/include/linux/gfp.h#L208:

/*   GFP_USER is for userspace allocations that also need to be directly
 *   accessibly by the kernel or hardware. It is typically used by hardware
 *   for buffers that are mapped to userspace (e.g. graphics) that hardware
 *   still must DMA to. cpuset limits are enforced for these allocations.
 */

怎么了?

至少有两个帐户是错误的:

  1. 系统调用劫持(更不用说 open 之类的东西了)只是个坏主意。捕获所有可能的开放路径的唯一明智方法是使用 LSM 挂钩。它也恰好处理正在打开的实际文件,避免了竞争:你在你的例程中读取路径,包装打开再次读取它。但到那时恶意用户空间可能已经更改了它,你最终看到了错误的文件。
  2. 应该清楚 getcwd 必须有一个解析名称的方法才能将其放入用户空间缓冲区。您应该深入研究调用,看看可以更改哪些内容以将其放入内核缓冲区。

你为什么要开始这样做?