这些问题源于阅读Spectre攻击论文。如果我理解正确,攻击源于CPU启发式方法推测性地执行(错误的)代码分支的可能性。考虑示例(在C中):
int arr[42];
if (i < 42) {
int j = arr[i];
}
如果我正确理解了这篇论文,int j=arr[i]
可以(在某些情况下)推测性地执行,即使i
为什么在数组出界访问的情况下,推测执行不会导致程序崩溃?
关键点是,在现代CPU中,动词执行并不意味着您认为它的含义。
执行一条指令是计算其输出和副作用(如果有的话)的行为。
然而,这并不会改变程序状态。
起初这似乎很难理解,但实际上没什么异国情调。
CPU有一个由所有寄存器组成的相当大的内部内存,大部分内存对程序员来说是不可见的,这部分被称为架构状态。
架构状态(AS)是CPU手册中记录的内容,是由一系列指令(例如程序)改变的。
由于更改AS只能通过ISA中给出的语义学(手册)和ISA指定串行语义学(指令按照程序顺序一个接一个地完成)来实现,因此不允许并行。
然而,现代CPU有很多资源(称为执行单元)可以独立完成工作。
为了利用所有这些资源,CPU的前端(负责从内存层次结构中读取指令并将其提供给执行单元的部分)能够在每个周期访问、解码和输出不止一条指令。
前端和后端(执行单元所在的位置)之间的界限不再真正处理指令(而是处理uops),但这是x86 CISC的麻烦。
所以现在CPU一次有4/6个uops来“执行”,但是如果ISA是串行的,除了将这些uops排队之外,它还能做什么?
嗯,前端是这样做的,这些uops不在AS上操作,而是在影子状态(SS,我这里的术语)上操作,它们的操作数被重命名,由CPU的大不可见内存的一部分组成。
改变并行或无序是可以的,因为它不是AS。
这就是执行:改变SS。
这真的值得吗?毕竟重要的是AS。
嗯,与执行相比,将SS转移到AS真的很快,所以这是值得的。
这是一个“重命名回来”(反转先前的重命名)的问题,它被称为指令的退役。
实际上,停用不仅仅是这样。
由于执行不会影响AS,副作用也不应该影响它。
这些副作用包括异常,但推测性地处理异常过于繁琐(它需要协调大量资源),因此异常处理会延迟到停用。
这也具有在处理异常时拥有正确的AS的优点,以及仅在必须实际处理异常时才引发异常的优点。
投机执行的要点是打赌,CPU打赌指令序列不会产生任何异常(包括页面错误),因此在大多数检查关闭的情况下执行它(我不能排除,从我的脑海中,一些检查没有进行),从而获得很多优势。
当它的时间退休这些指令的赌注被检查,如果任何失败,SS被丢弃。
这就是为什么推测性执行不会使您的程序崩溃。
Spectre所依赖的是这样一个事实,即推测执行确实在某种意义上改变了AS:缓存没有失效(同样出于性能原因,当赌注关闭时,SS根本不会复制到AS中),定时攻击是可能的。
这可以通过多种方式纠正,包括在读取TLB时执行基本权限检查(毕竟只使用权限0和3,因此逻辑很简单)或在缓存行中添加一位以标记它们为推测(被非推测代码视为无效)。