RAV*_*eus 5 assembly x86-64 osdev spectre
在阅读和学习开源操作系统时,我偶然发现了一种在汇编中调用“方法”的极其复杂的方法。它使用“ret”指令来调用库方法来执行以下操作:
push rbp ; rsp[1] = rbp
mov rbp, .continue ; save return label to rbp
xchg rbp, QWORD [rsp] ; restore rbp and set rsp[1] to return label
push rbp ; rsp[0] = rbp
mov rbp, 0x0000700000000000 + LIB_PTR_TABLE.funcOffset ; rbp = pointer to func pointer
mov rbp, QWORD [rbp] ; rbp = func pointer
xchg rbp, QWORD [rsp] ; restore rbp and set rsp[0] to func pointer
; "call" library by "returning" to the address we just planted
ret
.continue:
Run Code Online (Sandbox Code Playgroud)
我添加这些评论是为了自己理解它,看来我是正确的或足够接近的,因为我所做的所有实验都成功了。但后来我尝试这样做,效果也很完美:
mov rax, 0x0000700000000000 + LIB_PTR_TABLE.funcOffset ; rax = ptr to func ptr
mov rax, QWORD [rax] ; rax = func ptr
call rax ; actually call the library function in a normal fashion
Run Code Online (Sandbox Code Playgroud)
看看指令的数量以及 CPU 在这两种情况下实际要做的事情,我们会假设,如果一种速度更快,那就是“调用”变体。但由于使用了“ret”变体,并且首先需要大量的知识才能提出这个变体,那么第一个变体有什么优势呢?(或者确实如此?)
随着 CPU 变得越来越快,由于缓存未命中和分支预测错误等原因而导致 CPU 停顿(并且无法执行任何操作)的可能性也会增加。为了帮助避免这些停顿,大多数现代 80x86 CPU 都有一组逻辑来帮助预测控制流变化的目标地址;包括分支方向预测器、分支目标预测器、返回堆栈缓冲区等。
问题在于,恶意攻击者(使用推测执行和测量时序)可以从 CPU 收集的所有信息中提取机密信息,以提高性能;包括从分支方向预测器、分支目标预测器、返回堆栈缓冲区等中提取机密信息。
当这个问题被发现时,人们(主要是内核开发人员)争先恐后地想各种方法来缓解安全问题。具体来说,寻找避免、破坏或污染 CPU 收集的数据的方法。
更具体地说(对于您所显示的代码);如果代码使用了call rax,那么它会将数据添加到 CPU 的返回堆栈缓冲区中,恶意攻击者可以探测该缓冲区以确定有关原始值的信息rax(如果rax应该是机密的,那么这将构成机密性泄漏)。
一种替代方法是推送返回地址,然后使用间接跳转。在这种情况下,它只会将(机密)数据留在 CPU 的分支目标缓冲区中,攻击者可能会探测到这些数据,但这并没有真正的帮助。
相反ret,通过不在返回堆栈缓冲区(或分支目标缓冲区)中存储任何内容,可以防止安全问题。作为副作用,它还会“不同步”CPU 的返回堆栈缓冲区;稍微混淆了之前的调用/未来的返回。
可悲的是;所有这些都会导致性能问题 - 它让我们回到“随着 CPU 变得更快,CPU 停顿的机会增加”,并且除了停顿成本之外还增加了从错误地址获取代码的成本。