英特尔设计缺陷背后是什么迫使众多补丁?

显然有一个很大的问题,但我们不知道究竟是什么。

在过去的几个星期里,修补Linux内核的补丁已经在不断的流行。自从11月份以来,微软一直在测试Insider程序中的Windows更新,预计下周二Patch Tuesday将会将这些更新纳入主流Windows版本。微软的Azure下周进行了维护计划,亚马逊的AWS计划周五进行维护 - 大概是相关的。

由于Linux补丁程序首先被揭露,出现了一个更清晰的错误画面。虽然Linux和Windows在许多方面有所不同,这两种操作系统,而事实上,所有其他的x86操作系统,如FreeBSD和如何的基本要素的MacOS-把手系统内存是一样的,因为操作系统的这些部分是如此紧密地加上处理器的功能。

跟踪地址

系统中每个字节的内存都是隐含编号的,这些编号是每个字节的地址。使用物理内存地址操作的最早的操作系统,但物理内存地址由于许多原因而不方便。例如,在地址中通常存在间隙,并且(特别是在32位系统上),物理地址可能难于操作,需要36位数字,或者甚至更大的数字。

因此,现代操作系统都依赖于被称为虚拟内存的广义概念。虚拟内存系统允许程序和内核本身在简单,干净,统一的环境中运行。每个程序和内核本身都使用虚拟地址来访问内存,而不是使用物理地址来弥补缺陷和其他问题。这些虚拟地址是连续的,不需要担心间隙,并且可以方便地调整大小以方便操作。即使物理地址需要36位或更多的编号,32位程序也只能看到32位地址。

虽然这个虚拟寻址对于几乎每一个软件都是透明的,但是处理器最终需要知道虚拟地址指向哪个物理存储器。有一个从虚拟地址到物理地址的映射,并存储在一个称为页表的大型数据结构中。操作系统使用由处理器确定的布局来构建页表,并且处理器和操作系统结合使用页表,只要它们需要在虚拟地址和物理地址之间进行转换。

整个映射过程对于现代操作系统和处理器是非常重要和根本的,以至于处理器具有专用的缓存 - 转换后备缓冲区(TLB),它存储一定数量的虚拟到物理映射,以避免使用满页表每一次。

虚拟内存的使用为我们提供了许多超越寻址简单性的有用功能。其中最主要的是,每个单独的程序都有自己的一组虚拟地址,具有自己的一组虚拟到物理映射。这是用来提供“受保护的记忆”的基本技术。一个程序不能破坏或篡改另一个程序的内存,因为另一个程序的内存不是第一个程序的映射的一部分。

但是,每个进程单独映射的这些用法以及因此多余的页表对TLB缓存施加压力。TLB并不是非常大 - 通常是几百个映射 - 系统使用的页表越多,TLB包含任何特定的虚拟 - 物理转换的可能性就越小。

一半,一半

为了充分利用TLB,每个主流操作系统都将虚拟地址范围分为两部分。每个程序使用一半的地址; 另一半用于内核。在进程之间切换时,只有一半的页面表项改变 - 属于程序的项目。内核的一半对每个程序都是通用的(因为只有一个内核),所以它可以对每个进程使用相同的页表映射。这极大地帮助了TLB。虽然仍然需要丢弃属于进程内存地址一半的映射,但是可以保留内核一半的映射。

这个设计并不完全是一成不变的。在Linux上完成了这个工作,以便为32位进程提供整个地址范围,内核页表和每个程序之间不共享。虽然这给了程序更多的地址空间,但是却带来了性能成本,因为每次需要运行内核代码时,TLB都必须重新加载内核的页表项。因此,这种方法在x86系统上从未被广泛使用。

在内核和每个程序之间分配虚拟地址空间的决定的一个缺点是内存保护功能被削弱了。如果内核拥有自己的一组页表和虚拟地址,那么它将得到与不同程序相同的保护。内核的内存将是简单的不可见的。但是通过拆分寻址,用户程序和内核使用相同的地址范围,原则上用户程序将能够读写内核内存。

为了防止这种明显不利的情况,处理器和虚拟寻址系统具有“环”或“模式”的概念。x86处理器有很多环,但是对于这个问题,只有两个是相关的:“用户”(环3)和“管理员”(环0)。在运行常规用户程序时,处理器进入用户模式,环3。运行内核代码时,处理器处于环0,管理员模式,也称为内核模式。

这些环用于保护内核内存不受用户程序的影响。页表不仅仅是从虚拟地址到物理地址的映射,他们还包含有关这些地址的元数据,包括有关哪些环可以访问地址的信息。内核的页表项全部被标记为只能访问0; 该程序的条目被标记为可以从任何环访问。如果在环3中尝试访问环0存储器,则处理器阻止访问并产生异常。这样做的结果是,在环3中运行的用户程序应该无法获知有关内核及其环0内存的任何信息。

