TranslateProject/translated/tech/20171220 The current state of kernel page-table isolation.md

55 lines
8.0 KiB
Markdown
Raw Normal View History

2018-01-03 23:56:23 +08:00
内核页面现在的状况 - 页表隔离
============================================================
在十月底的时候,[KAISER][8] 补丁集公布了;它做了一项工作,将内核空间与用户空间使用的页面进行了隔离,以解决 x86 处理器上向攻击者透露内核布局的 bugs。这些补丁是自它们被公布以来这一星期中最值关注的事情但是它们似乎正在接近最终的状态。这看上起像是再次审视它们的合适机会。
这项工作被重命名为 “内核页表隔离” 或者 KPTI但是目的是一样的分割页面表它现在被用户空间和内核空间共享使用将这张表分成两套内核空间和用户空间各自使用一个。这对内核的内存管理产生根本性的变化并且这也是这些年来为此问题进行争论的人所希望看到的尤其是考虑到它的性能影响的时候。不过KPTI 仍然处于快速发展的轨道上。[一组准备的补丁][2] 已被被合并到 4.15 - rc4 之后的主版本线上了 — 一般情况下仅重要的修复才被允许这样做 — 并且其余的似乎被确定在 4.16 版的合并窗口中。许多内核开发者都在这项工作上投入了大量的时间,并且 Linus Torvalds [要求][3] 将这项工作回迁到长周期的稳定内核中。
也就是说已经KPTI已经在最后期限的压力下有了所有的安全补丁的标记。对于任何基于 ARM 的读者,在这里值的注意的是,在这项工作中有一个 [为 arm64 的等效补丁集][4]。
#### 51 补丁和总数
在这篇文章中x86 补丁系统在 [version 163][5]。它包含 51 补丁,因此,我们应该感谢那些没有公开的版本。最初的补丁集,由 Dave Hansen 发布,由 Thomas Gleixner、Peter Zijlstra、Andy Lutomirski、和 Hugh Dickins 根据许多其它人的建议,做了大量的修订。在这项工作中剩余的任何 bugs 将不会在那里出现,这是在代码方面缺乏经验的结果。
在现代系统中,页面表是以一个树形结构进行组织的,这样可以高效地存储稀疏内存映射和支持巨页特性;查看[这篇 2005 年的文章][6] 了解更多细节以及它是怎么工作的一个示意图。在一个有四级页面表的系统上目前的大多数大型系统都是这样顶级是页面全局目录PGD。紧接着是页面上层目录PUD页面中层目录PMD和页面表条目PTE。有五级页面表的系统是在 PGD 下面插入了一层(称为 P4D
页面故障解析通常遍历整个树去查找感兴趣的 PTE但是巨页可以被高层级的特定条目表示。例如一个 2MB 的内存 chunk 既可以由 PMD 层级的一个单个的巨页条目表示,也可以由一个单页 PTE 条目的完整页面表示。
在现在的内核中,每个处理器有一个单个的 PGD在 KPTI 系列补丁中所采取的第一步的其中一个措施是,去创建一个第二 PGD。当内核运行时原来的仍然在使用它映射所有的地址空间。当处理器运行在用户空间时第二个被激活后在打完系列补丁之后。它指向属于这个处理器的页面的相同的目录层次但是描述内核空间位于虚拟地址空间的顶端的部分通常都不在这里。
页面表条目包含权限描述位,它记录了内存如何被访问;这些位是,不用说都知道,设置它用来阻止用户空间访问内核页面,即便是通过这些页面映射进入的地址空间。不幸的是,一些硬件级的 bugs 允许一个用户空间的攻击者去确定一个给定的内核空间地址是否被映射,而不管那个页面上映射的地址是否被允许访问。而那个信息可以被用于击败内核地址空间布局随机化,可以使一个本地的攻击者更易于得逞。在 KPTI 背后的核心思想是,切换到一个没有内核空间映射的 PGD将会使基于这个漏洞的攻击失效而到现在为止我们还没有看到这些漏洞。
#### 细节
这个想法很简单,但是,就像经常发生的那样,有很多麻烦的细节,把一个简单的想法变成一个补丁系列的 51 部分,如果处理器在用户模式运行时响应一个硬件中断,处理中断需要的内核代码将在地址空间中不存在。因此,必须有足够的内核代码在用户模式中去映射,以能够切换回到内核 PGD并且使剩余的也可用。对于 traps、非屏蔽中断、和系统调用也存在一个相似的情况。这个代码很小而且可以与其它部分隔离但是在处理切换安全性和效率时涉及到一些很复杂的细节。
另一个难题来自 x86 本地描述符表LDT的构成它可以被用于去改变用户空间的内存布局。它可以使用鲜为人知的 [`modify_ldt()`][7] 系统调用来做微调。例如,在 Linux 上早期的 POSIX 线程实现,使用了 LDT 去创建一个本地线程存储区域。在现在的 Linux 系统上LDT 几乎不再使用了但是一些应用程序比如Wine仍然需要它。当它被使用时LDT 必须能够被用户空间和内核空间都可以访问到但是它必须一直处于内核空间中。KPTI 补丁集清理内核附近内存,在 PGD 级别上为 LDT 保留一个完全的条目;因此,`vmalloc()` 调用的可用空间收缩到仅有 12,800TB。那是一个非常巨大的 LDTs 空间数,可以满足有很多 CPUs 的系统需要。这种变化的其中一个结果是LDT 的位置是固定的,并且已知道用户空间 — 一个潜在的问题是,因此可以通过覆写 LDT 来很容易地破坏整个系统。在这个系列的最终的补丁是映射为只读 LDT以阻止此类攻击。
另一个潜在的漏洞是,如果内核可以被操纵进入用户空间而无需切换到已净化的 PGD。因为内核空间 PGD 也映射用户空间内存,这种疏忽可能被忽视一段时间。在这里的响应是将用户空间的虚拟地址部分映射为内核 PGD 的非可执行部分。用户空间使用了一个错误的页面表来开始运行,将会立即崩溃。
虽然所有已存在的 x86 处理器似乎都会受到已经披露的漏洞信息的影响但是以后的处理器可能不会受此影响。KPTI 有一个可测量的运行时成本cost估计在 5%。这个成本不应该由用户去承担,尤其是他们拿到了没有这个问题的新处理器时。这里有一个 `nopti` 命令行选项,可以在机器引导时去禁用它。这个补丁系列也增加了一个新的“特性”标识(`X86_BUG_CPU_INSECURE`)去标识有漏洞的 CPUs它是在现在的所有 x86 CPUs 上设置,但是在以后的硬件上可能没有。如果没有该特性标识,页面隔离将自动被关闭。
在 4.16 版的合并窗口打开之前剩下的这一个月。在这段时间里KPTI 补丁集毫无疑问将通过一些额外的修改来解决一些难以避免的小问题。一旦稳定之后,这些代码将被合并,并且将会尽快回迁到稳定内核。显然,我们期待一个缓慢更新的 — 但是更安全的 — 内核,作为我们的新年礼物。
--------------------------------------------------------------------------------
via: https://lwn.net/SubscriberLink/741878/eb6c9d3913d7cb2b/
作者:[Jonathan Corbet ][a]
译者:[qhwdw](https://github.com/qhwdw)
校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
[a]:https://lwn.net/SubscriberLink/741878/eb6c9d3913d7cb2b/
[1]:https://lwn.net/Promo/slink-trial2-2/claim
[2]:https://git.kernel.org/linus/64a48099b3b31568ac45716b7fafcb74a0c2fcfe
[3]:https://lwn.net/Articles/741882/
[4]:https://lwn.net/Articles/740393/
[5]:https://lwn.net/Articles/741883/
[6]:https://lwn.net/Articles/117749/
[7]:http://man7.org/linux/man-pages/man2/modify_ldt.2.html
[8]:https://lwn.net/Articles/738975/