首页
/ Linux 内核 SRSO(Speculative Return Stack Overflow / CVE-2023-20569)缓解机制深度指南

Linux 内核 SRSO(Speculative Return Stack Overflow / CVE-2023-20569)缓解机制深度指南

2026-09-07 20:27:52作者:傅爽业Veleda

导读

本文基于 Linux 内核源码树中的官方文档 Documentation/admin-guide/hw-vuln/srso.rst 展开,系统讲解针对 AMD 处理器"推测式返回栈溢出"(Speculative Return Stack Overflow,SRSO)漏洞(CVE-2023-20569)的内核缓解机制:如何解读 sysfs 中的缓解状态、如何通过 spec_rstack_overflow 内核命令行参数选择与切换缓解策略、Safe RET 缓解的底层工作原理,以及如何用性能计数器和官方 selftest 验证缓解确实生效。读完本文,你将能独立完成 AMD Zen1~Zen4 平台上 SRSO 漏洞的检测、缓解配置与生效性验证。


SRSO 漏洞是什么

SRSO 是 2023 年披露的、影响 AMD 处理器的一类推测执行(speculative execution)侧信道漏洞,在 Linux 内核中由 arch/x86/kernel/cpu/bugs.c 的相应逻辑负责检测与缓解,官方追踪编号为 CVE-2023-20569

其攻击形态与业界熟知的 Spectre/Meltdown/Retbleed 同属一类:攻击者先污染 CPU 的功能单元——此处是分支目标缓冲(Branch Target Buffer,BTB)返回地址预测器(Return Address Predictor,RAP)——再诱导高特权域(内核)把敏感数据泄漏出来。

AMD 处理器使用 RAP(也称 Return Address Stack / Return Stack Buffer,RSB)来预测 RET 指令的目标。问题在于:某些情况下,一条非架构性 CALL 指令(即 CPU 预测它是 CALL、但实际并非 CALL 的指令)也会在 RAP 中创建条目,而这个条目随后可能被用来预测后续某条 RET 指令的目标。

触发这种状态的具体微架构条件随代际而不同,但核心风险是一致的:攻击者可以"错训"(mis-train)内核空间的 BTB,使其把非架构性 CALL 预测到攻击者控制的地址上,从而劫持某条内核 RET 指令的推测目标,进而通过推测式侧信道实现信息泄露。从源码结构看,内核把这套攻击向量抽象为 User→Kernel、Guest→Host、User→User、Guest→Guest 四类,缓解策略的选择正是围绕这些权限域穿越场景展开的。

受影响处理器

处理器世代 Family 备注
AMD Zen 1 0x17 受影响
AMD Zen 2 0x17 受影响
AMD Zen 3 0x19 受影响
AMD Zen 4 0x19 受影响

所有 Family 0x17 与 0x19(Zen 1 至 Zen 4 全部代际)均受影响,更老型号的处理器尚未被调查确认。内核代码中以 boot_cpu_has_bug(X86_BUG_SRSO) 判定当前 CPU 是否带该漏洞(参见 arch/x86/kernel/cpu/bugs.c),而 selftest 则通过 CPUID(1).EAX 是否落在 0x00800f00~0x00afffff 区间来判断运行环境是否属于 Zen[1-4],见 tools/testing/selftests/x86/srso.c


检测系统信息与读取缓解状态

要让缓解真正生效,首先必须为系统加载包含 SRSO 修复的最新微码(microcode)。这是所有后续缓解措施生效的前提条件。

内核启动后,可通过以下 sysfs 文件查看 SRSO 缓解状态:

/sys/devices/system/cpu/vulnerabilities/spec_rstack_overflow

该文件的实际内容由内核在 arch/x86/kernel/cpu/bugs.csrso_show_state() 中写入:若 CPU 不含 X86_BUG_SRSO 漏洞,则直接输出 Not affected同一文件第 L3610 行)。展示函数 cpu_show_spec_rstack_overflow() 定义在 arch/x86/kernel/cpu/bugs.c,字符串表 srso_strings[] 位于 arch/x86/kernel/cpu/bugs.c

sysfs 文件中的可能取值