至少,这是理论。一连串的补丁和更新显示,这个地方已经崩溃了。这是大神秘的地方。

环之间移动

这是我们所知道的。每个现代处理器都执行一定的推测执行。例如,给定一些添加两个数字然后将结果存储在内存中的指令,处理器可能在确定内存中的目标是否实际可访问和可写入之前推测性地进行添加。在通常情况下,如果位置可写的,处理器设法节省一些时间,就像计算并行算法一样,计算出内存中的目标是什么。如果发现该位置不可访问(例如,试图写入没有映射且完全没有物理位置的地址的程序),则会产生一个异常,并推测执行被浪费。

英特尔处理器,特别是(虽然不是AMD的)处理器,允许推测性地执行写入环0存储器的环3代码。处理器正确地阻止了写操作,但是推测性的执行很快地干扰了处理器状态,因为某些数据将被加载到缓存和TLB中以确定是否允许写入。这反过来意味着一些操作将会快几个周期,或者几个周期较慢,这取决于它们的数据是否仍然在缓存中。除此之外,英特尔的处理器还具有一些特殊功能,例如Skylake处理器中引入的软件防护扩展(SGX),稍微改变了如何处理访问内存的尝试。同样,处理器仍然保护ring 0内存不受ring 3程序的影响,但是它的缓存和其他内部状态又一次被改变,造成了可测量的差异。

但是我们不知道的是内核信息有多少可以泄露给用户程序,或者泄露的程度如何。哪些英特尔处理器受到影响?再次说明这一点并不完全清楚,但有迹象表明,每一个推测性执行的英特尔芯片(这是自1995年以来推出的所有主流处理器)都可能以这种方式泄漏信息。

这个问题的第一个风向来自奥地利格拉茨技术大学的研究人员。他们发现的信息泄露足以破坏内核模式地址空间布局随机化(内核ASLR或KASLR)。ASLR是防止缓冲区溢出的最后努力。使用ASLR,程序及其数据被放置在随机存储器地址中,这使攻击者难以利用安全漏洞。KASLR将相同的随机化应用于内核,以便内核的数据(包括页表)和代码是随机定位的。

格拉茨的研究人员开发了KAISER,一组Linux内核补丁来防御这个问题。

如果问题只是使得ASLR的去随机化,这可能不会是一个巨大的灾难。ASLR是一个很好的保护,但它是已知的不完善。这意味着成为攻击者的障碍,而不是一个不可逾越的障碍。行业反应 - Windows和Linux的一个相当重大的改变,是在一些秘密的情况下开发的 - 表明ASLR不仅被打败,而且更广泛的从内核泄漏信息的能力已经被开发出来。事实上,研究人员已经开始发出警告,说他们可以泄漏并读取任意内核数据。另一种可能性是该漏洞可能被用来逃离虚拟机并损害管理程序。

Windows和Linux开发人员选择的解决方案基本上是相同的,并且源自KAISER的工作:内核页表项不再与每个进程共享。在Linux中,这被称为内核页面表隔离(KPTI)。

有了这些补丁,内存地址仍然分成两部分,这只是内核的一半几乎是空的。这并不完全是空的,因为一些内核部分需要永久映射,无论处理器是在环3 还是在环0中运行,而是接近于空。这意味着,即使一个恶意的用户程序试图探测内核内存和泄漏信息,它也会失败 - 没有什么可泄漏的。真正的内核页面表仅在内核本身运行时使用。

这首先破坏了拆分地址空间的原因。每次切换到用户程序时,TLB现在都需要清除与真实内核页表相关的任何条目,从而结束启用分割的性能节省。

这种影响会因工作量而异。每当一个程序调用内核时(从磁盘读取数据,向网络发送数据,打开一个文件等等),这个调用将会更加昂贵,因为这会迫使TLB被刷新并加载真正的内核页表。那些不使用内核的程序可能会遇到2-3%的打击,但仍然有一些开销,因为内核总是偶尔运行,以处理诸如多任务之类的事情。

但是调入内核的工作负载将会有更多的性能下降。在一个基准测试中,除了调用内核之外,其他任何事情都没有什么影响,它的性能下降了大约50%。换句话说,每次调用内核的时间比修补程序长两倍。使用Linux环回网络的基准测试结果也很受欢迎,例如这个Postgres基准测试中的17%。使用真实网络的真实数据库工作负载应该看到较低的影响,因为在真实网络中,调用内核的开销往往受到使用实际网络的开销的支配。

虽然英特尔系统是已知有缺陷的系统,但可能不是唯一受影响的系统。有些平台(如SPARC和IBM的S390)不受这个问题的困扰,因为它们的处理器内存管理不需要拆分地址空间和共享内核页表; 这些平台上的操作系统总是将它们的核心页面表与用户模式分开。但是,像ARM这样的其他公司可能不那么幸运。ARM Linux的可比较补丁正在开发中。

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: