如何从Linux内核访问用户空间内存?
我知道copy_to_user
/copy_from_user
,get_user
/put_user
功能是为了这个目的。如何从Linux内核访问用户空间内存?
我的问题是,给定一个用户空间地址/指针,我怎样才能访问内核地址指向的数据?
我可以想象,首先我必须确保包含页面应该在物理内存中(而不是在磁盘中)。
下一步是什么?我能否使用*p
,其中p
是指向某些用户空间数据的指针,直接引用数据?
或者我必须首先调用kmap
来将包含物理页面框架映射到内核虚拟地址空间吗?为什么?
指针本身还不够!您需要知道指针“属于”哪个进程。
当进程被抢占时,指针指向另一个进程的地址空间。地址可能不再映射,yadda yadda,
如果该进程在您访问数据时是当前进程,那么您应该使用copy_to_user/copy_from_user函数。
如果可以调度进程,可以尝试mlock()RAM中的页面,找出页面的物理RAM地址。每当你想访问它时,你将该物理页面映射到内核虚拟地址。
注:
- 恶意进程可以次munlock()的页面,诱骗您访问一个错误的RAM页。
- 我不确定mlock()语义要求下划线的RAM页面不能改变。
- 内核应该能够将一个页面锁定到RAM中,我不熟悉mm子系统。
不同的用户空间应用程序有不同的页面表。 1)你需要获得用户空间程序pid。 2)在pid的页表中搜索addree。
下面是将用户空间虚拟地址转换为物理地址的示例代码。 它适用于x86平台。
taskpid = find_get_pid(curpid);
task = pid_task(taskpid, PIDTYPE_PID);
mm = get_task_mm(task);
down_read(&mm->mmap_sem);
start_vaddr = vaddr;
end_vaddr = 0xC0000000;
while(start_vaddr < end_vaddr){
u32 end;
end = ((start_vaddr + PMD_SIZE) & PMD_MASK);
if(end < start_vaddr || end > end_vaddr)
end = end_vaddr;
ret = walk_pgd(start_vaddr, end, mm);
if(ret != 0){
printk("ret: %08x \n", ret);
break;
}
start_vaddr = end;
}
up_read(&mm->mmap_sem);
paddr = ret;
kaddr = __va(paddr);
mmput(mm);
您可能会觉得这很有用。
让我们重复的buff参数的读取和写入方法是 用户空间指针。因此,它不能直接由 内核代码解除引用。有此限制的几个原因:
根据其架构您的驱动器上运行,并 内核是如何配置的,而在内核模式下运行 用户空间指针可能无效所有。该地址可能没有映射,也可能指向其他一些随机数据。
即使指针确实意味着在内核空间同样的事情, 用户空间内存分页,系统调用时有问题的内存可能不 驻留在RAM中。尝试引用 用户空间内存直接可能会产生页面错误,这是内核代码不允许执行的内容错误,即 。结果将是 “oops”,这将导致系统调用 的进程死亡。
有问题的指针由用户程序提供,其中 可能有问题或恶意。如果你的驱动永远盲目取消引用 用户提供的指针,它提供了一个开放的门道允许 用户空间程序来访问或 系统的任何地方覆盖内存。如果您不希望对用户系统的安全性产生影响,则不能直接取消引用 用户空间指针。
来源:http://www.makelinux.net/ldd3/chp-3-sect-7
这么说,我是我自己很想知道,如果用户空间地址确实是有效的,并没有上述报考条件会发生什么......
好点,代码逻辑很好。但我想有一些哈希表或类似的数据结构,给定一个虚拟地址,可以帮助您快速找到物理页面。有一个缺陷:kaddr = __va(paddr);这条线只有在paddr驻留在低内存时才有效,对吧? – Infinite
paddr表示物理地址,所以始终存在于内存中。 kaddr表示内核地址。在Linux内核中,define是'#define __va(x)((void *)((unsigned long)(x)+ PAGE_OFFSET))'。内核地址内存映射并不复杂,只是一个PAGE_OFFSET。 (在x86模式下应该是0xC0000000)。 还有其他方法可以获取地址。用户空间应用程序可以通过/ proc//pagemap访问内核地址来获取页面信息。如果能得到PFN,它也可以得到内核地址。 –
richliu