fork和内核中映射的用户空间内存的交互

时间:2010-10-28 18:48:43

标签: linux-kernel fork shared-memory virtual-memory

考虑使用 get_user_pages (或get_page)来映射来自调用进程的页面的Linux驱动程序。然后将页面的物理地址传递给硬件设备。进程和设备都可以读取和写入页面,直到各方决定结束通信。特别是,在调用get_user_pages的系统调用返回后,通信可以继续使用页面。系统调用实际上是在进程和硬件设备之间设置共享内存区域

我担心如果进程调用 fork 会发生什么(它可能来自另一个线程,并且可能在调用get_user_pages的系统调用时发生进展或以后)。特别是,如果父进程在fork之后写入共享内存区域,我对底层物理地址有什么了解(可能是由于写时复制而改变)?我想明白:

  1. 内核需要做些什么来防范可能行为不端的过程(我不想创建安全漏洞!);
  2. 进程需要遵守哪些限制,以便我们的驱动程序的功能正常工作(即物理内存仍然映射到父进程中的同一地址)。

    • 理想情况下,我希望子进程根本不使用我们的驱动程序(可能几乎立即调用exec)来工作。
    • 理想情况下,父进程在分配内存时不必采取任何特殊步骤,因为我们现有的代码将堆栈分配的缓冲区传递给驱动程序。
    • 我知道madviseMADV_DONTFORK,并且可以让内存从子进程的空间中消失,但它不适用于堆栈分配的缓冲区。
    • “在我们的驱动程序处于活动状态时,不要使用fork”会很烦人,但如果满足第1点,则可以接受作为最后的手段。
  3. 我愿意指出文档或源代码。我特别关注Linux Device Drivers,但没有发现这个问题。 RTFS应用于内核源代码的相关部分有点压倒性。

    内核版本并未完全修复,但是最新版本(假设≥2.6.26)。如果重要的话,我们只针对Arm平台(到目前为止单处理器,但多核就在附近)。

2 个答案:

答案 0 :(得分:4)

fork()不会干扰get_user_pages()get_user_pages()会为您提供struct page

在能够访问它之前,您需要kmap(),并且此映射在内核空间中完成,而不是在用户空间中完成。

编辑:get_user_pages()触摸页面表,但您不应该担心这一点(它只是确保页面在用户空间中映射),如果有任何问题,则返回-EFAULT。< / p>

如果你fork(),直到执行copy-on-write,孩子将能够看到该页面。 一旦完成写入复制(因为子/驱动程序/父级通过用户空间映射写入页面 - 而不是驱动程序具有的内核kmap()),将不再共享该页面。如果您仍然在页面上(在驱动程序代码中)持有kmap(),您将无法知道您是持有父页面还是孩子的。

1)这不是一个安全漏洞,因为一旦你execve(),所有这一切都消失了。

2)当你fork()时,你希望两个进程都是相同的(它是一个分叉!!)。我认为你的设计应该允许父母和孩子访问驱动程序。 Execve()将清除所有内容。

如何在用户空间中添加一些功能,如:

 f = open("/dev/your_thing")
 mapping = mmap(f, ...)

在设备上调用mmap()时,使用特殊标志安装内存映射: http://os1a.cs.columbia.edu/lxr/source/include/linux/mm.h#071

你有一些有趣的事情:

#define VM_SHARED       0x00000008
#define VM_LOCKED       0x00002000
#define VM_DONTCOPY     0x00020000      /* Do not copy this vma on fork */

VM_SHARED将禁止写入时复制 VM_LOCKED将禁用该页面上的交换 VM_DONTCOPY会告诉内核不要在fork上复制vma区域,虽然我认为这不是一个好主意

答案 1 :(得分:3)

简短的回答是在您为驱动程序提供的任何用户空间缓冲区上使用madvise(addr, len, MADV_DONTFORK)。这告诉内核不应该将映射从父级复制到子级,因此没有CoW。

缺点是子节点在该地址没有继承映射,因此如果您希望子节点开始使用该驱动程序,则需要重新映射该内存。但在用户空间中这很容易做到。

更新:堆栈上的缓冲区存在问题,我不确定您是否可以确保安全。

您无法将其标记为DONTFORK,因为您的孩子可能正在该堆栈页面上运行,或者(更糟糕的是)它可能会在稍后返回并返回未映射的堆栈页面。 (我甚至测试了这个,你可以高兴地标记你的堆栈DONTFORK,当你分叉时会发生坏事)。

避免CoW的另一种方法是创建共享映射,但由于显而易见的原因,您无法映射共享堆栈。

这意味着如果你分叉就冒险出现CoW。即使孩子“只是”执行它仍然可能触摸堆栈页面并导致CoW,导致父母获得不同的页面,这是不好的。

对您有利的一点是,使用堆栈缓冲区的代码只需要担心它调用分叉的代码,即。函数返回后,您无法使用堆栈缓冲区。因此,您只需要审核您的被调查者,如果他们从不分叉您是安全的,但这仍然可能是不可行的,并且如果代码发生变化则很脆弱。

我认为你真的希望所有给你的驱动程序的内存来自用户空间中的自定义分配器。它不应该是那种侵扰性的。分配器可以直接mmap您的设备,如另一个答案所示,或者只使用匿名mmapmadvise(DONTFORK),可能mlock()以避免换出。