以下取值与文档描述对应(个别取值在本仓库内核版本中的实际字符串略有差异,已用"实际为"标注):

sysfs 输出 含义
Not affected 处理器不受该漏洞影响(不携带 X86_BUG_SRSO)。
Vulnerable 处理器存在漏洞,且未应用任何缓解
Vulnerable: No microcode 处理器存在漏洞,但尚未加载可扩展 IBPB 功能以解决该漏洞的微码。
Vulnerable: Safe RET, no microcode 内核已应用 "Safe RET" 缓解,但未加载扩展 IBPB 的微码;用户态任务可能仍处于易受攻击状态。
Vulnerable: Microcode, no safe RET 已应用扩展 IBPB 功能的微码补丁,但未解决 User→Kernel 与 Guest→Host 穿越保护;它只能覆盖 User→User 与 VM→VM 攻击向量。(对应 spec_rstack_overflow=microcode
Mitigation: Safe RET 结合微码与软件的完整缓解,补齐了 User→Kernel 与 Guest→Host 穿越保护;默认选择,也可通过 spec_rstack_overflow=safe-ret 显式指定。
Mitigation: IBPB 与 Safe RET 保护范围类似,但在权限域穿越(User→Kernel、Guest→Host)时执行一次 IBPB 屏障。(对应 spec_rstack_overflow=ibpb
Mitigation: IBPB on VMEXIT(本仓库实际为 Mitigation: IBPB on VMEXIT only 仅针对云服务商场景的 Guest→Host 穿越。(对应 spec_rstack_overflow=ibpb-vmexit
Mitigation: Reduced Speculation 见下文 BpSpecReduce 说明。

说明:本仓库中 arch/x86/kernel/cpu/bugs.csrso_strings[] 还额外定义了一个文档中未单列的取值 Mitigation: SMT disabled(对应 SRSO_MITIGATION_NOSMT)——当处理器为 Zen1/Zen2 且系统不可能开启 SMT(cpu_smt_possible() 为假)时自动选用,因为此类配置下不再受 SRSO 影响。

Reduced Speculation(BpSpecReduce)的自动启用逻辑

"Reduced Speculation" 属于一种自动叠加生效的缓解,分两种情况:

  1. 当选中上述 "IBPB on VMEXIT" 且 CPU 支持 BpSpecReduce 位时,会进一步自动升级为 "Reduced Speculation"。BpSpecReduce 相比 IBPB on VMEXIT 性能开销小得多,同时仍能覆盖 guest→host 攻击向量。
  2. 当 CPU 具备 SRSO_USER_KERNEL_NO=1 的 CPUID 位时也会自动启用。此时说明 user/kernel 边界已不再受影响,Safe RET 不再必要,代码逻辑会自动切到 =ibpb-vmexit 一类缓解。

内核中的这段自动决策逻辑实现在 arch/x86/kernel/cpu/bugs.cswitch 分支中:一旦选中 SRSO_MITIGATION_IBPB_ON_VMEXITboot_cpu_has(X86_FEATURE_SRSO_BP_SPEC_REDUCE) 为真,就打印 "Reducing speculation to address VM/HV SRSO attack vector." 并切到 SRSO_MITIGATION_BP_SPEC_REDUCE。该 feature 位同时控制 KVM 中 BpSpecReduce MSR 位的切换(见 arch/x86/kernel/cpu/bugs.c 的注释与 srso_apply_mitigation())。

与 Spectre v2 用户态策略的联动

需要注意,User→User 缓解强度由 Spectre v2 缓解中的 IBPB 环节如何配置决定:

  • conditional IBPB(条件式):每个进程可通过 PR_SPEC_DISABLE/PR_SPEC_ENABLE 等 prctl 自行决定是否需要在其周边执行 IBPB,参见 Spectre v2 文档
  • strict(严格模式,总是开启):在内核命令行提供 spectre_v2_user=on

缓解选项与内核命令行配置

SRSO 的缓解策略通过早参数(early param)spec_rstack_overflow 配置。解析代码位于 arch/x86/kernel/cpu/bugs.csrso_parse_cmdline(),所有取值在启动早期即被记录到静态变量 srso_mitigation 中:

内核命令行参数 对应缓解 说明
spec_rstack_overflow=off 无(Vulnerable 显式关闭缓解。
spec_rstack_overflow=microcode 仅微码 依赖扩展 IBPB 功能的微码,覆盖 User→User、VM→VM 向量。
spec_rstack_overflow=safe-ret Safe RET 软件 + 微码的完整缓解,默认值
spec_rstack_overflow=ibpb IBPB 在权限域穿越点执行 IBPB 的完整缓解。
spec_rstack_overflow=ibpb-vmexit IBPB on VMEXIT 面向云服务商,只覆盖 Guest→Host。

传入未知参数时内核会打印 Ignoring unknown SRSO option 并忽略之(见 arch/x86/kernel/cpu/bugs.c)。

默认策略的自动选择(源码视角)

若用户未显式指定(状态为 SRSO_MITIGATION_AUTO),内核在 srso_select_mitigation()arch/x86/kernel/cpu/bugs.c)中按下述逻辑裁决:

  1. CPU 无 X86_BUG_SRSONONE,直接返回。
  2. 若需要保护 Guest→Host,或需要保护 User→Kernel 且 CPU 不具备 SRSO_USER_KERNEL_NO 位 → 选 Safe RET
  3. 否则若只需保护 User→User 或 Guest→Guest → 选 microcode 缓解;
  4. 都不需要 → NONE
  5. 若 CPU 是 Zen1/Zen2 且 SMT 不可能开启 → 直接返回 NOSMT("SMT disabled"),因为这些处理器在 SMT 关闭时本就不受 SRSO 影响。
  6. 检查扩展 IBPB 功能的微码是否就位:缺少 X86_FEATURE_IBPB_BRTYPE 时打印 IBPB-extending microcode not applied! 并降级为 UCODE_NEEDED / SAFE_RET_UCODE_NEEDED(Safe RET 无需微码也能提供部分缓解)。
  7. 依据编译配置兜底检查:Safe RET 要求内核编译时开启 CONFIG_MITIGATION_SRSO;IBPB/IBPB on VMEXIT 要求开启 CONFIG_MITIGATION_IBPB_ENTRY,否则强制 NONE 并打印对应告警。

此外,srso_update_mitigation()arch/x86/kernel/cpu/bugs.c)还处理一个联动场景:若 Retbleed 缓解已选用 IBPB,且系统具备 X86_FEATURE_IBPB_BRTYPE 微码,则 Retbleed 的 IBPB 对 SRSO 同样有效,SRSO 会自动升级为 Mitigation: IBPB

性能权衡与默认选择建议

考虑到各类缓解的性能影响,默认选择是 Safe RETMitigation: Safe RET),它应能覆盖绝大多数攻击向量,包括本地的 User→Kernel 场景。默认设置在出现新攻击向量时会重新评估。

必须坦率指出:Safe RET 会依据工作负载带来一定的性能开销。如果系统管理员充分信任自己的用户态程序、且不愿承受该性能损失,可以随时用 spec_rstack_overflow=off 关闭缓解。同理,Mitigation: IBPB 是另一种完整缓解,但在穿越点执行间接分支预测屏障同样带来性能代价。

成功利用所需的前提条件

作为运维/安全评估参考,文档明确给出成功利用该漏洞需要满足的条件,这也解释了为何 SRSO 属于"本地、且需要较强前置条件"的漏洞:

  • 获得机器的本地访问权限
  • 攻破 kASLR(内核地址空间布局随机化);
  • 在运行中的内核里找到可用于利用的 gadget
  • 视微架构而定,可能需要在兄弟线程上创建并绑定额外工作负载(Family 0x19 上则不需要);
  • 最后运行利用程序。

深入 Safe RET 缓解的实现原理

Safe RET 是整个缓解体系中最核心的部分。其总体思路是:让所有 RET 指令都推测到一个受控位置——这与 retpoline 序列控制推测的方式类似。具体做法是让 __x86_return_thunk 强制 CPU 对每一个函数返回都"猜错",从而落入一条"安全返回"(safe return)序列。

在内核构建中,编译器通过 -mfunction-return=thunk-extern 把所有函数返回处的 RET 替换为跳转到 __x86_return_thunk(该符号在 arch/x86/lib/retpoline.S 中定义,其注释明确说明该函数名是"magical"的)。真正生效的返回 thunk 由 srso_apply_mitigation() 在启动后期通过 set_return_thunk() 安装(arch/x86/kernel/cpu/bugs.c)。

为了让该缓解本身安全,内核必须保证 safe return 序列自身不受攻击者干扰。由于不同代际的 BTB 结构不同,实现分两种:

Zen3 / Zen4:BTB 别名(aliasing)方案

在 Zen3 与 Zen4 上(Family 0x19,内核判定为 boot_cpu_data.x86 == 0x19),通过构造 BTB 别名来完成"冲刷 + 复用":

  • srso_alias_untrain_ret()(未训化函数)被放置到 2M 对齐的特殊地址;
  • srso_alias_safe_ret()(安全返回函数)与前者处于同一个 2M 页面内,但其虚拟地址的 第 2、8、14、20 位被置位(而 untrain 函数对应位为清零)。

这两个地址因此在 Zen3/Zen4 的 BTB 中必然互为别名:执行 untrain 会驱逐该 BTB 槽位上任何可能被污染的条目,于是随后所有函数返回都命中这一个干净的 safe ret 槽位。相关汇编与地址约束注释见 arch/x86/lib/retpoline.S,实际安装的是 srso_alias_return_thunk

Zen1 / Zen2:重解释(reinterpretation)方案

在更老的 Zen1 与 Zen2(Family 0x17)上,采用与 Retbleed 缓解类似的"重解释"手法,对应符号为 srso_untrain_ret()srso_safe_ret()。其关键技巧是(见 arch/x86/lib/retpoline.S):

  • srso_untrain_ret() 本质上是一条被"重新解释"的指令——一条 movabs $0xccccc30824648d48,%rax 的字节序列被精心摆放:当返回 thunk 稍后执行到内部标签 srso_safe_ret() 时,它表现为栈指针调整 + 一条 RET
  • 这条 RET 会被 CPU 预测错(mispredict)而撞进陷阱指令(UD2),随后实际执行流沿真实栈顶的返回地址继续——这正是"安全返回"的含义(retpoline.S 第 L255-L260 行注释)。

无论哪种方案,srso_return_thunk / srso_alias_return_thunk 的公共形态都是 call <safe_ret>; ud2:正常路径不会真正执行到该返回点,推测执行则被引导到陷阱。

若内核启动时相关编译配置(CONFIG_MITIGATION_SRSO)缺失,Safe RET 无法应用,内核会打印 WARNING: kernel not compiled with MITIGATION_SRSO. 并把状态置为 Vulnerable


验证 Safe RET 缓解确实生效

由于 Safe RET 的设计目标是"让内核态每条 RET 都推测错误",这天然提供了一个可观测的验证方法:用两个性能计数器对比内核态"已退休的 RET 数"与"已退休且预测错误的 RET 数"。

  • PMC 0xc8:退休的 RET/RET lw(near return)计数;
  • PMC 0xc9:退休且预测错误的 RET/RET lw 计数。

在 Intel/AMD 平台上也可用 perf list 查看预定义事件名:

# perf list ex_ret_near_ret

其输出应包含:

List of pre-defined events (to be used in -e or -M):

core:
  ex_ret_near_ret
       [Retired Near Returns]
  ex_ret_near_ret_mispred
       [Retired Near Returns Mispredicted]

随后可任选以下两种方式之一采样内核态(:k 修饰符)的返回指令行为:

# 方式一:使用事件助记符
perf stat -e ex_ret_near_ret:k -e ex_ret_near_ret_mispred:k sleep 10s

# 方式二:使用原始 PMC 编号
perf stat -e cpu/event=0xc8,umask=0/k -e cpu/event=0xc9,umask=0/k sleep 10s

缓解生效时:两者几乎相等

当 Safe RET 正常工作时,每一条退休的 RET 都应是被预测错误的,即 0xc8 ≈ 0xc9。文档给出的真实采样结果如下:

Performance counter stats for 'sleep 10s':

         137,167      cpu/event=0xc8,umask=0/k
         137,173      cpu/event=0xc9,umask=0/k

   10.004110303 seconds time elapsed

     0.000000000 seconds user
     0.004462000 seconds sys

可以看到两个计数几乎完全一致(137,167 vs 137,173)。

缓解未生效 / 被关闭时:两者差距悬殊

对比当缓解被禁用(spec_rstack_overflow=off)或未能正常工作时,同样的 10 秒采样窗口内,预测错误的退休 RET 数量只占极小比例

Performance counter stats for 'sleep 10s':

         201,627      cpu/event=0xc8,umask=0/k
           4,074      cpu/event=0xc9,umask=0/k

   10.003267252 seconds time elapsed

     0.002729000 seconds user
     0.000000000 seconds sys

201,627 条退休 RET 中仅有 4,074 条预测错误,说明绝大多数 RET 都被 BTB/RAP 正常预测——此时 Safe RET 未在起作用。

官方 selftest:一键验证

内核还附带了一个完成上述验证的 selftest。进入 tools/testing/selftests/x86/ 目录执行:

make srso
./srso

该程序 tools/testing/selftests/x86/srso.c 的内部逻辑与上文手工流程完全一致:

  1. 先通过 __cpuid(1, ...) 检查 CPUID,仅在 Zen[1-4](CPUID(1).EAX 处于 0x00800f00~0x00afffff)上继续运行,否则直接退出(第 L19-L25 行);
  2. syscall(SYS_perf_event_open, ...)PERF_TYPE_RAW 打开 0xc8(RET 退休)与 0xc9(RET 预测错误)两个计数器,并设置 exclude_user = exclude_hv = 1,即只统计内核态第 L27-L48 行);
  3. 通过 ioctl 复位并启用计数器,sleep(10) 后停表读数(第 L50-L63 行);
  4. 打印两个计数值,并给出结论:当两个计数几乎相等时,SRSO Safe-RET 缓解工作正常第 L65-L68 行)。
