硬件与系统安全概述

计算机体系结构

现代计算机体系结构基本都是基于冯-诺依曼体系结构,程序代码和数据都是放在内存中的,运行时CPU从内存中逐条取指令并执行, 指令还可以在必要时访问内存数据。
哈佛结构是冯-诺依曼结构的一种变形,特点是将程序存储器和数据存储器分开,即有两块独立的内存,一块存放指令,一块存放数据。

对于程序员,最多只能涉及到内存指令和数据这一级,计算机暴露给程序员的,只能是冯-诺依曼结构或者哈佛结构,称为计算机 体系结构。

如何提高CPU的运行速度

CPU执行一条指令,需要经过从内存中取指令、将指令译码解析成微操作、微操作最终驱动硬件电路部件三个步骤(即取指令、译码和执行)。
如果要等三个步骤都执行后才能执行下一条指令,则CPU速度无法提高。
现代CPU使用流水线技术,在第一条指令译码时,同时取第二条指令,效率大大提高。

流水线有效减少了单条指令的平均执行时间,但指令仍然是一条条顺序执行的。但是对于不同的寄存器来说,是可以并行执行的, 执行顺序的先后并不影响最终执行结果,乱序执行可以进一步提高指令执行速度。

但流水线和乱序执行都会碰到一个问题,从原理上讲它们仅适用于纯顺序执行的指令,一旦遇到分支,即条件跳转指令,因为不执行 到条件跳转指令本身,是没法知道程序转向何处执行的,也就是条件跳转指令的下一条指令在未执行前不确定,因此无法预先取得 条件跳转指令的后续指令,这时流水线和乱序执行都会失效,因为它们的前提是预先取得后续指令。

为了解决这个问题,现代CPU广泛使用分支预测手段。也就是预测条件跳转指令会跳向哪个分支,然后对这个分支进行预取后续 指令。分支预测常用的策略是:如果某一段时间内某一条件跳转都走向某一固定分支,则可以预测这条条件跳转指令下一次很大可能 也走向这一分支。

分支预测配合流水线和乱序执行,能够大大提高CPU的运行速度,是现代CPU的主流设计方法。

分支预测带来的问题--指令执行的回滚

分支预测不能保证100%成功预测,一旦预测失败,那么按照分支预测的后续指令实际上失效,这些指令已经完成的工作必须取消掉,否则 会造成错误的指令执行。

克服CPU运行速度与内存访问速度的差异--高速缓冲(Cache)

目前CPU主频已经达到3GHz以上,普遍采用多核并行,尽管主内存(DDR SDRAM)的主频已经达到2GHz—3GHz甚至更高,也无法完全满足 多核CPU运行速度的需求,因为指令执行还是必须从内存中取指令,如果内存访问速度不够,CPU运行速度会受到内存访问速度的限制。

为了克服这个问题,目前采用在CPU与主内存之间插入多级高速缓存(Cache)的方法,Cache是一种访问速度极高的存储器, 甚至可以集成在CPU内部,成为CPU微结构的一部分。Cache与主内存之间以块为单位交换数据,块长一般为数十字节。

当CPU需要访问内存,例如从内存中取指令时,第一次需要先将相应内存块一次性读入到空闲的Cache块,CPU再直接访问Cache块, 此时内存访问速度会慢一些,因为存在主内存与Cache之间传输成块数据的时间;CPU第二次访问相同块内存时,即可直接访问Cache块, 而无须访问主内存,内存访问速度会快得多。

主内存—Cache系统构成现代CPU的内存储器系统,其原理与操作系统中的硬盘—内存系统构成虚拟内存的原理极其相似。

指令执行的回滚在主内存--Cache系统留下的痕迹

如果分支预测失败,则分支预测预取的后续指令需要回滚。撤销指令是容易的,指令完成的工作,无非是对寄存器或者内存的修改,可以 暂且将修改缓存起来,如果撤销,最终不真正修改寄存器或者内存即可。

而内存则无法撤销,读内存提交前,CPU会将Cache块准备好,就算被回滚,相应内存块也已经读入到了Cache块。

而内存块是否已经读入到了Cache块,访问速度是有一定差异的,这个痕迹是可以被侧信道利用的。

缓存侧信道攻击实质

CPU微结构内部信息通过侧信道向宏观计算机体系结构的泄露
侧信道是指信息意外地从一个实体泄露到另一个实体的途径。

推测执行的两种漏洞

推测执行包含两种技术,乱序执行(out-of-order execution)和跳转预测(branch prediction),分别对应 熔断(Meltdown)漏洞和幽灵(Spectre)漏洞
Meltdown只涉及Intel处理器,Spectre影响Intel、AMD以及ARM处理器。
Meltdown和Spectre本质上都是基于缓存侧信道的攻击。
Meltdown只能从用户态攻击内核,Spectre攻击可以攻击任何有缺陷的对象,它要求被攻击对象里面有如下Pattern的代码

if (index1 < array_a_size) {
  index2 = array_a[index1];
  if (index2 < array_b_size)
     value = array_b[index2];
}

理论上,如果index1越界,后面的代码不会被执行。但按预执行理论,即使index1超出了array_a_size的范围,它还是会 预执行,一旦这个预执行被执行,我就可以通过控制index1的长度,让array_b的特定下标的数据Cacheline被点亮,如果我 有办法访问一次array_b的全部内容,index1的内容就被我抠出来了。

攻击原理

刷新与重载(Flush and Reload)的攻击中,攻击者首先刷新指令清空高速缓冲存储器的数据,然后等待 被攻击者去读取数据,因为数据不在高速缓冲存储器中,所以被攻击者请求的任何数据都必须从主存储器中获取。 然后,攻击者访问共享数据,同时测定这一过程所需的时间。时间少的就是缓存命中的数据。