AMD ERAPS

  1. 背景
  2. ERAPS
  3. cmdline对stuff rsb的影响
  4. 参考

背景

​ 在看社区近期commit的时候,发现有一些patch可能对AMD的cpu有性能优化,讲的是AMD ZEN5+ CPU中支持了一个新feature ERAPS看一下这块内容。

ERAPS

​ 一切从RSB开始。

​ If a CPU is to speculate past a return instruction, it must have some idea of where the code will return to. In recent Intel processors, there is a special hidden data structure called the “return stack buffer” (RSB) that caches return addresses for speculation.

​ 众所周知,一个函数在调用另一个函数的时候,cpu需要先把旧函数的返回地址push压栈,然后jmp到新函数去执行,等新函数执行完之后,再pop从栈中取出这个旧函数的返回地址,然后再去继续执行旧函数。而这个push pop过程是需要把返回地址压入内存栈、从内存栈中取出的。而为了优化这一部分push pop的时间,就出现了RSB,这是一个CPU内部维护的一个buffer。当call执行一个函数的时候,push压栈函数返回地址的时候,向RSB中也写入一份,当需要pop出栈的时候,先从RSB中尝试获取返回地址,只有这个预测失败了,才去内存栈中再去pop。所以RSB的作用就是加快ret的速度,减少pop内存栈中去取函数返回地址的访存时间。

​ 这个函数返回地址的预测就叫RET Predictor,在AMD中被称为Return Address Predictor (RAP),RSB被称为Return Address Store (RAS),无所谓,作用都是一样的

​ 既然RSB能够预测返回地址,那自然就引起攻击者们的注意了,所以就产生了对各种predictor(不止RSB一种predictor)的攻击,也就是Spectre v2漏洞。

​ 既然有攻击了,那就得研究防御方式。

  • Indirect Branch Prediction Barrier (IBPB):这种防御攻击的方式是在indirect branch predictor中建立一个barrier,这个barrier防止在barrier之前执行的indirect branch影响之后的indirect branch predictor。这种方式适用于jmp call ret predictor。

  • Indirect Branch Restricted Speculation (IBRS):It is a processor feature that prevents software running in userspace (or in a guest VM) from influencing the prediction of indirect branch targets in the kernel. When enabled, it also prevents indirect branch predictions to be shared between sibling processor threads (like STIBP does)。原理大概是自动划分并管理predictor域,在userspace和kernel切换之类的场景下清空预测做到隔离

​ 说回RSB,既然他是一个buffer,那就肯定有长度限制,一般可能就是16-32个entry大小(具体可以在intel或amd的spec中找到),那么如果一个调用链过长的话,就会把RSB填满,再写入的话就必须清空最旧的entry,这时候就可能发生underflow,然后cpu可能会从其他地方(比如branch history buffer,BHB)去继续预测。举个例子一个可能发生的情况是,假设RSB共4个entry,攻击者构造一个很长的调用链触发RSB underflow,然后返回导致RSB被清空,cpu会继续使用其他方式去预测,比如BHB,然后攻击者通过系统调用之类的方式进入内核态执行任务然后退出,这时候RSB中已经清空或者无效了,可能使用BHB去预测,如果BHB被攻击者训练好是一个恶意的内容的话,内核态返回的时候就去执行恶意的内容去了

call A    ; RSB: [A_ret]
call B    ; RSB: [B_ret, A_ret]
call C    ; RSB: [C_ret, B_ret, A_ret]
call D    ; RSB: [D_ret, C_ret, B_ret, A_ret]
call E    ; RSB: [E_ret, D_ret, C_ret, B_ret] ← A_ret 被挤出
...
ret       ; RSB: [D_ret, C_ret, B_ret]
ret       ; RSB: [C_ret, B_ret]
ret       ; RSB: [B_ret]
ret       ; RSB: [] ← 此时 RSB 为空(Underflow)
ret       ; CPU回退到其他预测机制,比如BHB

​ 所以本来只能IBRS来解决利用RSB underflow的攻击,上面已经说到了,IBRS可以划分并管理predictor domain,domain切换的时候应该是会清空这些内容的。但是这个东西会影响性能啊,文章明确提到IBRS会reducing performance by as much as 30%降低将近30%的性能。

​ 所以就又出现了一种方法,也就是这篇文章提到的https://lwn.net/Articles/901834/,防止RSB underflow攻击最好的方式自然就是不产生RSB underflow就好了。当RSB快耗尽的时候就填充RSB,使CPU不会回退到BHB等predictor,也就不会执行到攻击者训练好的返回地址上。

​ 怎么判断RSB是否快耗尽呢?搞一个per cpu变量,每次call的时候就右移,ret就左移,类似这种方式,就知道RSB什么时候快耗尽了。

​ 知道RSB耗尽了,怎么填充?填充什么呢?看一下这块

#这是fill RSB的宏,如果ftr,就执行__FILL_RETURN_BUFFER,如果ftr2,就执行__FILL_ONE_RETURN,否则,就执行Lskip_rsb_\@,也就是跳过fill了
.macro FILL_RETURN_BUFFER reg:req nr:req ftr:req ftr2=ALT_NOT(X86_FEATURE_ALWAYS)
	ALTERNATIVE_2 "jmp .Lskip_rsb_\@", \
		__stringify(__FILL_RETURN_BUFFER(\reg,\nr)), \ftr, \
		__stringify(nop;nop;__FILL_ONE_RETURN), \ftr2

.Lskip_rsb_\@:
.endm

