读取另一个进程先前拥有的物理内存帧以读取其内存页的内容

信息安全 访问控制 沙盒 虚拟内存
2021-08-18 03:03:26

我与@anger32进行了一次对话,他指出,将物理内存页帧传递给另一个进程时,将该帧支持的页面归零不是 Windows 和 Linux 等操作系统的责任(尽管他们这样做,但他们不保证这将发生),但操作系统有责任拥有允许其处理机密信息的证书。

是否有可能对另一个(也许更有特权的)进程进行以下攻击?

  1. 映射足够的内存页面并开始消耗足够的 CPU 时间来防止归零线程(至少在 Windows 上)具有最低优先级的线程获得 CPU 时间。

  2. 另一个进程将敏感数据放入内存

  3. 发生上下文切换

  4. 我们向操作系统请求一个内存页面,操作系统驱逐该进程的页面,并为我们提供由同一页框支持的新页面,而不会将其归零。

  5. 我们扫描页面寻找秘密。

他还指出,有一些方法可以在 Linux 上读取另一个进程的内存mmap、其标志和物理地址。你知道任何?真的有可能在 Linux 上获得另一个进程的内存,例如另一个用户或 SELinux 域的进程的内存吗?如果是这样,它看起来像一个非常危险的漏洞。

4个回答

我自己曾经对此很好奇,并在 linux 下编写了一个小程序,将所有可用内存 malloc 并将其转储到磁盘。

事实证明,在它交给我的应用程序之前,它已经全部归零了。

后来,我也检查了内核代码,可以确认是内核做的。

--

我认为确保旧数据不被其他进程使用是操作系统的责任,这是完全有道理的。您还会在哪里实施这样的安全措施?

编辑:

我不记得我是否针对 SWAP 内存进行了测试。由于将分配的磁盘空间(内存)归零所需的磁盘 IO,它可能会以不同的方式实现。

在 Linux 中,当满足以下任何条件时,进程能够读取另一个进程内存:

  1. 该进程具有 root 权限,或者它可以读取/proc/$PID/memor /dev/mem,默认情况下/proc/$PID/mem只能/dev/mem由 root 访问
  2. 父进程可以fork()/clone()以允许它读取其子进程的部分或全部内存的方式
  3. 父进程可以分叉子进程以允许子进程读取父进程的部分或全部内存
  4. 一个进程可以设置共享内存区域

除非进程以提升的特权运行,否则进程无法读取或写入任意不相关进程的内存。在所有其他情况下,您需要成为父进程或目标进程必须故意设置共享内存区域。

父进程可以访问子进程内存是大多数 Unix 系统的定义特征。在 Unix 系统(包括 Linux)中,新进程是使用fork()系统调用创建的。fork()通过在 OS 进程表中创建新条目来创建现有进程的副本,并将新进程虚拟内存设置为父虚拟内存的写入时副本。这确实意味着新进程可以读取父进程的内存,但此时新进程仍在执行父进程的代码,所以这不是安全问题。然后,新进程可能会调用其中一个exec*()系统调用将一个新的可执行文件重新映射到它自己的内存中并跳转到该可执行文件的开始符号。重新映射意味着现在新进程虚拟内存上的分页表中唯一的条目是新的可执行文件。当新进程试图读/写重新映射的区域时,会导致页面错误,操作系统会将被编辑的可执行文件的相应部分分页exec*()到内存中。如果新进程试图读取/写入未映射的内存区域,这将导致分段错误。在 fork 和 exec 的更高级用法中,进程可以 fork 然后映射子进程内存,这样子进程的全部或部分内存将在exec*().

在 Linux 中,当一个进程从操作系统分配内存(例如使用 malloc)时,该进程调用mmap()分配匿名映射。匿名映射由 RAM 或交换提供。匿名 mmap 由内核填充为零,除非进程请求MAP_UNINITIALIZED,这仅在非常受限制的嵌入式系统上出于性能原因而受到尊重,内核必须被编译以允许这样做。

此外,对于高安全性场景,Linux 允许进程通过使用 mlock 和/或 MAP_LOCKED 请求其全部或部分内存不可交换。锁定内存始终由 RAM 提供,通常用于防止加密密钥和用于解密的内存被交换到持久存储。

您描述的攻击在 Windows 上不起作用。饿死页面归零线程并不能阻止归零,它只会延迟它。页面归零后台任务的存在是一种性能优化。

基本上,具有隐私保证的朴素内存管理器的工作方式如下:

  • 从释放列表中保留一个页面
  • 归零
  • 使其可用于应用程序代码(设置页表条目以允许环 3 访问)

优化后的版本看起来更像:

  • 从归零列表中获取一页,如果有的话
  • 如果第一步成功,跳到最后一步
  • 从释放列表中保留一个页面
  • 归零
  • 使其可用于应用程序代码(设置页表条目以允许环 3 访问)

使归零线程饥饿将导致分配缓慢,因为归零尚未完成。它不会导致数据泄漏,因为数据结构保持归零和剩余页面隔离,并且当分配器用完预归零页面时,它必须及时进行归零。

绝对可以读取另一个进程的内存,但这只有在具有管理权限的情况下才有可能,当然操作系统不允许任何进程访问未分配给该进程的任何内存空间。

对于管理用户,这当然是可能的。例如,在 Windows 中,此功能默认实现以调试进程。您可以使用此处描述的任务管理器执行此操作。

但它也可以转储整个内存,包括所有进程以及当时存储在内存中的所有内容。这不再那么容易了,因为 Windows 系统默认不提供此功能。为此,应用程序会加载它自己的内存驱动程序,这允许它们直接访问内存,而不是通过操作系统。

在较旧的 linux 系统上,我们可以直接通过/dev分区中的内存设备转储内存。这已经不可能了,但是有一些内核模块也可以转储整个内存。这也需要 root 权限。您也可以为此处所述的一个过程手动执行此操作

//编辑:我刚刚询问了一位拥有 40 年经验的高级开发人员。答案是:它依赖于各种因素。他告诉我用 C++ 和 Java 初始化变量,这意味着用 C++ 或 Java 编写的应用程序将无法获取旧信息,因为它被初始化该变量所覆盖。但是 C 没有这样做,所以它可能是可能的,但它仍然依赖于操作系统。