spectre_v2启动参数
by JiangBiao
背景
针对Meltdown && Spectre安全漏洞的V2 patch中引入了retpoline技术,启动参数也变了,主要通过spectre_v2控制,但是可选的参数众多,有点眼晕,本文分析相关原理和实现代码。
参数说明
内核文档:kernel-parameters.txt中针对spectre_v2参数有如下说明:
spectre_v2= [X86] Control mitigation of Spectre variant 2 (indirect branch speculation) vulnerability.
on - unconditionally enable
off - unconditionally disable
auto - kernel detects whether your CPU model is vulnerable
Selecting 'on' will, and 'auto' may, choose a
mitigation method at run time according to the
CPU, the available microcode, the setting of the
CONFIG_RETPOLINE configuration option, and the
compiler with which the kernel was built.
Specific mitigations can also be selected manually:
retpoline - replace indirect branches
retpoline,generic - google's original retpoline
retpoline,amd - AMD-specific minimal thunk
Not specifying this option is equivalent to
spectre_v2=auto.
其实说的蛮清楚,但是就是有点复杂。我总结一下关键点:
- on,表示无论硬件如何,都开启。
- off,表示物理硬件如何,都关闭。
- auto,表示内核根据当前CPU model自动选择mitigation方法。参考的因素包括
- cpu model
- 是否有可用微码(也就是针对IBRS和IBPB提供的固件)
- 内核配置选项CONFIG_RETPOLINE是否配置
- 编译内核用的编译器是否提供了新的编译选项:-mindirect-branch=thunk-extern 情况比较复杂。
- retpoline/retpoline,generic/retpoline,amd。这几种就让人有点眼晕了,啥?retpoline还有3种?
事实上要比你看到的还要晕。上述内核文档中对这几种参数的说明也比较糢糊,不容易理解,需要实际看看代码。
代码分析
相关的主要代码都在spectre_v2_select_mitigation()函数中:
// 通过传入的内核启动参数,结合硬件以及相关配置,设置最终的migigation mode
static void __init spectre_v2_select_mitigation(void)
{
// 解析cmdline
enum spectre_v2_mitigation_cmd cmd = spectre_v2_parse_cmdline();
// 默认mode 为 SPECTRE_V2_NONE
enum spectre_v2_mitigation mode = SPECTRE_V2_NONE;
/*
* If the CPU is not affected and the command line mode is NONE or AUTO
* then nothing to do.
*/
// 如果CPU不受Spectre_V2影响,则不处理,即mode为默认SPECTRE_V2_NONE
if (!boot_cpu_has_bug(X86_BUG_SPECTRE_V2) &&
(cmd == SPECTRE_V2_CMD_NONE || cmd == SPECTRE_V2_CMD_AUTO)) {
pr_info("Not affected\n");
return;
}
// 判断参数
switch (cmd) {
// spectre_v2=off
case SPECTRE_V2_CMD_NONE:
return;
// spectre_v2=on
case SPECTRE_V2_CMD_FORCE:
// spectre_v2=auto或者没有设置
case SPECTRE_V2_CMD_AUTO:
// 如果配置了内核选项CONFIG_RETPOLINE,则进入auto
if (IS_ENABLED(CONFIG_RETPOLINE))
goto retpoline_auto;
break;
// spectre_v2=retpoline,amd
case SPECTRE_V2_CMD_RETPOLINE_AMD:
if (IS_ENABLED(CONFIG_RETPOLINE))
goto retpoline_amd;
break;
// spectre_v2=retpoline,generic
case SPECTRE_V2_CMD_RETPOLINE_GENERIC:
if (IS_ENABLED(CONFIG_RETPOLINE))
goto retpoline_generic;
break;
// spectre_v2=retpoline, 进入auto,相当于spectre_v2=auto
case SPECTRE_V2_CMD_RETPOLINE:
if (IS_ENABLED(CONFIG_RETPOLINE))
goto retpoline_auto;
break;
}
pr_err("Mitigation: kernel not compiled with retpoline; no mitigation available!");
return;
// auto方式,根据不同的条件,决定最后的mode
retpoline_auto:
// 如果是AMD CPU
if (boot_cpu_data.x86_vendor == X86_VENDOR_AMD) {
retpoline_amd:
// AMD主要通过硬件提供的lfence来实现repoline,如果没有,则使用传统的方式(AMD也可以使用传统方式哦)
if (!boot_cpu_has(X86_FEATURE_LFENCE_RDTSC)) {
pr_err("Mitigation: LFENCE not serializing, switching to generic retpoline\n");
goto retpoline_generic;
}
/*
* 判断当前编译器是否支持-mindirect-branch=thunk-extern选项,
* 如果支持,则mode为SPECTRE_V2_RETPOLINE_AMD,表示内核中C代码和汇编代码中包含CALL/JMP指令
* 都经过了封装,采用retpoline方式。
* 如果不支持,则mode为SPECTRE_V2_RETPOLINE_MINIMAL_AMD,表示只有汇编代码中的CALL/JMP指令
* 经过了封装,采用retpoline方式,C代码中由于没有编译器支持,不能使用retpoline方式封装。
*/
mode = retp_compiler() ? SPECTRE_V2_RETPOLINE_AMD :
SPECTRE_V2_RETPOLINE_MINIMAL_AMD;
setup_force_cpu_cap(X86_FEATURE_RETPOLINE_AMD);
setup_force_cpu_cap(X86_FEATURE_RETPOLINE);
} else {// 如果不是AMD CPU
retpoline_generic:
// 与上面类似,分为MINIMAL和普通两种方式,取决于编译器是否支持新选项
mode = retp_compiler() ? SPECTRE_V2_RETPOLINE_GENERIC :
SPECTRE_V2_RETPOLINE_MINIMAL;
setup_force_cpu_cap(X86_FEATURE_RETPOLINE);
}
// 设置最终mode
spectre_v2_enabled = mode;
pr_info("%s\n", spectre_v2_strings[mode]);
/*
* If neither SMEP nor PTI are available, there is a risk of
* hitting userspace addresses in the RSB after a context switch
* from a shallow call stack to a deeper one. To prevent this fill
* the entire RSB, even when using IBRS.
*
* Skylake era CPUs have a separate issue with *underflow* of the
* RSB, when they will predict 'ret' targets from the generic BTB.
* The proper mitigation for this is IBRS. If IBRS is not supported
* or deactivated in favour of retpolines the RSB fill on context
* switch is required.
*/
if ((!boot_cpu_has(X86_FEATURE_PTI) &&
!boot_cpu_has(X86_FEATURE_SMEP)) || is_skylake_era()) {
setup_force_cpu_cap(X86_FEATURE_RSB_CTXSW);
pr_info("Mitigation: Filling RSB on context switch\n");
}
/* Initialize Indirect Branch Prediction Barrier if supported */
// 如果CPU支持IBPU,这设置相关feature
if (boot_cpu_has(X86_FEATURE_IBPB)) {
setup_force_cpu_cap(X86_FEATURE_USE_IBPB);
pr_info("Mitigation: Enabling Indirect Branch Prediction Barrier\n");
}
/*
* Retpoline means the kernel is safe because it has no indirect
* branches. But firmware isn't, so use IBRS to protect that.
*/
// 针对firmware设置IBRS,仅在firmware中使用。内核中优先使用retpoline
if (boot_cpu_has(X86_FEATURE_IBRS)) {
setup_force_cpu_cap(X86_FEATURE_USE_IBRS_FW);
pr_info("Enabling Restricted Speculation for firmware calls\n");
}
}
从代码可以看出,内核启动时传入的参数并非直接对应最终的mode,之间有一定的“换算”关系,先看看入参类型的定义:
/* The kernel command line selection */
enum spectre_v2_mitigation_cmd {
SPECTRE_V2_CMD_NONE,
SPECTRE_V2_CMD_AUTO,
SPECTRE_V2_CMD_FORCE,
SPECTRE_V2_CMD_RETPOLINE,
SPECTRE_V2_CMD_RETPOLINE_GENERIC,
SPECTRE_V2_CMD_RETPOLINE_AMD,
};
static const struct {
const char *option;
enum spectre_v2_mitigation_cmd cmd;
bool secure;
} mitigation_options[] = {
{ "off", SPECTRE_V2_CMD_NONE, false },
{ "on", SPECTRE_V2_CMD_FORCE, true },
{ "retpoline", SPECTRE_V2_CMD_RETPOLINE, false },
{ "retpoline,amd", SPECTRE_V2_CMD_RETPOLINE_AMD, false },
{ "retpoline,generic", SPECTRE_V2_CMD_RETPOLINE_GENERIC, false },
{ "auto", SPECTRE_V2_CMD_AUTO, false },
};
这些分别对应了上述内核文档中说明的参数。共有6种情况。
再看看mode的定义:
/* The Spectre V2 mitigation variants */
enum spectre_v2_mitigation {
SPECTRE_V2_NONE,
SPECTRE_V2_RETPOLINE_MINIMAL,
SPECTRE_V2_RETPOLINE_MINIMAL_AMD,
SPECTRE_V2_RETPOLINE_GENERIC,
SPECTRE_V2_RETPOLINE_AMD,
SPECTRE_V2_IBRS,
};
mode也有6种情况,但他们之间却不是一一对应关系。根据前面的代码分析,我们可以总结下他们之间的对应关系。
参数与mode的对应关系
-
off –> SPECTRE_V2_NONE
- on,force,retpoline,这3个参数,从文档上看,看似各不相同,但是从代码实现上看,都是一样的。最终对应哪个mode,分5种情况:
- if (!CONFIG_RETPOLINE) –> SPECTRE_V2_NONE
- if (AMD CPU && 支持lfence && 新GCC) –> SPECTRE_V2_RETPOLINE_AMD
- if (AMD CPU && 支持lfence && 旧GCC) –> SPECTRE_V2_RETPOLINE_MINIMAL_AMD
-
if ((INTEL CPU 不支持lfence) && 新GCC) –> SPECTRE_V2_RETPOLINE_GENERIC -
if ((INTEL CPU 不支持lfence) && 旧GCC) –> SPECTRE_V2_RETPOLINE_MINIMAL
- retpoline,generic,分2种情况
- if (新GCC) –> SPECTRE_V2_RETPOLINE_GENERIC
- if (旧GCC) –> SPECTRE_V2_RETPOLINE_MINIMAL
- retpoline,amd,分4种情况:
- if (支持lfence && 新GCC) –> SPECTRE_V2_RETPOLINE_AMD
- if (支持lfence && 旧GCC) –> SPECTRE_V2_RETPOLINE_MINIMAL_AMD
- if (不支持lfence && 新GCC) –> SPECTRE_V2_RETPOLINE_GENERIC
- if (不支持lfence && 旧GCC) –> SPECTRE_V2_RETPOLINE_MINIMAL
这部分逻辑其实比较混乱,有不少的优化空间~
Subscribe via RSS