看了三天的 paper 终于看懂 Meltdown 怎么做到的
Spectre 太复杂看不懂没时间懒得看所以不谈
首先我默认大家都知道
instruction-level paralleism 指令层级平行,
superscalar 超纯量,
out-of-order execution 乱序执行,
speculative execution 推测执行
他们之间的关联为,为了达成 ILP 所以引入 superscalar。
但是资料相依性问题造成 superscalar 不能很好发挥,因此
引入 out-of-order execution 让 CPU 挑邻近的可以同时执
行的指令执行。再来 speculative execution 遇到条件分支
conditional branch 时先预测会进某个分支并执行。
除此之外还有 micro op,以下写作 uop,则是把一个 instr-
cution 拆成好几个 uop 并把他丢下去跑。CPU 里有好几个不
同运算单元,包含算术运算、读写单元、分支单元,而且分别
都有复数分身可以同时执行。所以指令拆成 uop 以后先堆到
buffer 里面,再丢到各个单元去跑。
把以上所有东西合起来就是,先解决资料相依性问题,挑几个
邻近的指令进来,拆成 uop,丢到 buffer,开始排程去跑,
并根据分支预测把可能的指令拿过来拆一拆一起下去跑。
接着 Meltdown 攻击的问题点在于,当 user mode process
存取他不该存取的内存资料时,那个指令会产生一个例外
execption, 接着会产生一个中断 interrupt 丢给 OS 去处理
这个例外。但因为以上几个东西合在一起,其实指令的执行先
后顺序是不一定的,有可能后面的跑一跑并且产生了一些副作
用,结果因为前面的指令产生例外,清空 buffer 与管线,虽
然暂存器 register 与内存都没有写入,理论上是当作没发
生,可是却有副作用发生了。这个副作用就是内存存取,会
先加载快取 cache。Layer 几的快取其实不重要,因为重点是
它被加载快取中,所以存取时花费的时间一定比从内存存取
来的快。这个时间的差异就是泄漏出来的资讯。
考虑下面的指令
1 ; rcx = kernel address
2 ; rbx = probe array
3 retry:
4 mov al, byte [rcx]
5 shl rax, 0xc
6 jz retry
7 mov rbx, qword [rbx + rax]
在第四行会产生一个例外,但因为 OOOE 与推测执行,造成第
七行的读取内存行为也执行了,它存取的资料被从内存中
搬入快取,但因为在第四行产生例外,因此 rbx 并没有真的
被写入资料。因为 rbx + rax 上的资料被移到快取了,它的
存取速度一定比其他在 probe array 里的资料来的快,因此
只要扫一次整个 probe array 看看哪个资料存取速度比别的
快,再算出它与 probe array 起始位置的偏移量,就知道在
第四行中从 rcx 读取到的 kernel data 是什么。
所以 KPTI 就是完全分离 kernel address 与 user address
,也就是 kernel space 的所有资料都不在 user space 中,
在 user space 读资料都读不到 kernel 资料,相对应的也不
会有例外产生。