关注了就能看到更多这么棒的文章哦~
memfd_secret() in 5.14
By Jonathan Corbet
August 6, 2021
DeepL assisted translation
https://lwn.net/Articles/865256/
2020 年 2 月之后,memfd_secret() 系统调用其实已经在 LWN 以其他形式报道过。一开始,它是 memfd_create()的一个 flag,但这部分功能后来被移到单独的系统调用中了。在这个功能发展的过程中有许多变化,但核心目的仍然是保持未变的,即允许用户空间的进程来创建一个内存区域,确保其他人(包括 kernel 本身)都无法访问。该内存区域可以用来存放加密密钥,或其他类似的不能暴露给他人的数据。这个新的系统调用最终被合并到了即将发布的 5.14 版本中。下面我们来看看这个系统调用最终在 mainline kernel 中是什么样子的。
memfd_secret()的原型如下:
int memfd_secret(unsigned int flags);
其中唯一可用的 flag 取值是 O_CLOEXEC,用来设置当此进程后续调用 execve()时,这部分区域会被移除掉。不过,在 fork 调用之后这部分应该保密的区域对子进程仍是可以访问的。memfd_secret() 的返回值是一个文件描述符,用来跟这个新创建的秘密内存区域关联起来。
在这个时刻,进程实际上不能访问这部分内存区域,因为目前甚至连 size 都还不知道。后续必须调用 ftruncate() 来设置该区域的 size,然后使用 mmap() 将该 "file" map 到属主进程的地址空间之内。针对这个 mapping 而分配的那些内存 page 会被从 kernel 的 direct map 中移除掉,并被特别标记过,防止它们后续被错误地重新 map 回来。此后,该内存区域是可以被该进程访问的,但对其他任何进程甚至包括内核都不能访问这一区域了。因此,这部分内存的数据就被很好地保护了起来。很好,但这也带来一些缺点。例如,指向这部分秘密内存区域的指针不能用在系统调用里了;该内存区域也不能用于 DMA 操作。
这项工作第一次公开发表是在 2019 年 10 月。在后续两年不断演进这个系统调用的时间里,它已经有了不少改变,不仅仅是变成了一个单独的系统调用(发生于 2020 年 7 月)而已,这是因为人们认为此功能与普通 memfds 操作完全没有什么共同点。在 2020 年 7 月底发布的第二版中 memfd_secret() 支持了预留一块固定区域的 memory,但在 9 月的第 5 版中就被删除了。
早期的 patch set 中包括一个 flag,用来要求从内核的 direct map 中删除这部分秘密 page。这样就可以让内核无法访问这些内存区域了(任何可能黑入内核的人也就无法看到这些数据了),但人们担心把用来形成 direct mapping 的 1GB page 中间到处挖洞的话会降低性能。不过,这些担忧已经随着时间的推移都消退了。实际上 2MB page 的性能表现与 1GB page 的性能并没有什么差异。因此,这个 flag 在 2020 年 8 月的第 4 版中消失了,变成了必须要从 direct map 中移除。
11 月发布的第 8 版中也增加了一些改动。会使用 CMA 来专门用作 memfd_secret() 的内存来源,而不是随便分配一块内核管理的内存了。另一个改动也一直让大家很有争议,它的作用是只要有一个秘密内存区域处于 active 状态时,就禁止系统的休眠(hibernation)。这样做的目的是防止秘密数据被写入持久性存储设备中(persistent storage),但如果一些用户发现他们的系统不能再休眠的话很可能会感到不满。尽管如此,这个行为也还是进入了 5.14 内核。
从一开始,memfd_secret()就支持一个 flag 专门用来申请 uncached mapping 来作为秘密数据区域,也就是说希望绕过 memory cache。这样就可以提供更高的安全性,因为在 cache 中都不会再有这些数据了(毕竟 Spectre 漏洞就可以窃取 cache 内容),但将内存设置为 uncached 的话会大大降低性能。cache 毕竟是非常重要的。Andy Lutomirski 一再反对提供这种 uncached mapping,反对因此引入的性能降低等。RApoport 最终同意把这个 flag 删除掉了。在第 11 版完成了删除,此后到现在的最新版本为止都不再具有这个功能了。
在第 17 版(2021 年 2 月)代码中,memfd_secret() 被默认禁用了,并增加了一个 command-line option(secretmem_enable=),用来在系统启动时启用这个功能。这个决定是出于担心系统性能可能会因为打破了 direct mapping 以及对秘密区域内存进行 locking 操作而受到影响。所以除非系统管理员将其打开,否则该功能就不可用。这个版本也不再使用 CMA 进行内存分配了。
基本上这就是最终版本的 memfd_secret() 的效果了。它已经来到了第 23 个版本,看起来版本非常多了,但一般来说内存管理方面的改动基本上都是这样的。等 5.14 版本发布后,我们将能看到这个功能的用户群体有多大,以及他们还需要哪些进一步的改动。不过目前而言,这项工作似乎最终已经成功实现完毕了。有兴趣的人可以参见这个 man page 草案(https://lwn.net/ml/linux-mm/20210729082900.1581359-1-rppt@kernel.org/)。
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。
欢迎分享、转载及基于现有协议再创作~
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~