【问题标题】:copy_to_user vs memcpycopy_to_user 与 memcpy
【发布时间】:2013-02-04 21:56:08
【问题描述】:

我一直被告知(在书籍和教程中)在将数据从内核空间复制到用户空间时,我们应该使用 copy_to_user() 并且使用 memcpy() 会导致系统出现问题。最近我错误地使用了 memcpy() 并且它工作得非常好,没有任何问题。 为什么我们应该使用 copy_to_user 而不是 memcpy()

我的测试代码(内核模块)是这样的:

static ssize_t test_read(struct file *file, char __user * buf,
             size_t len, loff_t * offset)
{
    char ani[100];

    if (!*offset) {
        memset(ani, 'A', 100);
        if (memcpy(buf, ani, 100))
            return -EFAULT;
        *offset = 100;
        return *offset;
    }

    return 0;
}

struct file_operations test_fops = {
    .owner = THIS_MODULE,
    .read = test_read,
};

static int __init my_module_init(void)
{
    struct proc_dir_entry *entry;

    printk("We are testing now!!\n");
    entry = create_proc_entry("test", S_IFREG | S_IRUGO, NULL);
    if (!entry)
        printk("Failed to creats proc entry test\n");

    entry->proc_fops = &test_fops;
    return 0;
}
module_init(my_module_init);

从用户空间应用程序,我正在阅读我的 /proc 条目,一切正常。

查看 copy_to_user() 的源代码表明它也是简单的 memcpy(),我们只是尝试使用 access_ok 并执行 memcpy 检查指针是否有效。

所以我的理解目前是,如果我们确定我们传递的指针,memcpy() 总是可以用来代替 copy_to_user

如果我的理解不正确,请纠正我,copy_to_user 有效且 memcpy() 失败的任何示例都非常有用。谢谢。

【问题讨论】:

  • 因为分页。
  • @Linuxios 抱歉,请您再解释一下。我无法证明内核能够完美地复制,我也无法在 copy_to_user 的源代码中看到与分页相关的任何内容。你能详细说明一下吗?
  • @Sandy:假设性问题:您使用的是具有 16 GB RAM 的 32 位系统。 memcpy 会起作用吗?
  • 不要使用 memcpy 作为 copy_to_user!这是马车。

标签: c linux-kernel linux-device-driver memcpy


【解决方案1】:

这个答案可能会迟到,但无论如何copy_to_user() 和它的姐妹copy_from_user() 都对用户传递的size 参数和缓冲区大小进行了一些大小限制检查,因此读取方法如下:

char name[] = "This message is from kernel space";
ssize_t read(struct file *f, char __user *to, size_t size, loff_t *loff){
                
    int ret = copy_to_user(to, name, size);
    if(ret){
        pr_info("[+] Error while copying data to user space");
        return ret;
    }
    pr_info("[+] Finished copying data to user space");
    return 0;
}

读取为read(ret, buffer, 10); 的用户空间应用OK,但将 10 替换为 35 或更多,内核将发出此错误:

Buffer overflow detected (34 < 35)!

并导致复制失败以防止内存泄漏。 copy_from_user() 也是如此,它也会进行一些内核缓冲区大小检查。


这就是为什么你必须使用char name[] 而不是char *name,因为使用指针(不是数组)会导致无法确定大小,这会使内核发出此错误:

BUG: unable to handle page fault for address: ffffffffc106f280
#PF: supervisor write access in kernel mode
#PF: error_code(0x0003) - permissions violation

希望这个答案能有所帮助。

【讨论】:

    【解决方案2】:

    这有几个原因。

    首先,安全。因为内核可以写入它想要的任何地址,如果你只使用你获得的用户空间地址并使用memcpy,攻击者可以写入另一个进程的页面,这是一个巨大的安全问题。 copy_to_user 检查目标页面是否可被当前进程写入。

    还有一些架构方面的考虑。例如,在 x86 上,目标页面必须固定在内存中。在某些架构上,您可能需要特殊说明。等等。 Linux 内核的可移植性目标需要这种抽象。

    【讨论】:

    • +1 出于安全考虑。不仅仅是另一个过程。对于 3G/1G 内存拆分,user 进程可以尝试覆盖内核内存。如果您的数据是code 来修改内核,这可能特别有用。许多CPUs 具有用户和主管模式。即使在 MMU-less 中,memcpy() 也很糟糕。
    • @BillPringlemeir:没错。感谢您的 +1。
    猜你喜欢
    • 2015-12-05
    • 2017-03-17
    • 2011-05-23
    • 2011-02-23
    • 1970-01-01
    • 2011-10-19
    • 1970-01-01
    • 1970-01-01
    • 2015-01-13
    相关资源
    最近更新 更多