Sleeping for 10 seconds
RETs: (137167 retired <-> 137173 mispredicted)
SRSO Safe-RET mitigation works correctly if both counts are almost equal.

实战排查速查表

目标 手段
查看当前 SRSO 缓解状态 cat /sys/devices/system/cpu/vulnerabilities/spec_rstack_overflow
内核态 RET 计数采样 perf stat -e cpu/event=0xc8,umask=0/k -e cpu/event=0xc9,umask=0/k sleep 10s
一键验证 Safe RET 进入 tools/testing/selftests/x86/ 执行 make srso && ./srso
显式指定缓解策略 内核命令行追加 spec_rstack_overflow=safe-ret / ibpb / ibpb-vmexit / microcode / off
彻底关闭缓解(不推荐) spec_rstack_overflow=off

关键源码索引

主题 位置
SRSO 缓解状态字符串表 arch/x86/kernel/cpu/bugs.c
命令行参数解析 arch/x86/kernel/cpu/bugs.c
缓解自动选择逻辑 arch/x86/kernel/cpu/bugs.c
缓解应用与 thunk 安装 arch/x86/kernel/cpu/bugs.c
sysfs 展示入口 arch/x86/kernel/cpu/bugs.carch/x86/kernel/cpu/bugs.c
Zen3/4 alias 与 Zen1/2 重解释汇编序列 arch/x86/lib/retpoline.S
编译器替换目标 __x86_return_thunk arch/x86/lib/retpoline.S
官方验证 selftest tools/testing/selftests/x86/srso.c
相关参考:Spectre v2(IBPB/PR_SPEC 联动) Documentation/admin-guide/hw-vuln/spectre.rst

最后仍需强调文档末尾的建议:像对待所有推测执行类漏洞一样,及时且持续地应用系统软件更新是维持有效防护的基础;SRSO 缓解策略的默认选择(Safe RET)也会随新攻击向量的出现而被内核开发者重新评估与调整,因此保持内核与微码同步更新至关重要。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
594
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
916
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
516
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388