5

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

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

  1. 内核需要做什么来防御潜在的行为不端的进程(我不想造成安全漏洞!);
  2. 进程需要遵守哪些限制才能使我们的驱动程序的功能正常工作(即物理内存保持映射到父进程中的相同地址)。

    • 理想情况下,我希望子进程根本不使用我们的驱动程序(它可能exec几乎立即调用)来工作的常见情况。
    • 理想情况下,父进程在分配内存时不必采取任何特殊步骤,因为我们有现有代码将堆栈分配的缓冲区传递给驱动程序。
    • 我知道madvisewith MADV_DONTFORK,可以让内存从子进程的空间中消失,但它不适用于堆栈分配的缓冲区。
    • “当你与我们的司机有连接时不要使用叉子”会很烦人,但如果满足第 1 点,作为最后的手段是可以接受的。

我愿意被指向文档或源代码。我特别查看了Linux Device Drivers,但没有发现这个问题得到解决。即使只是应用于内核源代码的相关部分的 RTFS 也有点让人不知所措。

内核版本不是完全固定的,而是最近的版本(比方说​≥2.6.26)。我们只针对 Arm 平台(目前是单处理器,但多核指日可待),如果重要的话。

4

2 回答 2

4

Afork()不会干涉get_user_pages()get_user_pages()会给你一个struct page

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

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

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

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

2)当你 fork() 时,你希望两个进程相同(这是一个 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 区域,尽管我认为这不是一个好主意

于 2010-10-28T19:20:33.187 回答
3

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

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

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

您不能标记它DONTFORK,因为您的孩子在分叉时可能正在该堆栈页面上运行,或者(在某种程度上更糟糕)它可能稍后执行函数返回并命中未映射的堆栈页面。(我什至对此进行了测试,您可以愉快地标记您的堆栈 DONTFORK,当您分叉时会发生坏事)。

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

这意味着如果你分叉,你会冒着牛的风险。即使孩子“只是”执行,它仍然可能会触及堆栈页面并导致 CoW,导致父级获得不同的页面,这很糟糕。

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

我认为您真的希望所有分配给驱动程序的内存都来自用户空间中的自定义分配器。它不应该那么具有侵入性。mmap正如其他答案所建议的那样,分配器可以直接使用您的设备,也可以仅使用匿名mmap, madvise(DONTFORK),并且可能mlock()避免换出。

于 2010-10-28T22:46:42.557 回答