最近业界爆出了Intel处理器猜测执行导致的漏洞。其实这个漏洞并非能够被轻易利用的那种,只是降低了黑客们侵入的门槛。
按照原生态思维,OS内核数据和代码区应该与用户区完全隔离,用户程序看到和访问的所有地址应该都是用户态地址,但是当用户程序执行系统调用时,进程会切入到内核态访问内核代码和数据,此时页表也需要从用户态页表切到内核态页表,这样性能比较差,因为页表在RAM中,纵使有TLB缓存,页表切换时之前TLB缓存的内容也会失效,要重新预热。于是目前主流OS都是将内核代码和数据区放置到每个用户进程虚拟地址空间中的高位区,32bit系统放到3~4G,windows则默认占用2~4G区,可以改为3~4G。64bit系统也放在高位。
每个进程空间的内核区都被映射到同样的物理内存区中,这样,进程的切换并不会导致TLB中之前缓存的那些针对内核区的页表条目作废,保证了性能。
进程无法访问内核区,因为强行访问的话,页表条目中会有权限位,进程当前的权限被保存在CS寄存器的CPL字段中,为Ring3,而内核页表项的权限为Ring0,CPU会禁止访问。
假设,OS内核爆出了某个漏洞,如果黑客向某个地址挂接或者注入代码则将取得Ring0权限。即便OS修复了该漏洞,但是有众多用户不希望打补丁,或者各种原因吧,继续裸跑,那么就有可能中招。为了“一劳永逸”的解决这个问题,或者说提高黑客们的侵入门槛,OS提供了一种机制,每次启动时,内核的代码和数据会被随机的放置在内核区,每次启动都有不同的布局,这样,即便某个漏洞被发现存在于某某地址,但是其他机器上的可不一定也是这个地址,这样,侵入程序就失去了通用性。该技术被称为ASLR(Address Space Layout Randomization)。
道高一尺魔高一丈。黑客们自有办法旁敲侧击。比如一个不透明的箱子(内核区),你想知道箱子左下角放的是什么数据,有没有数据,怎么办?敲一下听听声音,晃晃试试轻重。是的,黑客们也这么干。比如,先执行一个系统调用,sys_fork( )创建新任务,此时,CPU的TLB、L1/2/3 Cache中可能会充满与fork有关的代码和数据,调用返回后,这些内容并不会立即就被清掉,而是继续待在TLB和Cache中。然后,该程序尝试访问内核区某地址,当然这个访问会被禁止,CPU会发出GP通用保护异常。但是再被禁止之前,CPU其实是将对应地址的页表条目载入TLB,然后检查其权限发现不通过,然后才报异常。
如果程序接连发出多次访存请求访问内核区,由于目前处理器内部普遍采用提前执行的深流水线,所有这些指令都会被提前执行,提前将页表条目载入TLB,然后检查权限,不匹配则会在指令执行结果提交时报异常。但是其执行痕迹却留在了缓存中。如果程序尝试访问的内核区恰恰就是刚才所执行的系统调用相关的内容,那么缓存命中,对应条目并不会被清掉,此时程序可以尝试再次发送同样系统调用,此时会发现这个调用返回的比之前更快,这就说明了,该内核区域存放的可能就是sys_fork相关的代码和数据。如果再通过进一步的不断尝试,旁敲侧击,就可以精确判断出哪块内核代码、数据在内核虚拟区的哪里,这样,ASLR的效果就被绕过了,也就形成了漏洞。程序可以把整个内核区域都测试一遍,然后不断的分析,得出最终结论。
Intel本次爆出的漏洞,是提前执行漏洞。其与上述过程的区别在于,Intel在提前执行时,不禁将页表载入了TLB,而且连对应地址上的数据都被载入了缓存,这虽然不违反用户态永远无法直接访问内核态的原则,因为数据最终必须被载入寄存器才会被判定为“访问”,程序无法直接访问缓存,但是敏感数据已经进入了缓存,这说明Intel提前执行的力度太高。那么,这又有什么问题呢?
如果不提前执行,该越权访存指令并不会导致真的去访存,从而数据也就不会进入缓存,因为看到tlb权限不匹配就不会再去访存了,而Intel的提前执行被设计的太过激进,执行时甚至都不去检查权限,因为检查的话会增加开销,到最后检查也来得及,所以直接就去访存了,因为访存这一步比较慢,提前访存可以屏蔽访存的时延。所以,如果不提前执行,缓存中并不会有该内核地址的数据被存入,那又有什么可推断出来的结论?
如果由于提前执行而导致了有数据被存入,那么之前程序的猜测会更加精准。比如用户程序先发起一系列越权访问,导致内核某块数据载入cache,然后程序通过改变一系列调用方式重新向内核发起调用,根据调用返回时间判断本次调用是否命中了cache中的这块数据,命中了则返回更快。那么用户改变了这个参数会导致内核载入哪块数据,这个是可以通过阅读内核源码来分析出来的(Linux躺枪),在根据之前越权访问的是哪个地址?那么就可以更精准的才出来,哪块地址上存储的是哪块内核数据/代码。
所以说,Intel过于激进的提前执行,连访存都真的发生了,进入缓存了,从而让黑客有更大的可乘之机,这就是这个漏洞的技术本质。
解决办法:该硬件,提前执行不访存(性能太差),或者提前执行时就检查权限。要么,该软件,OS切换为KPTI(KernelPage Table Isolation)模式,也就是重新回到原生态思维下的内核与用户态完全隔离,这样,用户态程序访问的任何地址都是用户地址,访问不了内核区。
冬瓜哥也给出一个处理方式:是不是可以在cpu内加一个寄存器用来记录内核虚拟地址边界,凡是尝试访问的,一开始就给禁掉,而不是去读页表,进tlb,检测权限。