我一直被告知(在书籍和教程中),在将数据从内核空间复制到用户空间时,我们应该使用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()失败都非常有用。感谢。
答案 0 :(得分:27)
这有几个原因。
首先,安全。因为内核可以写入它想要的任何地址,如果你只使用用户空间地址并使用memcpy
,攻击者就可以写入另一个进程的页面,这是一个巨大的安全问题。 copy_to_user
检查当前进程是否可写入目标页面。
还有一些架构考虑因素。例如,在x86上,目标页面必须固定在内存中。在某些体系结构上,您可能需要特殊说明。等等。 Linux内核非常便携的目标需要这种抽象。
答案 1 :(得分:0)
这个答案可能晚了,但无论如何 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
希望这个答案在某种程度上有所帮助。