Re: [情报] Intel严重漏洞 OS更新将会降低效能

楼主: jk21234 (BL2400PT真不错)   2018-01-06 00:20:55
为什么cache预料之外的hit会导致data外流....
其实表面来说 资料没有被读出来 但是是被穷举的方式猜出来的
基本原理
0. int64 a = rdtsc()
RDTSC = Read the Time Stamp Counter,
这个数字从开机的时候就从0开始增加,一个cycle + 1.
这个指令不用进入高权限模式就可以执行
Pentium 75开始就有,在1995之后大量被使用
来测试效能 或者是作速度控制.
导致为了相容性不容易把它拔掉或者禁止低权限模式使用
有些范例会用进阶的rdtscp(),这个是修正版本
需要SSE2,但是跟多核心cpu相容...RDTSC在多核心cpu的值
不一样,本用途则差异不大
int64 a = rdtsc()
b = load_mem(ADR_A)
a = rdtsc() - a
这时候a会相近于这次load_mem所花掉的cycle数,
有一些误差 但是通常在10 cycle以下
如果我做两次 一次得到的a是> 200 , 一次是 < 20,
对cpu有点了解的就知道,cpu有一种叫做cache的机制
让读取有些时候可以加快不用那么慢
所以很直觉的, >200 应该表示cache miss , 原本不在cache中,
< 20 , 上次应该就在cache中
这个后面会应用到
<======想按左键吗??
1. 假设已知CPU的最后一层Cache 有10MB, 而且为12-way,
如果我有一个阵列U[10MB].考虑最简单的情况
我连续读取这10MB之后那一瞬间,是不是会把cache中原本的资料几乎
全部洗出来,cache中几乎全部是我这10MB的资料.
是的 这是基本假设
然后我们进阶一点,cache被U[10MB]塞满之后 我再load_mem(A),
这时候因为cache没有他的资料,一定是cache miss,然后放弃掉U[10MB]中的
某区块不在cache中,下次就是这个区块的内存万一又要读取到 就会miss
(中间省略)最后针对一个Address A,我都可以在U中找到32Byte*12
总共384Byte的位址 只要这群资料用12个load_mem()读入,就会
保证Address A的资料被挤出Cache
Q1:所以要先知道cpu cache的大小跟规格???
A:不用 用前面的方法本身就可以探索出cpu cache的最大值
跟way-associative的数值,其实就是拿各种size的阵列U
读取跟分割读取,可以求到的数值
2.
a = rdtsc()
b = load_mem(SYSTEM_A)
a = rdtsc() - a
会发生什么事情?如果SYSTEM_A是一个猜的Address,位在保护区段且不可读取
其实正常就是产生General Protection Fault,但可以Program自己接手回来
这个Exception
正常来说, B不会被更新, 但关键在第二次的rdtsc()什么时候执行
Out of order 有可能
a. load_mem之前
b. load_mem之后,exception之前
c. exception之后
第一种情况会得到一个特小个位数的值 也可以用mem_barrier()
或者mem_fence() (这两种指令是规定指令与mem_load要排队)
隔离开 第一种情况基本上就不会发生
第二种情况 惊异的是它很接近一般的mem_load,有时候可以看到
> 200t, 有时候看到 < 20t,明明SYSTEM_A就不给读取...
第三种就是rdtsc会超长,1000cycle内不太可能,或者.多核心之下不一定正常
可以当噪声值排除
不是用这个方法 但是继续进化的话...
3.设定一个阵列X,可以是256 Byte,或者适当的倍数
然后我同样有个U[10MB]的阵列
a = rdtsc()
b = load_mem(SYSTEM_A)
c = load_mem(&X + b)
mem_fense()
a = rdtsc() - a
第一次做无效的SYSTEM_A的load, B不会得到结果
但是,我这端不知道B的值 可是CPU知道X阵列的第B个元素
加起来是多少,他去加载这个位置了.....会在C拿到吗
不会 第一次load_mem无效,第二次load_mem一样无效
至于exception的问题同前,但是多跑几次在这组行为中看到cache miss
与cache hit的时间差异
然后我找个样本
a = rdtsc()
b = load_mem(DUMMY)
c = load_mem(&X + 0)
mem_fense()
a = rdtsc() - a
找出U[10MB]之中,对第二次&X + 0
具有cache互斥性的384 Byte资料为一组
抽出来 跟
a = rdtsc()
b = load_mem(SYSTEM_A)
c = load_mem(&X + B)
mem_fense()
a = rdtsc() - a
一起玩
如果发现&X + B的加载行为,Cache Miss的时间
和&X + 0的对照组相同
那么, B的内容就是0
如果比照起来不相似&X + 0与它的384 byte伙伴的相处关系
那就要再找&X + 1,这时候是另外384 byte快乐伙伴
来比较.....最多比较256次来确定一个Byte
实际上这整个流程都在穷举 应该全速运作也要十万个cycle以上
才能确定1 Byte的资料
3a.Speculative Execution
我不太确定正确的方法要不要应用speculative execution的指令
在这件事情需要用被动的指令speculativity还是主动指定为
speculative execution 但假设是的话 可以应用的范围为
无效的mem_load,原本会产生exception,
但是我可以用speculative execution,放在不会执行的if-else中
但CPU不具有足够条件做出正确的branch prediction的话,
在不执行那端的mem_load也会排入pipeline,并且在最后取消
volatile V = *Y
a = rdtsc()
if (V > 0)
else
{
b = load_mem(DUMMY)
c = load_mem(&X + 0)
}
a = rdtsc() - a
==>speculative execution化
volatile V = *Y
a = rdtsc()
SPEC_TRUE(V) { }
SPEC_FALSE(V)
{
b = load_mem(DUMMY)
c = load_mem(&X + 0)
}
a = rdtsc() - a
这时候CPU ooo会同时开始执行SPEC_TRUE与SPEC_FALSE的指令
最后SPEC_FALSE因为不成立,所以load_mem执行到中间就被取消
但这中间已经去Memory动作了 因此Cache也产生变化,但是取消
后不发exception,可能速度会加快很多 然后Host OS无法
侦测到你一直在产生GPF,降低被发现的机率
F.最后结论
以上的用法有需多前提条件 因此了解这个条件后有对应的话
可能可以避开.....对这没什么自信 降低发生问题的洞
1.电脑Cache读取的时间与外部内存差异巨大
现况一定是这样 未来也一定是 无解
2.rdtsc精确度太高 来个1000 cycle误差就行
或者限制仅kernel mode可以用(规格上已经支援 但为了相容性不开)
总之rdtsc要背太多旧程式相容性的锅..
3.直觉上,跑一个内存加载指令,应该要在合法的位置才启动啊
但实际上:
|TLB/PF |VALID|
|FETCH|DECODE|MEM
作者: PlayStation3 (超级喜欢于小文)   2018-01-06 00:30:00
JK神
作者: david7112123 (Ukuhama)   2018-01-07 01:00:00

Links booklink

Contact Us: admin [ a t ] ucptt.com