#__FILL_RETURN_BUFFER过程
#先把nr除2存入reg,作为循环的次数,为什么除2?可以看后面,一次执行两条__FILL_RETURN_SLOT,所以nr/2次循环就够了
#__FILL_RETURN_SLOT填充rsb
#调整栈指针
#循环处理
#ASM_CREDIT_CALL_DEPTH,就是上面说的那个rsb溢出计数的重新设置一下
#define __FILL_RETURN_BUFFER(reg, nr)			\
	mov	$(nr/2), reg;				\
771:							\
	__FILL_RETURN_SLOT				\
	__FILL_RETURN_SLOT				\
	add	$(BITS_PER_LONG/8) * 2, %_ASM_SP;	\
	dec	reg;					\
	jnz	771b;					\
	/* barrier for jnz misprediction */		\
	lfence;						\
	ASM_CREDIT_CALL_DEPTH				\
	CALL_THUNKS_DEBUG_INC_CTXSW

#define ASM_CREDIT_CALL_DEPTH					\
	movq	$-1, PER_CPU_VAR(pcpu_hot + X86_call_depth);

#看一下__FILL_RETURN_SLOT怎么处理的
#其实就是一个call指令,后面一个int3
#为什么还有int3?实际上,call指令就填充了rsb了
#看__FILL_RETURN_BUFFER,执行完__FILL_RETURN_SLOT之后其实是有栈指针的修改的
#所以正常情况下,就执行不到这些地方去
#但是万一有攻击者训练预测返回值这些的,执行了ret了,也能保证到时候会调用一个int3防御一下
#define __FILL_RETURN_SLOT			\
	ANNOTATE_INTRA_FUNCTION_CALL;		\
	call	772f;				\
	int3;					\
772:

.Lskip_rsb_@注释:

前面的L,All local labels begin with L. Normally both as and ld forget symbols that start with L. These labels are used for symbols you are never intended to see. If you use the -L option then as retains these symbols in the object file. If you also instruct ld to retain these symbols, you may use them in debugging.表示的是local label,也就是一个本地标签,在汇编和链接时都不会显示这个符号。如果想调试的话,需要在as汇编阶段加上-L

后面的@,as maintains a counter of how many macros it has executed in this pseudo-variable; you can copy that number to your output with `@‘, but *only within a macro definition.*也就是相当于维护一个计数器,记录宏展开的次数。什么作用呢?防止宏命名冲突?因为这个宏不一定只在一处调用展开,可能多处调用,那就有可能出现多个Lskip_rsb_,但是使用@的话,每一处就会有唯一的命名,比如Lskip_rsb_1、Lskip_rsb_2

​ 所以这个stuff的方式就是在需要的地方通过call指令来填充rsb,防止调到攻击者的恶意返回地址。(顺便说一下,文档提到,stuff rsb的方式比IBRS性能要好不少,甚至有382%的性能提升)

​ 哪些地方会调用?目前看在 __switch_to_asm的汇编中(进程上下文切换)、__vmx_vcpu_run、__svm_vcpu_run会执行

​ 好,了解以上内容后,ERAPS的作用就很明显了,就是使用硬件填充rsb来代替每次contest switch、vm exit的这种手动的指令filling。那这个性能自然又比stuff rsb要好不少。

cmdline对stuff rsb的影响

cmdline加上nospectre_v2这些东西可以允许不对这些漏洞进行修复,mitigations=off 则是直接关闭所有漏洞修复,那么这些cmdline会不会关闭stuff rsb呢?

static void __init spectre_v2_select_mitigation(void)
{
	enum spectre_v2_mitigation_cmd cmd = spectre_v2_parse_cmdline();
	enum spectre_v2_mitigation mode = SPECTRE_V2_NONE;
  ...
  setup_force_cpu_cap(X86_FEATURE_RSB_CTXSW);
  ...

static enum spectre_v2_mitigation_cmd __init spectre_v2_parse_cmdline(void)
{
	enum spectre_v2_mitigation_cmd cmd = SPECTRE_V2_CMD_AUTO;
	char arg[20];
	int ret, i;

	if (cmdline_find_option_bool(boot_command_line, "nospectre_v2") ||
	    cpu_mitigations_off())
		return SPECTRE_V2_CMD_NONE;

​ 上面可以看出,如果设置了nospectre_v2或者mitigations=off的话,就不set X86_FEATURE_RSB_CTXSW了,那执行FILL_RETURN_BUFFER的时候不会成功了

​ 但是,如果这个cpufeature本来就是开启的,那是不是就即使设置上nospectre_v2或者mitigations=off,也会stuff rsb呢?

/*
 * Auxiliary flags: Linux defined - For features scattered in various
 * CPUID levels like 0x6, 0xA etc, word 7.
 *
 * Reuse free bits when adding new feature flags!
 */


#define X86_FEATURE_RSB_VMEXIT		( 7*32+13) /* "" Fill RSB on VM-Exit */
......
#define X86_FEATURE_RSB_CTXSW		( 7*32+19) /* "" Fill RSB on context switches */

额 好吧 看上去这些feature就是linux定义开启与关闭的

既然这样的话,还是受nospectre_v2或者mitigations=off这些参数控制

参考

https://linuxsecurity.com/news/security-projects/amd-eraps-zen-5-performance-security

https://lore.kernel.org/kvm/20241031153925.36216-2-amit@kernel.org/

https://www.phoronix.com/review/amd-zen5-eraps

https://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git/commit/?id=b5cbd5ff79a06395a17f8f524f6f8e90dcfe42d1

https://lwn.net/Articles/901834/

https://blogs.oracle.com/linux/post/understanding-spectre-v2-mitigations-on-x86

https://lwn.net/ml/linux-kernel/20220716230954.334016834@linutronix.de/

https://ftp.gnu.org/old-gnu/Manuals/gas-2.9.1/html_chapter/as_7.html


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 857879363@qq.com