linuxfreebsdkernelnetbsdspectredragonflybsdmitigationmeltdowncve-2017-5754cve-2017-5715cve-2017-5753cve-2018-3640cve-2018-3639foreshadowcve-2018-3615cve-2018-3620cve-2018-3646zombieloadcve-2019-11135
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
5946 lines
214 KiB
5946 lines
214 KiB
#! /bin/sh |
|
# SPDX-License-Identifier: GPL-3.0-only |
|
# vim: set ts=8 sw=8 sts=4 noet: |
|
# |
|
# Spectre & Meltdown checker |
|
# |
|
# Check for the latest version at: |
|
# https://github.com/speed47/spectre-meltdown-checker |
|
# git clone https://github.com/speed47/spectre-meltdown-checker.git |
|
# or wget https://meltdown.ovh -O spectre-meltdown-checker.sh |
|
# or curl -L https://meltdown.ovh -o spectre-meltdown-checker.sh |
|
# |
|
# Stephane Lesimple |
|
# |
|
VERSION='0.43' |
|
|
|
trap 'exit_cleanup' EXIT |
|
trap '_warn "interrupted, cleaning up..."; exit_cleanup; exit 1' INT |
|
exit_cleanup() |
|
{ |
|
# cleanup the temp decompressed config & kernel image |
|
[ -n "$dumped_config" ] && [ -f "$dumped_config" ] && rm -f "$dumped_config" |
|
[ -n "$kerneltmp" ] && [ -f "$kerneltmp" ] && rm -f "$kerneltmp" |
|
[ -n "$kerneltmp2" ] && [ -f "$kerneltmp2" ] && rm -f "$kerneltmp2" |
|
[ -n "$mcedb_tmp" ] && [ -f "$mcedb_tmp" ] && rm -f "$mcedb_tmp" |
|
[ -n "$intel_tmp" ] && [ -d "$intel_tmp" ] && rm -rf "$intel_tmp" |
|
[ "$mounted_debugfs" = 1 ] && umount /sys/kernel/debug 2>/dev/null |
|
[ "$mounted_procfs" = 1 ] && umount "$procfs" 2>/dev/null |
|
[ "$insmod_cpuid" = 1 ] && rmmod cpuid 2>/dev/null |
|
[ "$insmod_msr" = 1 ] && rmmod msr 2>/dev/null |
|
[ "$kldload_cpuctl" = 1 ] && kldunload cpuctl 2>/dev/null |
|
[ "$kldload_vmm" = 1 ] && kldunload vmm 2>/dev/null |
|
} |
|
|
|
# if we were git clone'd, adjust VERSION |
|
if [ -d "$(dirname "$0")/.git" ] && command -v git >/dev/null 2>&1; then |
|
describe=$(git -C "$(dirname "$0")" describe --tags --dirty 2>/dev/null) |
|
[ -n "$describe" ] && VERSION=$(echo "$describe" | sed -e s/^v//) |
|
fi |
|
|
|
show_usage() |
|
{ |
|
# shellcheck disable=SC2086 |
|
cat <<EOF |
|
Usage: |
|
Live mode (auto): $(basename $0) [options] |
|
Live mode (manual): $(basename $0) [options] <[--kernel <kimage>] [--config <kconfig>] [--map <mapfile>]> --live |
|
Offline mode: $(basename $0) [options] <[--kernel <kimage>] [--config <kconfig>] [--map <mapfile>]> |
|
|
|
Modes: |
|
Two modes are available. |
|
|
|
First mode is the "live" mode (default), it does its best to find information about the currently running kernel. |
|
To run under this mode, just start the script without any option (you can also use --live explicitly) |
|
|
|
Second mode is the "offline" mode, where you can inspect a non-running kernel. |
|
This mode is automatically enabled when you specify the location of the kernel file, config and System.map files: |
|
|
|
--kernel kernel_file specify a (possibly compressed) Linux or BSD kernel file |
|
--config kernel_config specify a kernel config file (Linux only) |
|
--map kernel_map_file specify a kernel System.map file (Linux only) |
|
|
|
If you want to use live mode while specifying the location of the kernel, config or map file yourself, |
|
you can add --live to the above options, to tell the script to run in live mode instead of the offline mode, |
|
which is enabled by default when at least one file is specified on the command line. |
|
|
|
Options: |
|
--no-color don't use color codes |
|
--verbose, -v increase verbosity level, possibly several times |
|
--explain produce an additional human-readable explanation of actions to take to mitigate a vulnerability |
|
--paranoid require IBPB to deem Variant 2 as mitigated |
|
also require SMT disabled + unconditional L1D flush to deem Foreshadow-NG VMM as mitigated |
|
also require SMT disabled to deem MDS vulnerabilities mitigated |
|
|
|
--no-sysfs don't use the /sys interface even if present [Linux] |
|
--sysfs-only only use the /sys interface, don't run our own checks [Linux] |
|
--coreos special mode for CoreOS (use an ephemeral toolbox to inspect kernel) [Linux] |
|
|
|
--arch-prefix PREFIX specify a prefix for cross-inspecting a kernel of a different arch, for example "aarch64-linux-gnu-", |
|
so that invoked tools will be prefixed with this (i.e. aarch64-linux-gnu-objdump) |
|
--batch text produce machine readable output, this is the default if --batch is specified alone |
|
--batch short produce only one line with the vulnerabilities separated by spaces |
|
--batch json produce JSON output formatted for Puppet, Ansible, Chef... |
|
--batch nrpe produce machine readable output formatted for NRPE |
|
--batch prometheus produce output for consumption by prometheus-node-exporter |
|
|
|
--variant VARIANT specify which variant you'd like to check, by default all variants are checked |
|
VARIANT can be one of 1, 2, 3, 3a, 4, l1tf, msbds, mfbds, mlpds, mdsum, taa, mcepsc, srbds |
|
can be specified multiple times (e.g. --variant 2 --variant 3) |
|
--cve [cve1,cve2,...] specify which CVE you'd like to check, by default all supported CVEs are checked |
|
--hw-only only check for CPU information, don't check for any variant |
|
--no-hw skip CPU information and checks, if you're inspecting a kernel not to be run on this host |
|
--vmm [auto,yes,no] override the detection of the presence of a hypervisor, default: auto |
|
--update-fwdb update our local copy of the CPU microcodes versions database (using the awesome |
|
MCExtractor project and the Intel firmwares GitHub repository) |
|
--update-builtin-fwdb same as --update-fwdb but update builtin DB inside the script itself |
|
--dump-mock-data used to mimick a CPU on an other system, mainly used to help debugging this script |
|
|
|
Return codes: |
|
0 (not vulnerable), 2 (vulnerable), 3 (unknown), 255 (error) |
|
|
|
IMPORTANT: |
|
A false sense of security is worse than no security at all. |
|
Please use the --disclaimer option to understand exactly what this script does. |
|
|
|
EOF |
|
} |
|
|
|
show_disclaimer() |
|
{ |
|
cat <<EOF |
|
Disclaimer: |
|
|
|
This tool does its best to determine whether your system is immune (or has proper mitigations in place) for the |
|
collectively named "speculative execution" vulnerabilities. It doesn't attempt to run any kind of exploit, and can't guarantee |
|
that your system is secure, but rather helps you verifying whether your system has the known correct mitigations in place. |
|
However, some mitigations could also exist in your kernel that this script doesn't know (yet) how to detect, or it might |
|
falsely detect mitigations that in the end don't work as expected (for example, on backported or modified kernels). |
|
|
|
Your system exposure also depends on your CPU. As of now, AMD and ARM processors are marked as immune to some or all of these |
|
vulnerabilities (except some specific ARM models). All Intel processors manufactured since circa 1995 are thought to be vulnerable, |
|
except some specific/old models, such as some early Atoms. Whatever processor one uses, one might seek more information |
|
from the manufacturer of that processor and/or of the device in which it runs. |
|
|
|
The nature of the discovered vulnerabilities being quite new, the landscape of vulnerable processors can be expected |
|
to change over time, which is why this script makes the assumption that all CPUs are vulnerable, except if the manufacturer |
|
explicitly stated otherwise in a verifiable public announcement. |
|
|
|
Please also note that for Spectre vulnerabilities, all software can possibly be exploited, this tool only verifies that the |
|
kernel (which is the core of the system) you're using has the proper protections in place. Verifying all the other software |
|
is out of the scope of this tool. As a general measure, ensure you always have the most up to date stable versions of all |
|
the software you use, especially for those who are exposed to the world, such as network daemons and browsers. |
|
|
|
This tool has been released in the hope that it'll be useful, but don't use it to jump to conclusions about your security. |
|
|
|
EOF |
|
} |
|
|
|
os=$(uname -s) |
|
|
|
# parse options |
|
opt_kernel='' |
|
opt_config='' |
|
opt_map='' |
|
opt_live=-1 |
|
opt_no_color=0 |
|
opt_batch=0 |
|
opt_batch_format='text' |
|
opt_verbose=1 |
|
opt_cve_list='' |
|
opt_cve_all=1 |
|
opt_no_sysfs=0 |
|
opt_sysfs_only=0 |
|
opt_coreos=0 |
|
opt_arch_prefix='' |
|
opt_hw_only=0 |
|
opt_no_hw=0 |
|
opt_vmm=-1 |
|
opt_explain=0 |
|
opt_paranoid=0 |
|
opt_mock=0 |
|
|
|
global_critical=0 |
|
global_unknown=0 |
|
nrpe_vuln='' |
|
|
|
supported_cve_list='CVE-2017-5753 CVE-2017-5715 CVE-2017-5754 CVE-2018-3640 CVE-2018-3639 CVE-2018-3615 CVE-2018-3620 CVE-2018-3646 CVE-2018-12126 CVE-2018-12130 CVE-2018-12127 CVE-2019-11091 CVE-2019-11135 CVE-2018-12207 CVE-2020-0543' |
|
|
|
# find a sane command to print colored messages, we prefer `printf` over `echo` |
|
# because `printf` behavior is more standard across Linux/BSD |
|
# we'll try to avoid using shell builtins that might not take options |
|
echo_cmd_type='echo' |
|
# ignore SC2230 here because `which` ignores builtins while `command -v` doesn't, and |
|
# we don't want builtins here. Even if `which` is not installed, we'll fallback to the |
|
# `echo` builtin anyway, so this is safe. |
|
# shellcheck disable=SC2230 |
|
if command -v printf >/dev/null 2>&1; then |
|
echo_cmd=$(command -v printf) |
|
echo_cmd_type='printf' |
|
elif which echo >/dev/null 2>&1; then |
|
echo_cmd=$(which echo) |
|
else |
|
# maybe the `which` command is broken? |
|
[ -x /bin/echo ] && echo_cmd=/bin/echo |
|
# for Android |
|
[ -x /system/bin/echo ] && echo_cmd=/system/bin/echo |
|
fi |
|
# still empty? fallback to builtin |
|
[ -z "$echo_cmd" ] && echo_cmd='echo' |
|
__echo() |
|
{ |
|
opt="$1" |
|
shift |
|
_msg="$*" |
|
|
|
if [ "$opt_no_color" = 1 ] ; then |
|
# strip ANSI color codes |
|
# some sed versions (i.e. toybox) can't seem to handle |
|
# \033 aka \x1B correctly, so do it for them. |
|
if [ "$echo_cmd_type" = printf ]; then |
|
_interpret_chars='' |
|
else |
|
_interpret_chars='-e' |
|
fi |
|
_ctrlchar=$($echo_cmd $_interpret_chars "\033") |
|
_msg=$($echo_cmd $_interpret_chars "$_msg" | sed -r "s/$_ctrlchar\[([0-9][0-9]?(;[0-9][0-9]?)?)?m//g") |
|
fi |
|
if [ "$echo_cmd_type" = printf ]; then |
|
if [ "$opt" = "-n" ]; then |
|
$echo_cmd "$_msg" |
|
else |
|
$echo_cmd "$_msg\n" |
|
fi |
|
else |
|
# shellcheck disable=SC2086 |
|
$echo_cmd $opt -e "$_msg" |
|
fi |
|
} |
|
|
|
_echo() |
|
{ |
|
if [ "$opt_verbose" -ge "$1" ]; then |
|
shift |
|
__echo '' "$*" |
|
fi |
|
} |
|
|
|
_echo_nol() |
|
{ |
|
if [ "$opt_verbose" -ge "$1" ]; then |
|
shift |
|
__echo -n "$*" |
|
fi |
|
} |
|
|
|
_warn() |
|
{ |
|
_echo 0 "\033[31m$*\033[0m" >&2 |
|
} |
|
|
|
_info() |
|
{ |
|
_echo 1 "$*" |
|
} |
|
|
|
_info_nol() |
|
{ |
|
_echo_nol 1 "$*" |
|
} |
|
|
|
_verbose() |
|
{ |
|
_echo 2 "$*" |
|
} |
|
|
|
_verbose_nol() |
|
{ |
|
_echo_nol 2 "$*" |
|
} |
|
|
|
_debug() |
|
{ |
|
_echo 3 "\033[34m(debug) $*\033[0m" |
|
} |
|
|
|
explain() |
|
{ |
|
if [ "$opt_explain" = 1 ] ; then |
|
_info '' |
|
_info "> \033[41m\033[30mHow to fix:\033[0m $*" |
|
fi |
|
} |
|
|
|
cve2name() |
|
{ |
|
case "$1" in |
|
CVE-2017-5753) echo "Spectre Variant 1, bounds check bypass";; |
|
CVE-2017-5715) echo "Spectre Variant 2, branch target injection";; |
|
CVE-2017-5754) echo "Variant 3, Meltdown, rogue data cache load";; |
|
CVE-2018-3640) echo "Variant 3a, rogue system register read";; |
|
CVE-2018-3639) echo "Variant 4, speculative store bypass";; |
|
CVE-2018-3615) echo "Foreshadow (SGX), L1 terminal fault";; |
|
CVE-2018-3620) echo "Foreshadow-NG (OS), L1 terminal fault";; |
|
CVE-2018-3646) echo "Foreshadow-NG (VMM), L1 terminal fault";; |
|
CVE-2018-12126) echo "Fallout, microarchitectural store buffer data sampling (MSBDS)";; |
|
CVE-2018-12130) echo "ZombieLoad, microarchitectural fill buffer data sampling (MFBDS)";; |
|
CVE-2018-12127) echo "RIDL, microarchitectural load port data sampling (MLPDS)";; |
|
CVE-2019-11091) echo "RIDL, microarchitectural data sampling uncacheable memory (MDSUM)";; |
|
CVE-2019-11135) echo "ZombieLoad V2, TSX Asynchronous Abort (TAA)";; |
|
CVE-2018-12207) echo "No eXcuses, iTLB Multihit, machine check exception on page size changes (MCEPSC)";; |
|
CVE-2020-0543) echo "Special Register Buffer Data Sampling (SRBDS)";; |
|
*) echo "$0: error: invalid CVE '$1' passed to cve2name()" >&2; exit 255;; |
|
esac |
|
} |
|
|
|
is_cpu_vulnerable_cached=0 |
|
_is_cpu_vulnerable_cached() |
|
{ |
|
# shellcheck disable=SC2086 |
|
case "$1" in |
|
CVE-2017-5753) return $variant1;; |
|
CVE-2017-5715) return $variant2;; |
|
CVE-2017-5754) return $variant3;; |
|
CVE-2018-3640) return $variant3a;; |
|
CVE-2018-3639) return $variant4;; |
|
CVE-2018-3615) return $variantl1tf_sgx;; |
|
CVE-2018-3620) return $variantl1tf;; |
|
CVE-2018-3646) return $variantl1tf;; |
|
CVE-2018-12126) return $variant_msbds;; |
|
CVE-2018-12130) return $variant_mfbds;; |
|
CVE-2018-12127) return $variant_mlpds;; |
|
CVE-2019-11091) return $variant_mdsum;; |
|
CVE-2019-11135) return $variant_taa;; |
|
CVE-2018-12207) return $variant_itlbmh;; |
|
CVE-2020-0543) return $variant_srbds;; |
|
*) echo "$0: error: invalid variant '$1' passed to is_cpu_vulnerable()" >&2; exit 255;; |
|
esac |
|
} |
|
|
|
is_cpu_vulnerable() |
|
{ |
|
# param: one of the $supported_cve_list items |
|
# returns 0 if vulnerable, 1 if not vulnerable |
|
# (note that in shell, a return of 0 is success) |
|
# by default, everything is vulnerable, we work in a "whitelist" logic here. |
|
# usage: is_cpu_vulnerable CVE-xxxx-yyyy && do something if vulnerable |
|
if [ "$is_cpu_vulnerable_cached" = 1 ]; then |
|
_is_cpu_vulnerable_cached "$1" |
|
return $? |
|
fi |
|
|
|
variant1='' |
|
variant2='' |
|
variant3='' |
|
variant3a='' |
|
variant4='' |
|
variantl1tf='' |
|
variant_msbds='' |
|
variant_mfbds='' |
|
variant_mlpds='' |
|
variant_mdsum='' |
|
variant_taa='' |
|
variant_itlbmh='' |
|
variant_srbds='' |
|
|
|
if is_cpu_mds_free; then |
|
[ -z "$variant_msbds" ] && variant_msbds=immune |
|
[ -z "$variant_mfbds" ] && variant_mfbds=immune |
|
[ -z "$variant_mlpds" ] && variant_mlpds=immune |
|
[ -z "$variant_mdsum" ] && variant_mdsum=immune |
|
_debug "is_cpu_vulnerable: cpu not affected by Microarchitectural Data Sampling" |
|
fi |
|
|
|
if is_cpu_taa_free; then |
|
[ -z "$variant_taa" ] && variant_taa=immune |
|
_debug "is_cpu_vulnerable: cpu not affected by TSX Asynhronous Abort" |
|
fi |
|
|
|
if is_cpu_srbds_free; then |
|
[ -z "$variant_srbds" ] && variant_srbds=immune |
|
_debug "is_cpu_vulnerable: cpu not affected by Special Register Buffer Data Sampling" |
|
fi |
|
|
|
if is_cpu_specex_free; then |
|
variant1=immune |
|
variant2=immune |
|
variant3=immune |
|
variant3a=immune |
|
variant4=immune |
|
variantl1tf=immune |
|
variant_msbds=immune |
|
variant_mfbds=immune |
|
variant_mlpds=immune |
|
variant_mdsum=immune |
|
variant_taa=immune |
|
variant_srbds=immune |
|
elif is_intel; then |
|
# Intel |
|
# https://github.com/crozone/SpectrePoC/issues/1 ^F E5200 => spectre 2 not vulnerable |
|
# https://github.com/paboldin/meltdown-exploit/issues/19 ^F E5200 => meltdown vulnerable |
|
# model name : Pentium(R) Dual-Core CPU E5200 @ 2.50GHz |
|
if echo "$cpu_friendly_name" | grep -qE 'Pentium\(R\) Dual-Core[[:space:]]+CPU[[:space:]]+E[0-9]{4}K?'; then |
|
variant1=vuln |
|
[ -z "$variant2" ] && variant2=immune |
|
variant3=vuln |
|
fi |
|
if [ "$capabilities_rdcl_no" = 1 ]; then |
|
# capability bit for future Intel processor that will explicitly state |
|
# that they're not vulnerable to Meltdown |
|
# this var is set in check_cpu() |
|
[ -z "$variant3" ] && variant3=immune |
|
[ -z "$variantl1tf" ] && variantl1tf=immune |
|
_debug "is_cpu_vulnerable: RDCL_NO is set so not vuln to meltdown nor l1tf" |
|
fi |
|
if [ "$capabilities_ssb_no" = 1 ]; then |
|
# capability bit for future Intel processor that will explicitly state |
|
# that they're not vulnerable to Variant 4 |
|
# this var is set in check_cpu() |
|
[ -z "$variant4" ] && variant4=immune |
|
_debug "is_cpu_vulnerable: SSB_NO is set so not vuln to variant4" |
|
fi |
|
if is_cpu_ssb_free; then |
|
[ -z "$variant4" ] && variant4=immune |
|
_debug "is_cpu_vulnerable: cpu not affected by speculative store bypass so not vuln to variant4" |
|
fi |
|
# variant 3a |
|
if [ "$cpu_family" = 6 ]; then |
|
if [ "$cpu_model" = "$INTEL_FAM6_XEON_PHI_KNL" ] || [ "$cpu_model" = "$INTEL_FAM6_XEON_PHI_KNM" ]; then |
|
_debug "is_cpu_vulnerable: xeon phi immune to variant 3a" |
|
[ -z "$variant3a" ] && variant3a=immune |
|
elif [ "$cpu_model" = "$INTEL_FAM6_ATOM_SILVERMONT" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_ATOM_SILVERMONT_MID" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_ATOM_SILVERMONT_D" ]; then |
|
# https://www.intel.com/content/www/us/en/security-center/advisory/intel-sa-00115.html |
|
# https://github.com/speed47/spectre-meltdown-checker/issues/310 |
|
# => silvermont CPUs (aka cherry lake for tablets and brawsell for mobile/desktop) don't seem to be vulnerable |
|
# => goldmont ARE vulnerable |
|
_debug "is_cpu_vulnerable: silvermont immune to variant 3a" |
|
[ -z "$variant3a" ] && variant3a=immune |
|
fi |
|
fi |
|
# L1TF (RDCL_NO already checked above) |
|
if [ "$cpu_family" = 6 ]; then |
|
if [ "$cpu_model" = "$INTEL_FAM6_ATOM_SALTWELL" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_ATOM_SALTWELL_TABLET" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_ATOM_SALTWELL_MID" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_ATOM_BONNELL" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_ATOM_BONNELL_MID" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_ATOM_SILVERMONT" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_ATOM_SILVERMONT_MID" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_ATOM_SILVERMONT_D" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_ATOM_AIRMONT" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_ATOM_AIRMONT_MID" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_ATOM_AIRMONT_NP" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_ATOM_GOLDMONT" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_ATOM_GOLDMONT_D" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_ATOM_GOLDMONT_PLUS" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_ATOM_TREMONT_D" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_XEON_PHI_KNL" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_XEON_PHI_KNM" ]; then |
|
|
|
_debug "is_cpu_vulnerable: intel family 6 but model known to be immune to l1tf" |
|
[ -z "$variantl1tf" ] && variantl1tf=immune |
|
else |
|
_debug "is_cpu_vulnerable: intel family 6 is vuln to l1tf" |
|
variantl1tf=vuln |
|
fi |
|
elif [ "$cpu_family" -lt 6 ]; then |
|
_debug "is_cpu_vulnerable: intel family < 6 is immune to l1tf" |
|
[ -z "$variantl1tf" ] && variantl1tf=immune |
|
fi |
|
elif is_amd || is_hygon; then |
|
# AMD revised their statement about variant2 => vulnerable |
|
# https://www.amd.com/en/corporate/speculative-execution |
|
variant1=vuln |
|
variant2=vuln |
|
[ -z "$variant3" ] && variant3=immune |
|
# https://www.amd.com/en/corporate/security-updates |
|
# "We have not identified any AMD x86 products susceptible to the Variant 3a vulnerability in our analysis to-date." |
|
[ -z "$variant3a" ] && variant3a=immune |
|
if is_cpu_ssb_free; then |
|
[ -z "$variant4" ] && variant4=immune |
|
_debug "is_cpu_vulnerable: cpu not affected by speculative store bypass so not vuln to variant4" |
|
fi |
|
variantl1tf=immune |
|
elif [ "$cpu_vendor" = CAVIUM ]; then |
|
variant3=immune |
|
variant3a=immune |
|
variantl1tf=immune |
|
elif [ "$cpu_vendor" = ARM ]; then |
|
# ARM |
|
# reference: https://developer.arm.com/support/security-update |
|
# some devices (phones or other) have several ARMs and as such different part numbers, |
|
# an example is "bigLITTLE". we shouldn't rely on the first CPU only, so we check the whole list |
|
i=0 |
|
for cpupart in $cpu_part_list |
|
do |
|
i=$(( i + 1 )) |
|
# do NOT quote $cpu_arch_list below |
|
# shellcheck disable=SC2086 |
|
cpuarch=$(echo $cpu_arch_list | awk '{ print $'$i' }') |
|
_debug "checking cpu$i: <$cpupart> <$cpuarch>" |
|
# some kernels report AArch64 instead of 8 |
|
[ "$cpuarch" = "AArch64" ] && cpuarch=8 |
|
if [ -n "$cpupart" ] && [ -n "$cpuarch" ]; then |
|
# Cortex-R7 and Cortex-R8 are real-time and only used in medical devices or such |
|
# I can't find their CPU part number, but it's probably not that useful anyway |
|
# model R7 R8 A8 A9 A12 A15 A17 A57 A72 A73 A75 A76 |
|
# part ? ? c08 c09 c0d c0f c0e d07 d08 d09 d0a d0b? |
|
# arch 7? 7? 7 7 7 7 7 8 8 8 8 8 |
|
# |
|
# Whitelist identified non-vulnerable processors, use vulnerability information from |
|
# https://developer.arm.com/support/arm-security-updates/speculative-processor-vulnerability |
|
# |
|
# Maintain cumulative check of vulnerabilities - |
|
# if at least one of the cpu is vulnerable, then the system is vulnerable |
|
if [ "$cpuarch" = 7 ] && echo "$cpupart" | grep -q -w -e 0xc08 -e 0xc09 -e 0xc0d -e 0xc0e; then |
|
variant1=vuln |
|
variant2=vuln |
|
[ -z "$variant3" ] && variant3=immune |
|
[ -z "$variant3a" ] && variant3a=immune |
|
[ -z "$variant4" ] && variant4=immune |
|
_debug "checking cpu$i: armv7 A8/A9/A12/A17 non vulnerable to variants 3, 3a & 4" |
|
elif [ "$cpuarch" = 7 ] && echo "$cpupart" | grep -q -w -e 0xc0f; then |
|
variant1=vuln |
|
variant2=vuln |
|
[ -z "$variant3" ] && variant3=immune |
|
variant3a=vuln |
|
[ -z "$variant4" ] && variant4=immune |
|
_debug "checking cpu$i: armv7 A15 non vulnerable to variants 3 & 4" |
|
elif [ "$cpuarch" = 8 ] && echo "$cpupart" | grep -q -w -e 0xd07 -e 0xd08; then |
|
variant1=vuln |
|
variant2=vuln |
|
[ -z "$variant3" ] && variant3=immune |
|
variant3a=vuln |
|
variant4=vuln |
|
_debug "checking cpu$i: armv8 A57/A72 non vulnerable to variants 3" |
|
elif [ "$cpuarch" = 8 ] && echo "$cpupart" | grep -q -w -e 0xd09; then |
|
variant1=vuln |
|
variant2=vuln |
|
[ -z "$variant3" ] && variant3=immune |
|
[ -z "$variant3a" ] && variant3a=immune |
|
variant4=vuln |
|
_debug "checking cpu$i: armv8 A73 non vulnerable to variants 3 & 3a" |
|
elif [ "$cpuarch" = 8 ] && echo "$cpupart" | grep -q -w -e 0xd0a; then |
|
variant1=vuln |
|
variant2=vuln |
|
variant3=vuln |
|
[ -z "$variant3a" ] && variant3a=immune |
|
variant4=vuln |
|
_debug "checking cpu$i: armv8 A75 non vulnerable to variant 3a" |
|
elif [ "$cpuarch" = 8 ] && echo "$cpupart" | grep -q -w -e 0xd0b; then |
|
variant1=vuln |
|
[ -z "$variant2" ] && variant2=immune |
|
[ -z "$variant3" ] && variant3=immune |
|
[ -z "$variant3a" ] && variant3a=immune |
|
variant4=vuln |
|
_debug "checking cpu$i: armv8 A76 non vulnerable to variant 2, 3 & 3a" |
|
elif [ "$cpuarch" -le 7 ] || { [ "$cpuarch" = 8 ] && [ $(( cpupart )) -lt $(( 0xd07 )) ]; } ; then |
|
[ -z "$variant1" ] && variant1=immune |
|
[ -z "$variant2" ] && variant2=immune |
|
[ -z "$variant3" ] && variant3=immune |
|
[ -z "$variant3a" ] && variant3a=immune |
|
[ -z "$variant4" ] && variant4=immune |
|
_debug "checking cpu$i: arm arch$cpuarch, all immune (v7 or v8 and model < 0xd07)" |
|
else |
|
variant1=vuln |
|
variant2=vuln |
|
variant3=vuln |
|
variant3a=vuln |
|
variant4=vuln |
|
_debug "checking cpu$i: arm unknown arch$cpuarch part$cpupart, considering vuln" |
|
fi |
|
fi |
|
_debug "is_cpu_vulnerable: for cpu$i and so far, we have <$variant1> <$variant2> <$variant3> <$variant3a> <$variant4>" |
|
done |
|
variantl1tf=immune |
|
fi |
|
|
|
# we handle iTLB Multihit here (not linked to is_specex_free) |
|
if is_intel; then |
|
# commit f9aa6b73a407b714c9aac44734eb4045c893c6f7 |
|
if [ "$cpu_family" = 6 ]; then |
|
if [ "$cpu_model" = "$INTEL_FAM6_ATOM_SALTWELL" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_ATOM_SALTWELL_TABLET" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_ATOM_SALTWELL_MID" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_ATOM_BONNELL" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_ATOM_BONNELL_MID" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_ATOM_SILVERMONT" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_ATOM_SILVERMONT_D" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_ATOM_SILVERMONT_MID" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_ATOM_AIRMONT" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_XEON_PHI_KNL" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_XEON_PHI_KNM" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_ATOM_AIRMONT_MID" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_ATOM_GOLDMONT" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_ATOM_GOLDMONT_D" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_ATOM_GOLDMONT_PLUS" ]; then |
|
_debug "is_cpu_vulnerable: intel family 6 but model known to be immune to itlbmh" |
|
[ -z "$variant_itlbmh" ] && variant_itlbmh=immune |
|
else |
|
_debug "is_cpu_vulnerable: intel family 6 is vuln to itlbmh" |
|
variant_itlbmh=vuln |
|
fi |
|
elif [ "$cpu_family" -lt 6 ]; then |
|
_debug "is_cpu_vulnerable: intel family < 6 is immune to itlbmh" |
|
[ -z "$variant_itlbmh" ] && variant_itlbmh=immune |
|
fi |
|
else |
|
_debug "is_cpu_vulnerable: non-intel not vulnerable to itlbmh" |
|
[ -z "$variant_itlbmh" ] && variant_itlbmh=immune |
|
fi |
|
|
|
_debug "is_cpu_vulnerable: temp results are <$variant1> <$variant2> <$variant3> <$variant3a> <$variant4> <$variantl1tf>" |
|
[ "$variant1" = "immune" ] && variant1=1 || variant1=0 |
|
[ "$variant2" = "immune" ] && variant2=1 || variant2=0 |
|
[ "$variant3" = "immune" ] && variant3=1 || variant3=0 |
|
[ "$variant3a" = "immune" ] && variant3a=1 || variant3a=0 |
|
[ "$variant4" = "immune" ] && variant4=1 || variant4=0 |
|
[ "$variantl1tf" = "immune" ] && variantl1tf=1 || variantl1tf=0 |
|
[ "$variant_msbds" = "immune" ] && variant_msbds=1 || variant_msbds=0 |
|
[ "$variant_mfbds" = "immune" ] && variant_mfbds=1 || variant_mfbds=0 |
|
[ "$variant_mlpds" = "immune" ] && variant_mlpds=1 || variant_mlpds=0 |
|
[ "$variant_mdsum" = "immune" ] && variant_mdsum=1 || variant_mdsum=0 |
|
[ "$variant_taa" = "immune" ] && variant_taa=1 || variant_taa=0 |
|
[ "$variant_itlbmh" = "immune" ] && variant_itlbmh=1 || variant_itlbmh=0 |
|
[ "$variant_srbds" = "immune" ] && variant_srbds=1 || variant_srbds=0 |
|
variantl1tf_sgx="$variantl1tf" |
|
# even if we are vulnerable to L1TF, if there's no SGX, we're not vulnerable to the original foreshadow |
|
[ "$cpuid_sgx" = 0 ] && variantl1tf_sgx=1 |
|
_debug "is_cpu_vulnerable: final results are <$variant1> <$variant2> <$variant3> <$variant3a> <$variant4> <$variantl1tf> <$variantl1tf_sgx>" |
|
is_cpu_vulnerable_cached=1 |
|
_is_cpu_vulnerable_cached "$1" |
|
return $? |
|
} |
|
|
|
is_cpu_specex_free() |
|
{ |
|
# return true (0) if the CPU doesn't do speculative execution, false (1) if it does. |
|
# if it's not in the list we know, return false (1). |
|
# source: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/kernel/cpu/common.c#n882 |
|
# { X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_SALTWELL, X86_FEATURE_ANY }, |
|
# { X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_SALTWELL_TABLET, X86_FEATURE_ANY }, |
|
# { X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_BONNELL_MID, X86_FEATURE_ANY }, |
|
# { X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_SALTWELL_MID, X86_FEATURE_ANY }, |
|
# { X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_BONNELL, X86_FEATURE_ANY }, |
|
# { X86_VENDOR_CENTAUR, 5 }, |
|
# { X86_VENDOR_INTEL, 5 }, |
|
# { X86_VENDOR_NSC, 5 }, |
|
# { X86_VENDOR_ANY, 4 }, |
|
|
|
parse_cpu_details |
|
if is_intel; then |
|
if [ "$cpu_family" = 6 ]; then |
|
if [ "$cpu_model" = "$INTEL_FAM6_ATOM_SALTWELL" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_ATOM_SALTWELL_TABLET" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_ATOM_BONNELL_MID" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_ATOM_SALTWELL_MID" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_ATOM_BONNELL" ]; then |
|
return 0 |
|
fi |
|
elif [ "$cpu_family" = 5 ]; then |
|
return 0 |
|
fi |
|
fi |
|
[ "$cpu_family" = 4 ] && return 0 |
|
return 1 |
|
} |
|
|
|
is_cpu_mds_free() |
|
{ |
|
# return true (0) if the CPU isn't affected by microarchitectural data sampling, false (1) if it does. |
|
# if it's not in the list we know, return false (1). |
|
# source: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/kernel/cpu/common.c |
|
#VULNWL_INTEL(ATOM_GOLDMONT, NO_MDS | NO_L1TF), |
|
#VULNWL_INTEL(ATOM_GOLDMONT_X, NO_MDS | NO_L1TF), |
|
#VULNWL_INTEL(ATOM_GOLDMONT_PLUS, NO_MDS | NO_L1TF), |
|
|
|
#/* AMD Family 0xf - 0x12 */ |
|
#VULNWL_AMD(0x0f, NO_MELTDOWN | NO_SSB | NO_L1TF | NO_MDS), |
|
#VULNWL_AMD(0x10, NO_MELTDOWN | NO_SSB | NO_L1TF | NO_MDS), |
|
#VULNWL_AMD(0x11, NO_MELTDOWN | NO_SSB | NO_L1TF | NO_MDS), |
|
#VULNWL_AMD(0x12, NO_MELTDOWN | NO_SSB | NO_L1TF | NO_MDS), |
|
|
|
#/* FAMILY_ANY must be last, otherwise 0x0f - 0x12 matches won't work */ |
|
#VULNWL_AMD(X86_FAMILY_ANY, NO_MELTDOWN | NO_L1TF | NO_MDS), |
|
#VULNWL_HYGON(X86_FAMILY_ANY, NO_MELTDOWN | NO_L1TF | NO_MDS), |
|
parse_cpu_details |
|
if is_intel; then |
|
if [ "$cpu_family" = 6 ]; then |
|
if [ "$cpu_model" = "$INTEL_FAM6_ATOM_GOLDMONT" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_ATOM_GOLDMONT_D" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_ATOM_GOLDMONT_PLUS" ]; then |
|
return 0 |
|
fi |
|
fi |
|
[ "$capabilities_mds_no" = 1 ] && return 0 |
|
fi |
|
|
|
# official statement from AMD says none of their CPUs are vulnerable |
|
# https://www.amd.com/en/corporate/product-security |
|
# https://www.amd.com/system/files/documents/security-whitepaper.pdf |
|
if is_amd; then |
|
return 0 |
|
elif is_hygon; then |
|
return 0 |
|
elif [ "$cpu_vendor" = CAVIUM ]; then |
|
return 0 |
|
elif [ "$cpu_vendor" = ARM ]; then |
|
return 0 |
|
fi |
|
|
|
return 1 |
|
} |
|
|
|
|
|
is_cpu_taa_free() |
|
{ |
|
# return true (0) if the CPU isn't affected by tsx asynchronous aborts, false (1) if it does. |
|
# There are three types of processors that do not require additional mitigations. |
|
# 1. CPUs that do not support Intel TSX are not affected. |
|
# 2. CPUs that enumerate IA32_ARCH_CAPABILITIES[TAA_NO] (bit 8)=1 are not affected. |
|
# 3. CPUs that support Intel TSX and do not enumerate IA32_ARCH_CAPABILITIES[MDS_NO] (bit 5)=1 |
|
# do not need additional mitigations beyond what is already required to mitigate MDS. |
|
|
|
if ! is_intel; then |
|
return 0 |
|
# is intel |
|
elif [ "$capabilities_taa_no" = 1 ] || [ "$cpuid_rtm" = 0 ]; then |
|
return 0 |
|
fi |
|
|
|
return 1 |
|
} |
|
|
|
is_cpu_srbds_free() |
|
{ |
|
# return zero (0) if the CPU isn't affected by special register buffer data sampling, one (1) if it is. |
|
# If it's not in the list we know, return one (1). |
|
# source: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/kernel/cpu/common.c |
|
# |
|
# A processor is affected by SRBDS if its Family_Model and stepping is in the |
|
# following list, with the exception of the listed processors |
|
# exporting MDS_NO while Intel TSX is available yet not enabled. The |
|
# latter class of processors are only affected when Intel TSX is enabled |
|
# by software using TSX_CTRL_MSR otherwise they are not affected. |
|
# |
|
# ============= ============ ======== |
|
# common name Family_Model Stepping |
|
# ============= ============ ======== |
|
# IvyBridge 06_3AH All (INTEL_FAM6_IVYBRIDGE) |
|
# |
|
# Haswell 06_3CH All (INTEL_FAM6_HASWELL) |
|
# Haswell_L 06_45H All (INTEL_FAM6_HASWELL_L) |
|
# Haswell_G 06_46H All (INTEL_FAM6_HASWELL_G) |
|
# |
|
# Broadwell_G 06_47H All (INTEL_FAM6_BROADWELL_G) |
|
# Broadwell 06_3DH All (INTEL_FAM6_BROADWELL) |
|
# |
|
# Skylake_L 06_4EH All (INTEL_FAM6_SKYLAKE_L) |
|
# Skylake 06_5EH All (INTEL_FAM6_SKYLAKE) |
|
# |
|
# Kabylake_L 06_8EH <=0xC (MDS_NO) (INTEL_FAM6_KABYLAKE_L) |
|
# |
|
# Kabylake 06_9EH <=0xD (MDS_NO) (INTEL_FAM6_KABYLAKE) |
|
# ============= ============ ======== |
|
parse_cpu_details |
|
if is_intel; then |
|
if [ "$cpu_family" = 6 ]; then |
|
if [ "$cpu_model" = "$INTEL_FAM6_IVYBRIDGE" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_HASWELL" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_HASWELL_L" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_HASWELL_G" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_BROADWELL_G" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_BROADWELL" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_SKYLAKE_L" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_SKYLAKE" ]; then |
|
return 1 |
|
elif [ "$cpu_model" = "$INTEL_FAM6_KABYLAKE_L" ] && [ "$cpu_stepping" -le 12 ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_KABYLAKE" ] && [ "$cpu_stepping" -le 13 ]; then |
|
if [ "$capabilities_mds_no" -eq 1 ] && { [ "$cpuid_rtm" -eq 0 ] || [ "$tsx_ctrl_msr_rtm_disable" -eq 1 ] ;} ; then |
|
return 0 |
|
else |
|
return 1 |
|
fi |
|
fi |
|
fi |
|
fi |
|
|
|
return 0 |
|
|
|
} |
|
|
|
is_cpu_ssb_free() |
|
{ |
|
# return true (0) if the CPU isn't affected by speculative store bypass, false (1) if it does. |
|
# if it's not in the list we know, return false (1). |
|
# source1: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/kernel/cpu/common.c#n945 |
|
# source2: https://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git/tree/arch/x86/kernel/cpu/common.c |
|
# Only list CPUs that speculate but are immune, to avoid duplication of cpus listed in is_cpu_specex_free() |
|
#{ X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_SILVERMONT }, |
|
#{ X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_AIRMONT }, |
|
#{ X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_SILVERMONT_X }, |
|
#{ X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_SILVERMONT_MID }, |
|
#{ X86_VENDOR_INTEL, 6, INTEL_FAM6_CORE_YONAH }, |
|
#{ X86_VENDOR_INTEL, 6, INTEL_FAM6_XEON_PHI_KNL }, |
|
#{ X86_VENDOR_INTEL, 6, INTEL_FAM6_XEON_PHI_KNM }, |
|
#{ X86_VENDOR_AMD, 0x12, }, |
|
#{ X86_VENDOR_AMD, 0x11, }, |
|
#{ X86_VENDOR_AMD, 0x10, }, |
|
#{ X86_VENDOR_AMD, 0xf, }, |
|
parse_cpu_details |
|
if is_intel; then |
|
if [ "$cpu_family" = 6 ]; then |
|
if [ "$cpu_model" = "$INTEL_FAM6_ATOM_AIRMONT" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_ATOM_SILVERMONT" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_ATOM_SILVERMONT_D" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_ATOM_SILVERMONT_MID" ]; then |
|
return 0 |
|
elif [ "$cpu_model" = "$INTEL_FAM6_CORE_YONAH" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_XEON_PHI_KNL" ] || \ |
|
[ "$cpu_model" = "$INTEL_FAM6_XEON_PHI_KNM" ]; then |
|
return 0 |
|
fi |
|
fi |
|
fi |
|
if is_amd; then |
|
if [ "$cpu_family" = "18" ] || \ |
|
[ "$cpu_family" = "17" ] || \ |
|
[ "$cpu_family" = "16" ] || \ |
|
[ "$cpu_family" = "15" ]; then |
|
return 0 |
|
fi |
|
fi |
|
if is_hygon; then |
|
return 1 |
|
fi |
|
[ "$cpu_family" = 4 ] && return 0 |
|
return 1 |
|
} |
|
|
|
show_header() |
|
{ |
|
_info "Spectre and Meltdown mitigation detection tool v$VERSION" |
|
_info |
|
} |
|
|
|
[ -z "$HOME" ] && HOME="$(getent passwd "$(whoami)" | cut -d: -f6)" |
|
mcedb_cache="$HOME/.mcedb" |
|
update_fwdb() |
|
{ |
|
show_header |
|
|
|
if [ -r "$mcedb_cache" ]; then |
|
previous_dbversion=$(awk '/^# %%% MCEDB / { print $4 }' "$mcedb_cache") |
|
fi |
|
|
|
# first, download the MCE.db from the excellent platomav's MCExtractor project |
|
mcedb_tmp="$(mktemp /tmp/mcedb-XXXXXX)" |
|
mcedb_url='https://github.com/platomav/MCExtractor/raw/master/MCE.db' |
|
_info_nol "Fetching MCE.db from the MCExtractor project... " |
|
if command -v wget >/dev/null 2>&1; then |
|
wget -q "$mcedb_url" -O "$mcedb_tmp"; ret=$? |
|
elif command -v curl >/dev/null 2>&1; then |
|
curl -sL "$mcedb_url" -o "$mcedb_tmp"; ret=$? |
|
elif command -v fetch >/dev/null 2>&1; then |
|
fetch -q "$mcedb_url" -o "$mcedb_tmp"; ret=$? |
|
else |
|
echo ERROR "please install one of \`wget\`, \`curl\` of \`fetch\` programs" |
|
return 1 |
|
fi |
|
if [ "$ret" != 0 ]; then |
|
echo ERROR "error $ret while downloading MCE.db" |
|
return $ret |
|
fi |
|
echo DONE |
|
|
|
# second, get the Intel firmwares from GitHub |
|
intel_tmp="$(mktemp -d /tmp/intelfw-XXXXXX)" |
|
intel_url="https://github.com/intel/Intel-Linux-Processor-Microcode-Data-Files/archive/main.zip" |
|
_info_nol "Fetching Intel firmwares... " |
|
## https://github.com/intel/Intel-Linux-Processor-Microcode-Data-Files.git |
|
if command -v wget >/dev/null 2>&1; then |
|
wget -q "$intel_url" -O "$intel_tmp/fw.zip"; ret=$? |
|
elif command -v curl >/dev/null 2>&1; then |
|
curl -sL "$intel_url" -o "$intel_tmp/fw.zip"; ret=$? |
|
elif command -v fetch >/dev/null 2>&1; then |
|
fetch -q "$intel_url" -o "$intel_tmp/fw.zip"; ret=$? |
|
else |
|
echo ERROR "please install one of \`wget\`, \`curl\` of \`fetch\` programs" |
|
return 1 |
|
fi |
|
if [ "$ret" != 0 ]; then |
|
echo ERROR "error $ret while downloading Intel firmwares" |
|
return $ret |
|
fi |
|
echo DONE |
|
|
|
# now extract MCEdb contents using sqlite |
|
_info_nol "Extracting MCEdb data... " |
|
if ! command -v sqlite3 >/dev/null 2>&1; then |
|
echo ERROR "please install the \`sqlite3\` program" |
|
return 1 |
|
fi |
|
mcedb_revision=$(sqlite3 "$mcedb_tmp" "select revision from MCE") |
|
mcedb_date=$(sqlite3 "$mcedb_tmp" "select strftime('%Y/%m/%d', date, 'unixepoch') from MCE") |
|
if [ -z "$mcedb_revision" ]; then |
|
echo ERROR "downloaded file seems invalid" |
|
return 1 |
|
fi |
|
|
|
echo OK "MCExtractor database revision $mcedb_revision dated $mcedb_date" |
|
|
|
# parse Intel firmwares to get their versions |
|
_info_nol "Integrating Intel firmwares data to db... " |
|
if ! command -v unzip >/dev/null 2>&1; then |
|
echo ERROR "please install the \`unzip\` program" |
|
return 1 |
|
fi |
|
( cd "$intel_tmp" && unzip fw.zip >/dev/null; ) |
|
if ! [ -d "$intel_tmp/Intel-Linux-Processor-Microcode-Data-Files-main/intel-ucode" ]; then |
|
echo ERROR "expected the 'intel-ucode' folder in the downloaded zip file" |
|
return 1 |
|
fi |
|
|
|
if ! command -v iucode_tool >/dev/null 2>&1; then |
|
if ! command -v iucode-tool >/dev/null 2>&1; then |
|
echo ERROR "please install the \`iucode-tool\` program" |
|
return 1 |
|
else |
|
iucode_tool="iucode-tool" |
|
fi |
|
else |
|
iucode_tool="iucode_tool" |
|
fi |
|
# 079/001: sig 0x000106c2, pf_mask 0x01, 2009-04-10, rev 0x0217, size 5120 |
|
# 078/004: sig 0x000106ca, pf_mask 0x10, 2009-08-25, rev 0x0107, size 5120 |
|
$iucode_tool -l "$intel_tmp/Intel-Linux-Processor-Microcode-Data-Files-main/intel-ucode" | grep -wF sig | while read -r _line |
|
do |
|
_line=$( echo "$_line" | tr -d ',') |
|
_cpuid=$( echo "$_line" | awk '{print $3}') |
|
_cpuid=$(( _cpuid )) |
|
_cpuid=$(printf "0x%08X" "$_cpuid") |
|
_date=$( echo "$_line" | awk '{print $6}' | tr -d '-') |
|
_version=$(echo "$_line" | awk '{print $8}') |
|
_version=$(( _version )) |
|
_version=$(printf "0x%08X" "$_version") |
|
_sqlstm="$(printf "INSERT INTO Intel (cpuid,version,yyyymmdd) VALUES (\"%s\",\"%s\",\"%s\");" "$(printf "%08X" "$_cpuid")" "$(printf "%08X" "$_version")" "$_date")" |
|
sqlite3 "$mcedb_tmp" "$_sqlstm" |
|
done |
|
_intel_latest_date=$(sqlite3 "$mcedb_tmp" "SELECT yyyymmdd from Intel ORDER BY yyyymmdd DESC LIMIT 1;") |
|
echo DONE "(version $_intel_latest_date)" |
|
|
|
dbdate=$(echo "$mcedb_date" | tr -d '/') |
|
if [ "$dbdate" -lt "$_intel_latest_date" ]; then |
|
dbdate="$_intel_latest_date" |
|
fi |
|
dbversion="$mcedb_revision.$dbdate+i$_intel_latest_date" |
|
|
|
if [ "$1" != builtin ] && [ -n "$previous_dbversion" ] && [ "$previous_dbversion" = "v$dbversion" ]; then |
|
echo "We already have this version locally, no update needed" |
|
return 0 |
|
fi |
|
|
|
_info_nol "Building local database... " |
|
{ |
|
echo "# Spectre & Meltdown Checker"; |
|
echo "# %%% MCEDB v$dbversion"; |
|
sqlite3 "$mcedb_tmp" "SELECT '# I,0x'||t1.cpuid||',0x'||MAX(t1.version)||','||t1.yyyymmdd FROM Intel AS t1 LEFT OUTER JOIN Intel AS t2 ON t2.cpuid=t1.cpuid AND t2.yyyymmdd > t1.yyyymmdd WHERE t2.yyyymmdd IS NULL GROUP BY t1.cpuid ORDER BY t1.cpuid ASC;" | grep -v '^# .,0x00000000,'; |
|
sqlite3 "$mcedb_tmp" "SELECT '# A,0x'||t1.cpuid||',0x'||MAX(t1.version)||','||t1.yyyymmdd FROM AMD AS t1 LEFT OUTER JOIN AMD AS t2 ON t2.cpuid=t1.cpuid AND t2.yyyymmdd > t1.yyyymmdd WHERE t2.yyyymmdd IS NULL GROUP BY t1.cpuid ORDER BY t1.cpuid ASC;" | grep -v '^# .,0x00000000,'; |
|
} > "$mcedb_cache" |
|
echo DONE "(version $dbversion)" |
|
|
|
if [ "$1" = builtin ]; then |
|
newfile=$(mktemp /tmp/smc-XXXXXX) |
|
awk '/^# %%% MCEDB / { exit }; { print }' "$0" > "$newfile" |
|
awk '{ if (NR>1) { print } }' "$mcedb_cache" >> "$newfile" |
|
cat "$newfile" > "$0" |
|
rm -f "$newfile" |
|
fi |
|
} |
|
|
|
parse_opt_file() |
|
{ |
|
# parse_opt_file option_name option_value |
|
option_name="$1" |
|
option_value="$2" |
|
if [ -z "$option_value" ]; then |
|
show_header |
|
show_usage |
|
echo "$0: error: --$option_name expects one parameter (a file)" >&2 |
|
exit 1 |
|
elif [ ! -e "$option_value" ]; then |
|
show_header |
|
echo "$0: error: couldn't find file $option_value" >&2 |
|
exit 1 |
|
elif [ ! -f "$option_value" ]; then |
|
show_header |
|
echo "$0: error: $option_value is not a file" >&2 |
|
exit 1 |
|
elif [ ! -r "$option_value" ]; then |
|
show_header |
|
echo "$0: error: couldn't read $option_value (are you root?)" >&2 |
|
exit 1 |
|
fi |
|
echo "$option_value" |
|
exit 0 |
|
} |
|
|
|
while [ -n "$1" ]; do |
|
if [ "$1" = "--kernel" ]; then |
|
opt_kernel=$(parse_opt_file kernel "$2"); ret=$? |
|
[ $ret -ne 0 ] && exit 255 |
|
shift 2 |
|
elif [ "$1" = "--config" ]; then |
|
opt_config=$(parse_opt_file config "$2"); ret=$? |
|
[ $ret -ne 0 ] && exit 255 |
|
shift 2 |
|
elif [ "$1" = "--map" ]; then |
|
opt_map=$(parse_opt_file map "$2"); ret=$? |
|
[ $ret -ne 0 ] && exit 255 |
|
shift 2 |
|
elif [ "$1" = "--arch-prefix" ]; then |
|
opt_arch_prefix="$2" |
|
shift 2 |
|
elif [ "$1" = "--live" ]; then |
|
opt_live=1 |
|
shift |
|
elif [ "$1" = "--no-color" ]; then |
|
opt_no_color=1 |
|
shift |
|
elif [ "$1" = "--no-sysfs" ]; then |
|
opt_no_sysfs=1 |
|
shift |
|
elif [ "$1" = "--sysfs-only" ]; then |
|
opt_sysfs_only=1 |
|
shift |
|
elif [ "$1" = "--coreos" ]; then |
|
opt_coreos=1 |
|
shift |
|
elif [ "$1" = "--coreos-within-toolbox" ]; then |
|
# don't use directly: used internally by --coreos |
|
opt_coreos=0 |
|
shift |
|
elif [ "$1" = "--paranoid" ]; then |
|
opt_paranoid=1 |
|
shift |
|
elif [ "$1" = "--hw-only" ]; then |
|
opt_hw_only=1 |
|
shift |
|
elif [ "$1" = "--no-hw" ]; then |
|
opt_no_hw=1 |
|
shift |
|
elif [ "$1" = "--no-explain" ]; then |
|
# deprecated, kept for compatibility |
|
opt_explain=0 |
|
shift |
|
elif [ "$1" = "--update-fwdb" ] || [ "$1" = "--update-mcedb" ]; then |
|
update_fwdb |
|
exit $? |
|
elif [ "$1" = "--update-builtin-fwdb" ] || [ "$1" = "--update-builtin-mcedb" ]; then |
|
update_fwdb builtin |
|
exit $? |
|
elif [ "$1" = "--dump-mock-data" ]; then |
|
opt_mock=1 |
|
shift |
|
elif [ "$1" = "--explain" ]; then |
|
opt_explain=1 |
|
shift |
|
elif [ "$1" = "--batch" ]; then |
|
opt_batch=1 |
|
opt_verbose=0 |
|
opt_no_color=1 |
|
shift |
|
case "$1" in |
|
text|short|nrpe|json|prometheus) opt_batch_format="$1"; shift;; |
|
--*) ;; # allow subsequent flags |
|
'') ;; # allow nothing at all |
|
*) |
|
echo "$0: error: unknown batch format '$1'" >&2 |
|
echo "$0: error: --batch expects a format from: text, nrpe, json" >&2 |
|
exit 255 |
|
;; |
|
esac |
|
elif [ "$1" = "-v" ] || [ "$1" = "--verbose" ]; then |
|
opt_verbose=$(( opt_verbose + 1 )) |
|
[ "$opt_verbose" -ge 2 ] && opt_mock=1 |
|
shift |
|
elif [ "$1" = "--cve" ]; then |
|
if [ -z "$2" ]; then |
|
echo "$0: error: option --cve expects a parameter, supported CVEs are: $supported_cve_list" >&2 |
|
exit 255 |
|
fi |
|
selected_cve=$(echo "$supported_cve_list" | grep -iwo "$2") |
|
if [ -n "$selected_cve" ]; then |
|
opt_cve_list="$opt_cve_list $selected_cve" |
|
opt_cve_all=0 |
|
else |
|
echo "$0: error: unsupported CVE specified ('$2'), supported CVEs are: $supported_cve_list" >&2 |
|
exit 255 |
|
fi |
|
shift 2 |
|
elif [ "$1" = "--vmm" ]; then |
|
if [ -z "$2" ]; then |
|
echo "$0: error: option --vmm (auto, yes, no)" >&2 |
|
exit 255 |
|
fi |
|
case "$2" in |
|
auto) opt_vmm=-1;; |
|
yes) opt_vmm=1;; |
|
no) opt_vmm=0;; |
|
*) echo "$0: error: expected one of (auto, yes, no) to option --vmm instead of '$2'" >&2; exit 255;; |
|
esac |
|
shift 2 |
|
elif [ "$1" = "--variant" ]; then |
|
if [ -z "$2" ]; then |
|
echo "$0: error: option --variant expects a parameter (1, 2, 3, 3a, 4 or l1tf)" >&2 |
|
exit 255 |
|
fi |
|
case "$2" in |
|
1) opt_cve_list="$opt_cve_list CVE-2017-5753"; opt_cve_all=0;; |
|
2) opt_cve_list="$opt_cve_list CVE-2017-5715"; opt_cve_all=0;; |
|
3) opt_cve_list="$opt_cve_list CVE-2017-5754"; opt_cve_all=0;; |
|
3a) opt_cve_list="$opt_cve_list CVE-2018-3640"; opt_cve_all=0;; |
|
4) opt_cve_list="$opt_cve_list CVE-2018-3639"; opt_cve_all=0;; |
|
msbds) opt_cve_list="$opt_cve_list CVE-2018-12126"; opt_cve_all=0;; |
|
mfbds) opt_cve_list="$opt_cve_list CVE-2018-12130"; opt_cve_all=0;; |
|
mlpds) opt_cve_list="$opt_cve_list CVE-2018-12127"; opt_cve_all=0;; |
|
mdsum) opt_cve_list="$opt_cve_list CVE-2019-11091"; opt_cve_all=0;; |
|
l1tf) opt_cve_list="$opt_cve_list CVE-2018-3615 CVE-2018-3620 CVE-2018-3646"; opt_cve_all=0;; |
|
taa) opt_cve_list="$opt_cve_list CVE-2019-11135"; opt_cve_all=0;; |
|
mcepsc) opt_cve_list="$opt_cve_list CVE-2018-12207"; opt_cve_all=0;; |
|
srbds) opt_cve_list="$opt_cve_list CVE-2020-0543"; opt_cve_all=0;; |
|
*) |
|
echo "$0: error: invalid parameter '$2' for --variant, expected either 1, 2, 3, 3a, 4, l1tf, msbds, mfbds, mlpds, mdsum, taa, mcepsc or srbds" >&2; |
|
exit 255 |
|
;; |
|
esac |
|
shift 2 |
|
elif [ "$1" = "-h" ] || [ "$1" = "--help" ]; then |
|
show_header |
|
show_usage |
|
exit 0 |
|
elif [ "$1" = "--version" ]; then |
|
opt_no_color=1 |
|
show_header |
|
exit 0 |
|
elif [ "$1" = "--disclaimer" ]; then |
|
show_header |
|
show_disclaimer |
|
exit 0 |
|
else |
|
show_header |
|
show_usage |
|
echo "$0: error: unknown option '$1'" |
|
exit 255 |
|
fi |
|
done |
|
|
|
show_header |
|
|
|
if [ "$opt_no_sysfs" = 1 ] && [ "$opt_sysfs_only" = 1 ]; then |
|
_warn "Incompatible options specified (--no-sysfs and --sysfs-only), aborting" |
|
exit 255 |
|
fi |
|
|
|
if [ "$opt_no_hw" = 1 ] && [ "$opt_hw_only" = 1 ]; then |
|
_warn "Incompatible options specified (--no-hw and --hw-only), aborting" |
|
exit 255 |
|
fi |
|
|
|
if [ "$opt_live" = -1 ]; then |
|
if [ -n "$opt_kernel" ] || [ -n "$opt_config" ] || [ -n "$opt_map" ]; then |
|
# no --live specified and we have a least one of the kernel/config/map files on the cmdline: offline mode |
|
opt_live=0 |
|
else |
|
opt_live=1 |
|
fi |
|
fi |
|
|
|
# print status function |
|
pstatus() |
|
{ |
|
if [ "$opt_no_color" = 1 ]; then |
|
_info_nol "$2" |
|
else |
|
case "$1" in |
|
red) col="\033[41m\033[30m";; |
|
green) col="\033[42m\033[30m";; |
|
yellow) col="\033[43m\033[30m";; |
|
blue) col="\033[44m\033[30m";; |
|
*) col="";; |
|
esac |
|
_info_nol "$col $2 \033[0m" |
|
fi |
|
[ -n "$3" ] && _info_nol " ($3)" |
|
_info |
|
unset col |
|
} |
|
|
|
# Print the final status of a vulnerability (incl. batch mode) |
|
# Arguments are: CVE UNK/OK/VULN description |
|
pvulnstatus() |
|
{ |
|
pvulnstatus_last_cve="$1" |
|
if [ "$opt_batch" = 1 ]; then |
|
case "$1" in |
|
CVE-2017-5753) aka="SPECTRE VARIANT 1";; |
|
CVE-2017-5715) aka="SPECTRE VARIANT 2";; |
|
CVE-2017-5754) aka="MELTDOWN";; |
|
CVE-2018-3640) aka="VARIANT 3A";; |
|
CVE-2018-3639) aka="VARIANT 4";; |
|
CVE-2018-3615) aka="L1TF SGX";; |
|
CVE-2018-3620) aka="L1TF OS";; |
|
CVE-2018-3646) aka="L1TF VMM";; |
|
CVE-2018-12126) aka="MSBDS";; |
|
CVE-2018-12130) aka="MFBDS";; |
|
CVE-2018-12127) aka="MLPDS";; |
|
CVE-2019-11091) aka="MDSUM";; |
|
CVE-2019-11135) aka="TAA";; |
|
CVE-2018-12207) aka="ITLBMH";; |
|
CVE-2020-0543) aka="SRBDS";; |
|
*) echo "$0: error: invalid CVE '$1' passed to pvulnstatus()" >&2; exit 255;; |
|
esac |
|
|
|
case "$opt_batch_format" in |
|
text) _echo 0 "$1: $2 ($3)";; |
|
short) short_output="${short_output}$1 ";; |
|
json) |
|
case "$2" in |
|
UNK) is_vuln="null";; |
|
VULN) is_vuln="true";; |
|
OK) is_vuln="false";; |
|
*) echo "$0: error: unknown status '$2' passed to pvulnstatus()" >&2; exit 255;; |
|
esac |
|
[ -z "$json_output" ] && json_output='[' |
|
json_output="${json_output}{\"NAME\":\"$aka\",\"CVE\":\"$1\",\"VULNERABLE\":$is_vuln,\"INFOS\":\"$3\"}," |
|
;; |
|
|
|
nrpe) [ "$2" = VULN ] && nrpe_vuln="$nrpe_vuln $1";; |
|
prometheus) |
|
prometheus_output="${prometheus_output:+$prometheus_output\n}specex_vuln_status{name=\"$aka\",cve=\"$1\",status=\"$2\",info=\"$3\"} 1" |
|
;; |
|
*) echo "$0: error: invalid batch format '$opt_batch_format' specified" >&2; exit 255;; |
|
esac |
|
fi |
|
|
|
# always fill global_* vars because we use that do decide the program exit code |
|
case "$2" in |
|
UNK) global_unknown="1";; |
|
VULN) global_critical="1";; |
|
OK) ;; |
|
*) echo "$0: error: unknown status '$2' passed to pvulnstatus()" >&2; exit 255;; |
|
esac |
|
|
|
# display info if we're not in quiet/batch mode |
|
vulnstatus="$2" |
|
shift 2 |
|
_info_nol "> \033[46m\033[30mSTATUS:\033[0m " |
|
case "$vulnstatus" in |
|
UNK) pstatus yellow 'UNKNOWN' "$@"; final_summary="$final_summary \033[43m\033[30m$pvulnstatus_last_cve:??\033[0m";; |
|
VULN) pstatus red 'VULNERABLE' "$@"; final_summary="$final_summary \033[41m\033[30m$pvulnstatus_last_cve:KO\033[0m";; |
|
OK) pstatus green 'NOT VULNERABLE' "$@"; final_summary="$final_summary \033[42m\033[30m$pvulnstatus_last_cve:OK\033[0m";; |
|
*) echo "$0: error: unknown status '$vulnstatus' passed to pvulnstatus()" >&2; exit 255;; |
|
esac |
|
} |
|
|
|
|
|
# The 3 below functions are taken from the extract-linux script, available here: |
|
# https://github.com/torvalds/linux/blob/master/scripts/extract-vmlinux |
|
# The functions have been modified for better integration to this script |
|
# The original header of the file has been retained below |
|
|
|
# ---------------------------------------------------------------------- |
|
# extract-vmlinux - Extract uncompressed vmlinux from a kernel image |
|
# |
|
# Inspired from extract-ikconfig |
|
# (c) 2009,2010 Dick Streefland <dick@streefland.net> |
|
# |
|
# (c) 2011 Corentin Chary <corentin.chary@gmail.com> |
|
# |
|
# Licensed under the GNU General Public License, version 2 (GPLv2). |
|
# ---------------------------------------------------------------------- |
|
|
|
kernel='' |
|
kernel_err='' |
|
check_kernel() |
|
{ |
|
_file="$1" |
|
_mode="$2" |
|
# checking the return code of readelf -h is not enough, we could get |
|
# a damaged ELF file and validate it, check for stderr warnings too |
|
|
|
# the warning "readelf: Warning: [16]: Link field (0) should index a symtab section./" can appear on valid kernels, ignore it |
|
_readelf_warnings=$("${opt_arch_prefix}readelf" -S "$_file" 2>&1 >/dev/null | grep -v 'should index a symtab section' | tr "\n" "/"); ret=$? |
|
_readelf_sections=$("${opt_arch_prefix}readelf" -S "$_file" 2>/dev/null | grep -c -e data -e text -e init) |
|
_kernel_size=$(stat -c %s "$_file" 2>/dev/null || stat -f %z "$_file" 2>/dev/null || echo 10000) |
|
_debug "check_kernel: ret=$? size=$_kernel_size sections=$_readelf_sections warnings=$_readelf_warnings" |
|
if [ "$_mode" = desperate ]; then |
|
if "${opt_arch_prefix}strings" "$_file" | grep -Eq '^Linux version '; then |
|
_debug "check_kernel (desperate): ... matched!" |
|
if [ "$_readelf_sections" = 0 ] && grep -qF -e armv6 -e armv7 "$_file"; then |
|
_debug "check_kernel (desperate): raw arm binary found, adjusting objdump options" |
|
objdump_options="-D -b binary -marm" |
|
else |
|
objdump_options="-d" |
|
fi |
|
return 0 |
|
else |
|
_debug "check_kernel (desperate): ... invalid" |
|
fi |
|
else |
|
if [ $ret -eq 0 ] && [ -z "$_readelf_warnings" ] && [ "$_readelf_sections" -gt 0 ]; then |
|
if [ "$_kernel_size" -ge 100000 ]; then |
|
_debug "check_kernel: ... file is valid" |
|
objdump_options="-d" |
|
return 0 |
|
else |
|
_debug "check_kernel: ... file seems valid but is too small, ignoring" |
|
fi |
|
else |
|
_debug "check_kernel: ... file is invalid" |
|
fi |
|
fi |
|
return 1 |
|
} |
|
|
|
try_decompress() |
|
{ |
|
# The obscure use of the "tr" filter is to work around older versions of |
|
# "grep" that report the byte offset of the line instead of the pattern. |
|
|
|
# Try to find the header ($1) and decompress from here |
|
_debug "try_decompress: looking for $3 magic in $6" |
|
for pos in $(tr "$1\n$2" "\n$2=" < "$6" | grep -abo "^$2") |
|
do |
|
_debug "try_decompress: magic for $3 found at offset $pos" |
|
if ! command -v "$3" >/dev/null 2>&1; then |
|
if [ "$8" = 1 ]; then |
|
# pass1: if the tool is not installed, just bail out silently |
|
# and hope that the next decompression tool will be, and that |
|
# it'll happen to be the proper one for this kernel |
|
_debug "try_decompress: the '$3' tool is not installed (pass 1), try the next algo" |
|
else |
|
# pass2: if the tool is not installed, populate kernel_err this time |
|
kernel_err="missing '$3' tool, please install it, usually it's in the '$5' package" |
|
_debug "try_decompress: $kernel_err" |
|
fi |
|
return 1 |
|
fi |
|
pos=${pos%%:*} |
|
# shellcheck disable=SC2086 |
|
tail -c+$pos "$6" 2>/dev/null | $3 $4 > "$kerneltmp" 2>/dev/null; ret=$? |
|
if [ ! -s "$kerneltmp" ]; then |
|
# don't rely on $ret, sometimes it's != 0 but worked |
|
# (e.g. gunzip ret=2 just means there was trailing garbage) |
|
_debug "try_decompress: decompression with $3 failed (err=$ret)" |
|
elif check_kernel "$kerneltmp" "$7"; then |
|
kernel="$kerneltmp" |
|
_debug "try_decompress: decompressed with $3 successfully!" |
|
return 0 |
|
elif [ "$3" != "cat" ]; then |
|
_debug "try_decompress: decompression with $3 worked but result is not a kernel, trying with an offset" |
|
[ -z "$kerneltmp2" ] && kerneltmp2=$(mktemp /tmp/kernel-XXXXXX) |
|
cat "$kerneltmp" > "$kerneltmp2" |
|
try_decompress '\177ELF' xxy 'cat' '' cat "$kerneltmp2" && return 0 |
|
else |
|
_debug "try_decompress: decompression with $3 worked but result is not a kernel" |
|
fi |
|
done |
|
return 1 |
|
} |
|
|
|
extract_kernel() |
|
{ |
|
[ -n "$1" ] || return 1 |
|
# Prepare temp files: |
|
kerneltmp="$(mktemp /tmp/kernel-XXXXXX)" |
|
|
|
# Initial attempt for uncompressed images or objects: |
|
if check_kernel "$1"; then |
|
_debug "extract_kernel: found kernel is valid, no decompression needed" |
|
cat "$1" > "$kerneltmp" |
|
kernel=$kerneltmp |
|
return 0 |
|
fi |
|
|
|
# That didn't work, so retry after decompression. |
|
for pass in 1 2; do |
|
for mode in normal desperate; do |
|
_debug "extract_kernel: pass $pass $mode mode" |
|
try_decompress '\037\213\010' xy gunzip '' gunzip "$1" "$mode" "$pass" && return 0 |
|
try_decompress '\002\041\114\030' xyy 'lz4' '-d -l' liblz4-tool "$1" "$mode" "$pass" && return 0 |
|
try_decompress '\3757zXZ\000' abcde unxz '' xz-utils "$1" "$mode" "$pass" && return 0 |
|
try_decompress 'BZh' xy bunzip2 '' bzip2 "$1" "$mode" "$pass" && return 0 |
|
try_decompress '\135\0\0\0' xxx unlzma '' xz-utils "$1" "$mode" "$pass" && return 0 |
|
try_decompress '\211\114\132' xy 'lzop' '-d' lzop "$1" "$mode" "$pass" && return 0 |
|
try_decompress '\177ELF' xxy 'cat' '' cat "$1" "$mode" "$pass" && return 0 |
|
try_decompress '(\265/\375' xxy unzstd '' zstd "$1" "$mode" "$pass" && return 0 |
|
done |
|
done |
|
kernel_err="kernel compression format is unknown or image is invalid" |
|
_verbose "Couldn't extract the kernel image ($kernel_err), accuracy might be reduced" |
|
return 1 |
|
} |
|
|
|
# end of extract-vmlinux functions |
|
|
|
mount_debugfs() |
|
{ |
|
if [ ! -e /sys/kernel/debug/sched_features ]; then |
|
# try to mount the debugfs hierarchy ourselves and remember it to umount afterwards |
|
mount -t debugfs debugfs /sys/kernel/debug 2>/dev/null && mounted_debugfs=1 |
|
fi |
|
} |
|
|
|
load_msr() |
|
{ |
|
if [ "$os" = Linux ]; then |
|
if ! grep -qw msr "$procfs/modules" 2>/dev/null; then |
|
modprobe msr 2>/dev/null && insmod_msr=1 |
|
_debug "attempted to load module msr, insmod_msr=$insmod_msr" |
|
else |
|
_debug "msr module already loaded" |
|
fi |
|
else |
|
if ! kldstat -q -m cpuctl; then |
|
kldload cpuctl 2>/dev/null && kldload_cpuctl=1 |
|
_debug "attempted to load module cpuctl, kldload_cpuctl=$kldload_cpuctl" |
|
else |
|
_debug "cpuctl module already loaded" |
|
fi |
|
fi |
|
} |
|
|
|
load_cpuid() |
|
{ |
|
if [ "$os" = Linux ]; then |
|
if ! grep -qw cpuid "$procfs/modules" 2>/dev/null; then |
|
modprobe cpuid 2>/dev/null && insmod_cpuid=1 |
|
_debug "attempted to load module cpuid, insmod_cpuid=$insmod_cpuid" |
|
else |
|
_debug "cpuid module already loaded" |
|
fi |
|
else |
|
if ! kldstat -q -m cpuctl; then |
|
kldload cpuctl 2>/dev/null && kldload_cpuctl=1 |
|
_debug "attempted to load module cpuctl, kldload_cpuctl=$kldload_cpuctl" |
|
else |
|
_debug "cpuctl module already loaded" |
|
fi |
|
fi |
|
} |
|
|
|
# shellcheck disable=SC2034 |
|
EAX=1; EBX=2; ECX=3; EDX=4; |
|
read_cpuid() |
|
{ |
|
# leaf is the value of the eax register when calling the cpuid instruction: |
|
_leaf="$1" |
|
# eax=1 ebx=2 ecx=3 edx=4: |
|
_register="$2" |
|
# number of bits to shift the register right to: |
|
_shift="$3" |
|
# mask to apply as an AND operand to the shifted register value |
|
_mask="$4" |
|
# wanted value (optional), if present we return 0(true) if the obtained value is equal, 1 otherwise: |
|
_wanted="$5" |
|
# in any case, the read value is globally available in $read_cpuid_value |
|
|
|
read_cpuid_value='' |
|
if [ ! -e /dev/cpu/0/cpuid ] && [ ! -e /dev/cpuctl0 ]; then |
|
# try to load the module ourselves (and remember it so we can rmmod it afterwards) |
|
load_cpuid |
|
fi |
|
|
|
if [ -e /dev/cpu/0/cpuid ]; then |
|
# Linux |
|
if [ ! -r /dev/cpu/0/cpuid ]; then |
|
return 2 |
|
fi |
|
# on some kernel versions, /dev/cpu/0/cpuid doesn't imply that the cpuid module is loaded, in that case dd returns an error |
|
dd if=/dev/cpu/0/cpuid bs=16 count=1 >/dev/null 2>&1 || load_cpuid |
|
# we need _leaf to be converted to decimal for dd |
|
_leaf=$(( _leaf )) |
|
# to avoid using iflag=skip_bytes, which doesn't exist on old versions of dd, seek to the closer multiple-of-16 |
|
_ddskip=$(( _leaf / 16 )) |
|
_odskip=$(( _leaf - _ddskip * 16 )) |
|
# now read the value |
|
_cpuid=$(dd if=/dev/cpu/0/cpuid bs=16 skip=$_ddskip count=$((_odskip + 1)) 2>/dev/null | od -j $((_odskip * 16)) -A n -t u4) |
|
elif [ -e /dev/cpuctl0 ]; then |
|
# BSD |
|
if [ ! -r /dev/cpuctl0 ]; then |
|
return 2 |
|
fi |
|
_cpuid=$(cpucontrol -i "$_leaf" /dev/cpuctl0 2>/dev/null | awk '{print $4,$5,$6,$7}') |
|
# cpuid level 0x1: 0x000306d4 0x00100800 0x4dfaebbf 0xbfebfbff |
|
else |
|
return 2 |
|
fi |
|
|
|
_debug "cpuid: leaf$_leaf on cpu0, eax-ebx-ecx-edx: $_cpuid" |
|
_mockvarname="SMC_MOCK_CPUID_${_leaf}" |
|
if [ -n "$(eval echo \$$_mockvarname)" ]; then |
|
_cpuid="$(eval echo \$$_mockvarname)" |
|
_debug "read_cpuid: MOCKING enabled for leaf $_leaf, will return $_cpuid" |
|
mocked=1 |
|
else |
|
mockme=$(printf "%b\n%b" "$mockme" "SMC_MOCK_CPUID_${_leaf}='$_cpuid'") |
|
fi |
|
[ -z "$_cpuid" ] && return 2 |
|
# get the value of the register we want |
|
_reg=$(echo "$_cpuid" | awk '{print $'"$_register"'}') |
|
# Linux returns it as decimal, BSD as hex, normalize to decimal |
|
_reg=$(( _reg )) |
|
# shellcheck disable=SC2046 |
|
_debug "cpuid: wanted register ($_register) has value $_reg aka "$(printf "%08x" "$_reg") |
|
_reg_shifted=$(( _reg >> _shift )) |
|
# shellcheck disable=SC2046 |
|
_debug "cpuid: shifted value by $_shift is $_reg_shifted aka "$(printf "%x" "$_reg_shifted") |
|
read_cpuid_value=$(( _reg_shifted & _mask )) |
|
# shellcheck disable=SC2046 |
|
_debug "cpuid: after AND $_mask, final value is $read_cpuid_value aka "$(printf "%x" "$read_cpuid_value") |
|
if [ -n "$_wanted" ]; then |
|
_debug "cpuid: wanted $_wanted and got $read_cpuid_value" |
|
if [ "$read_cpuid_value" = "$_wanted" ]; then |
|
return 0 |
|
else |
|
return 1 |
|
fi |
|
fi |
|
|
|
return 0 |
|
} |
|
|
|
dmesg_grep() |
|
{ |
|
# grep for something in dmesg, ensuring that the dmesg buffer |
|
# has not been truncated |
|
dmesg_grepped='' |
|
if ! dmesg | grep -qE -e '(^|\] )Linux version [0-9]' -e '^FreeBSD is a registered' ; then |
|
# dmesg truncated |
|
return 2 |
|
fi |
|
dmesg_grepped=$(dmesg | grep -E "$1" | head -1) |
|
# not found: |
|
[ -z "$dmesg_grepped" ] && return 1 |
|
# found, output is in $dmesg_grepped |
|
return 0 |
|
} |
|
|
|
is_coreos() |
|
{ |
|
command -v coreos-install >/dev/null 2>&1 && command -v toolbox >/dev/null 2>&1 && return 0 |
|
return 1 |
|
} |
|
|
|
parse_cpu_details() |
|
{ |
|
[ "$parse_cpu_details_done" = 1 ] && return 0 |
|
|
|
if [ -e "$procfs/cpuinfo" ]; then |
|
cpu_vendor=$( grep '^vendor_id' "$procfs/cpuinfo" | awk '{print $3}' | head -1) |
|
cpu_friendly_name=$(grep '^model name' "$procfs/cpuinfo" | cut -d: -f2- | head -1 | sed -e 's/^ *//') |
|
# special case for ARM follows |
|
if grep -qi 'CPU implementer[[:space:]]*:[[:space:]]*0x41' "$procfs/cpuinfo"; then |
|
cpu_vendor='ARM' |
|
# some devices (phones or other) have several ARMs and as such different part numbers, |
|
# an example is "bigLITTLE", so we need to store the whole list, this is needed for is_cpu_vulnerable |
|
cpu_part_list=$(awk '/CPU part/ {print $4}' "$procfs/cpuinfo") |
|
cpu_arch_list=$(awk '/CPU architecture/ {print $3}' "$procfs/cpuinfo") |
|
# take the first one to fill the friendly name, do NOT quote the vars below |
|
# shellcheck disable=SC2086 |
|
cpu_arch=$(echo $cpu_arch_list | awk '{ print $1 }') |
|
# shellcheck disable=SC2086 |
|
cpu_part=$(echo $cpu_part_list | awk '{ print $1 }') |
|
[ "$cpu_arch" = "AArch64" ] && cpu_arch=8 |
|
cpu_friendly_name="ARM" |
|
[ -n "$cpu_arch" ] && cpu_friendly_name="$cpu_friendly_name v$cpu_arch" |
|
[ -n "$cpu_part" ] && cpu_friendly_name="$cpu_friendly_name model $cpu_part" |
|
|
|
elif grep -qi 'CPU implementer[[:space:]]*:[[:space:]]*0x43' "$procfs/cpuinfo"; then |
|
cpu_vendor='CAVIUM' |
|
fi |
|
|
|
cpu_family=$( grep '^cpu family' "$procfs/cpuinfo" | awk '{print $4}' | grep -E '^[0-9]+$' | head -1) |
|
cpu_model=$( grep '^model' "$procfs/cpuinfo" | awk '{print $3}' | grep -E '^[0-9]+$' | head -1) |
|
cpu_stepping=$(grep '^stepping' "$procfs/cpuinfo" | awk '{print $3}' | grep -E '^[0-9]+$' | head -1) |
|
cpu_ucode=$( grep '^microcode' "$procfs/cpuinfo" | awk '{print $3}' | head -1) |
|
else |
|
cpu_vendor=$( dmesg | grep -i -m1 'Origin=' | cut -f2 -w | cut -f2 -d= | cut -f2 -d\" ) |
|
cpu_family=$( dmesg | grep -i -m1 'Family=' | cut -f4 -w | cut -f2 -d= ) |
|
cpu_family=$(( cpu_family )) |
|
cpu_model=$( dmesg | grep -i -m1 'Model=' | cut -f5 -w | cut -f2 -d= ) |
|
cpu_model=$(( cpu_model )) |
|
cpu_stepping=$( dmesg | grep -i -m1 'Stepping=' | cut -f6 -w | cut -f2 -d= ) |
|
cpu_friendly_name=$(sysctl -n hw.model 2>/dev/null) |
|
fi |
|
|
|
if [ -n "$SMC_MOCK_CPU_FRIENDLY_NAME" ]; then |
|
cpu_friendly_name="$SMC_MOCK_CPU_FRIENDLY_NAME" |
|
_debug "parse_cpu_details: MOCKING cpu friendly name to $cpu_friendly_name" |
|
mocked=1 |
|
else |
|
mockme=$(printf "%b\n%b" "$mockme" "SMC_MOCK_CPU_FRIENDLY_NAME='$cpu_friendly_name'") |
|
fi |
|
if [ -n "$SMC_MOCK_CPU_VENDOR" ]; then |
|
cpu_vendor="$SMC_MOCK_CPU_VENDOR" |
|
_debug "parse_cpu_details: MOCKING cpu vendor to $cpu_vendor" |
|
mocked=1 |
|
else |
|
mockme=$(printf "%b\n%b" "$mockme" "SMC_MOCK_CPU_VENDOR='$cpu_vendor'") |
|
fi |
|
if [ -n "$SMC_MOCK_CPU_FAMILY" ]; then |
|
cpu_family="$SMC_MOCK_CPU_FAMILY" |
|
_debug "parse_cpu_details: MOCKING cpu family to $cpu_family" |
|
mocked=1 |
|
else |
|
mockme=$(printf "%b\n%b" "$mockme" "SMC_MOCK_CPU_FAMILY='$cpu_family'") |
|
fi |
|
if [ -n "$SMC_MOCK_CPU_MODEL" ]; then |
|
cpu_model="$SMC_MOCK_CPU_MODEL" |
|
_debug "parse_cpu_details: MOCKING cpu model to $cpu_model" |
|
mocked=1 |
|
else |
|
mockme=$(printf "%b\n%b" "$mockme" "SMC_MOCK_CPU_MODEL='$cpu_model'") |
|
fi |
|
if [ -n "$SMC_MOCK_CPU_STEPPING" ]; then |
|
cpu_stepping="$SMC_MOCK_CPU_STEPPING" |
|
_debug "parse_cpu_details: MOCKING cpu stepping to $cpu_stepping" |
|
mocked=1 |
|
else |
|
mockme=$(printf "%b\n%b" "$mockme" "SMC_MOCK_CPU_STEPPING='$cpu_stepping'") |
|
fi |
|
|
|
# get raw cpuid, it's always useful (referenced in the Intel doc for firmware updates for example) |
|
if read_cpuid 0x1 $EAX 0 0xFFFFFFFF; then |
|
cpu_cpuid="$read_cpuid_value" |
|
else |
|
cpu_cpuid=0 |
|
fi |
|
|
|
# under BSD, linprocfs often doesn't export ucode information, so fetch it ourselves the good old way |
|
if [ -z "$cpu_ucode" ] && [ "$os" != Linux ]; then |
|
load_cpuid |
|
if [ -e /dev/cpuctl0 ]; then |
|
# init MSR with NULLs |
|
cpucontrol -m 0x8b=0 /dev/cpuctl0 |
|
# call CPUID |
|
cpucontrol -i 1 /dev/cpuctl0 >/dev/null |
|
# read MSR |
|
cpu_ucode=$(cpucontrol -m 0x8b /dev/cpuctl0 | awk '{print $3}') |
|
# convert to decimal |
|
cpu_ucode=$(( cpu_ucode )) |
|
# convert back to hex |
|
cpu_ucode=$(printf "0x%x" "$cpu_ucode") |
|
fi |
|
fi |
|
|
|
# if we got no cpu_ucode (e.g. we're in a vm), fall back to 0x0 |
|
[ -z "$cpu_ucode" ] && cpu_ucode=0x0 |
|
|
|
if [ -n "$SMC_MOCK_CPU_UCODE" ]; then |
|
cpu_ucode="$SMC_MOCK_CPU_UCODE" |
|
_debug "parse_cpu_details: MOCKING cpu ucode to $cpu_ucode" |
|
mocked=1 |
|
else |
|
mockme=$(printf "%b\n%b" "$mockme" "SMC_MOCK_CPU_UCODE='$cpu_ucode'") |
|
fi |
|
|
|
echo "$cpu_ucode" | grep -q ^0x && cpu_ucode=$(( cpu_ucode )) |
|
ucode_found=$(printf "family 0x%x model 0x%x stepping 0x%x ucode 0x%x cpuid 0x%x" "$cpu_family" "$cpu_model" "$cpu_stepping" "$cpu_ucode" "$cpu_cpuid") |
|
|
|
# also define those that we will need in other funcs |
|
# taken from https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/include/asm/intel-family.h |
|
# curl -s 'https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/plain/arch/x86/include/asm/intel-family.h' | awk '/#define INTEL_FAM6/ {print $2"=$(( "$3" )) # "$4,$5,$6,$7,$8,$9}' |
|
# shellcheck disable=SC2034 |
|
{ |
|
INTEL_FAM6_CORE_YONAH=$(( 0x0E )) # |
|
INTEL_FAM6_CORE2_MEROM=$(( 0x0F )) # |
|
INTEL_FAM6_CORE2_MEROM_L=$(( 0x16 )) # |
|
INTEL_FAM6_CORE2_PENRYN=$(( 0x17 )) # |
|
INTEL_FAM6_CORE2_DUNNINGTON=$(( 0x1D )) # |
|
INTEL_FAM6_NEHALEM=$(( 0x1E )) # |
|
INTEL_FAM6_NEHALEM_G=$(( 0x1F )) # /* Auburndale / Havendale */ |
|
INTEL_FAM6_NEHALEM_EP=$(( 0x1A )) # |
|
INTEL_FAM6_NEHALEM_EX=$(( 0x2E )) # |
|
INTEL_FAM6_WESTMERE=$(( 0x25 )) # |
|
INTEL_FAM6_WESTMERE_EP=$(( 0x2C )) # |
|
INTEL_FAM6_WESTMERE_EX=$(( 0x2F )) # |
|
INTEL_FAM6_SANDYBRIDGE=$(( 0x2A )) # |
|
INTEL_FAM6_SANDYBRIDGE_X=$(( 0x2D )) # |
|
INTEL_FAM6_IVYBRIDGE=$(( 0x3A )) # |
|
INTEL_FAM6_IVYBRIDGE_X=$(( 0x3E )) # |
|
INTEL_FAM6_HASWELL=$(( 0x3C )) # |
|
INTEL_FAM6_HASWELL_X=$(( 0x3F )) # |
|
INTEL_FAM6_HASWELL_L=$(( 0x45 )) # |
|
INTEL_FAM6_HASWELL_G=$(( 0x46 )) # |
|
INTEL_FAM6_BROADWELL=$(( 0x3D )) # |
|
INTEL_FAM6_BROADWELL_G=$(( 0x47 )) # |
|
INTEL_FAM6_BROADWELL_X=$(( 0x4F )) # |
|
INTEL_FAM6_BROADWELL_D=$(( 0x56 )) # |
|
INTEL_FAM6_SKYLAKE_L=$(( 0x4E )) # |
|
INTEL_FAM6_SKYLAKE=$(( 0x5E )) # |
|
INTEL_FAM6_SKYLAKE_X=$(( 0x55 )) # |
|
INTEL_FAM6_KABYLAKE_L=$(( 0x8E )) # |
|
INTEL_FAM6_KABYLAKE=$(( 0x9E )) # |
|
INTEL_FAM6_CANNONLAKE_L=$(( 0x66 )) # |
|
INTEL_FAM6_ICELAKE_X=$(( 0x6A )) # |
|
INTEL_FAM6_ICELAKE_D=$(( 0x6C )) # |
|
INTEL_FAM6_ICELAKE=$(( 0x7D )) # |
|
INTEL_FAM6_ICELAKE_L=$(( 0x7E )) # |
|
INTEL_FAM6_ICELAKE_NNPI=$(( 0x9D )) # |
|
INTEL_FAM6_TIGERLAKE_L=$(( 0x8C )) # |
|
INTEL_FAM6_TIGERLAKE=$(( 0x8D )) # |
|
INTEL_FAM6_COMETLAKE=$(( 0xA5 )) # |
|
INTEL_FAM6_COMETLAKE_L=$(( 0xA6 )) # |
|
INTEL_FAM6_ATOM_BONNELL=$(( 0x1C )) # /* Diamondville, Pineview */ |
|
INTEL_FAM6_ATOM_BONNELL_MID=$(( 0x26 )) # /* Silverthorne, Lincroft */ |
|
INTEL_FAM6_ATOM_SALTWELL=$(( 0x36 )) # /* Cedarview */ |
|
INTEL_FAM6_ATOM_SALTWELL_MID=$(( 0x27 )) # /* Penwell */ |
|
INTEL_FAM6_ATOM_SALTWELL_TABLET=$(( 0x35 )) # /* Cloverview */ |
|
INTEL_FAM6_ATOM_SILVERMONT=$(( 0x37 )) # /* Bay Trail, Valleyview */ |
|
INTEL_FAM6_ATOM_SILVERMONT_D=$(( 0x4D )) # /* Avaton, Rangely */ |
|
INTEL_FAM6_ATOM_SILVERMONT_MID=$(( 0x4A )) # /* Merriefield */ |
|
INTEL_FAM6_ATOM_AIRMONT=$(( 0x4C )) # /* Cherry Trail, Braswell */ |
|
INTEL_FAM6_ATOM_AIRMONT_MID=$(( 0x5A )) # /* Moorefield */ |
|
INTEL_FAM6_ATOM_AIRMONT_NP=$(( 0x75 )) # /* Lightning Mountain */ |
|
INTEL_FAM6_ATOM_GOLDMONT=$(( 0x5C )) # /* Apollo Lake */ |
|
INTEL_FAM6_ATOM_GOLDMONT_D=$(( 0x5F )) # /* Denverton */ |
|
INTEL_FAM6_ATOM_GOLDMONT_PLUS=$(( 0x7A )) # /* Gemini Lake */ |
|
INTEL_FAM6_ATOM_TREMONT_D=$(( 0x86 )) # /* Jacobsville */ |
|
INTEL_FAM6_ATOM_TREMONT=$(( 0x96 )) # /* Elkhart Lake */ |
|
INTEL_FAM6_XEON_PHI_KNL=$(( 0x57 )) # /* Knights Landing */ |
|
INTEL_FAM6_XEON_PHI_KNM=$(( 0x85 )) # /* Knights Mill */ |
|
INTEL_FAM6_CORE_YONAH=$(( 0x0E )) |
|
} |
|
parse_cpu_details_done=1 |
|
} |
|
is_hygon() |
|
{ |
|
[ "$cpu_vendor" = HygonGenuine ] && return 0 |
|
return 1 |
|
} |
|
|
|
is_amd() |
|
{ |
|
[ "$cpu_vendor" = AuthenticAMD ] && return 0 |
|
return 1 |
|
} |
|
|
|
is_intel() |
|
{ |
|
[ "$cpu_vendor" = GenuineIntel ] && return 0 |
|
return 1 |
|
} |
|
|
|
is_cpu_smt_enabled() |
|
{ |
|
# SMT / HyperThreading is enabled if siblings != cpucores |
|
if [ -e "$procfs/cpuinfo" ]; then |
|
_siblings=$(awk '/^siblings/ {print $3;exit}' "$procfs/cpuinfo") |
|
_cpucores=$(awk '/^cpu cores/ {print $4;exit}' "$procfs/cpuinfo") |
|
if [ -n "$_siblings" ] && [ -n "$_cpucores" ]; then |
|
if [ "$_siblings" = "$_cpucores" ]; then |
|
return 1 |
|
else |
|
return 0 |
|
fi |
|
fi |
|
fi |
|
# we can't tell |
|
return 2 |
|
} |
|
|
|
is_ucode_blacklisted() |
|
{ |
|
parse_cpu_details |
|
# if it's not an Intel, don't bother: it's not blacklisted |
|
is_intel || return 1 |
|
# it also needs to be family=6 |
|
[ "$cpu_family" = 6 ] || return 1 |
|
# now, check each known bad microcode |
|
# source: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/kernel/cpu/intel.c#n105 |
|
# 2018-02-08 update: https://newsroom.intel.com/wp-content/uploads/sites/11/2018/02/microcode-update-guidance.pdf |
|
# model,stepping,microcode |
|
for tuple in \ |
|
$INTEL_FAM6_KABYLAKE_DESKTOP,0x0B,0x80 \ |
|
$INTEL_FAM6_KABYLAKE_DESKTOP,0x0A,0x80 \ |
|
$INTEL_FAM6_KABYLAKE_DESKTOP,0x09,0x80 \ |
|
$INTEL_FAM6_KABYLAKE_MOBILE,0x0A,0x80 \ |
|
$INTEL_FAM6_KABYLAKE_MOBILE,0x09,0x80 \ |
|
$INTEL_FAM6_SKYLAKE_X,0x03,0x0100013e \ |
|
$INTEL_FAM6_SKYLAKE_X,0x04,0x02000036 \ |
|
$INTEL_FAM6_SKYLAKE_X,0x04,0x0200003a \ |
|
$INTEL_FAM6_SKYLAKE_X,0x04,0x0200003c \ |
|
$INTEL_FAM6_BROADWELL_CORE,0x04,0x28 \ |
|
$INTEL_FAM6_BROADWELL_GT3E,0x01,0x1b \ |
|
$INTEL_FAM6_BROADWELL_XEON_D,0x02,0x14 \ |
|
$INTEL_FAM6_BROADWELL_XEON_D,0x03,0x07000011 \ |
|
$INTEL_FAM6_BROADWELL_X,0x01,0x0b000023 \ |
|
$INTEL_FAM6_BROADWELL_X,0x01,0x0b000025 \ |
|
$INTEL_FAM6_HASWELL_L,0x01,0x21 \ |
|
$INTEL_FAM6_HASWELL_GT3E,0x01,0x18 \ |
|
$INTEL_FAM6_HASWELL_CORE,0x03,0x23 \ |
|
$INTEL_FAM6_HASWELL_X,0x02,0x3b \ |
|
$INTEL_FAM6_HASWELL_X,0x04,0x10 \ |
|
$INTEL_FAM6_IVYBRIDGE_X,0x04,0x42a \ |
|
$INTEL_FAM6_SANDYBRIDGE_X,0x06,0x61b \ |
|
$INTEL_FAM6_SANDYBRIDGE_X,0x07,0x712 |
|
do |
|
model=$(echo "$tuple" | cut -d, -f1) |
|
stepping=$(( $(echo "$tuple" | cut -d, -f2) )) |
|
if [ "$cpu_model" = "$model" ] && [ "$cpu_stepping" = "$stepping" ]; then |
|
ucode=$(( $(echo "$tuple" | cut -d, -f3) )) |
|
if [ "$cpu_ucode" = "$ucode" ]; then |
|
_debug "is_ucode_blacklisted: we have a match! ($cpu_model/$cpu_stepping/$cpu_ucode)" |
|
return 0 |
|
fi |
|
fi |
|
done |
|
_debug "is_ucode_blacklisted: no ($cpu_model/$cpu_stepping/$cpu_ucode)" |
|
return 1 |
|
} |
|
|
|
is_skylake_cpu() |
|
{ |
|
# is this a skylake cpu? |
|
# return 0 if yes, 1 otherwise |
|
#if (boot_cpu_data.x86_vendor == X86_VENDOR_INTEL && |
|
# boot_cpu_data.x86 == 6) { |
|
# switch (boot_cpu_data.x86_model) { |
|
# case INTEL_FAM6_SKYLAKE_MOBILE: |
|
# case INTEL_FAM6_SKYLAKE_DESKTOP: |
|
# case INTEL_FAM6_SKYLAKE_X: |
|
# case INTEL_FAM6_KABYLAKE_MOBILE: |
|
# case INTEL_FAM6_KABYLAKE_DESKTOP: |
|
# return true; |
|
parse_cpu_details |
|
is_intel || return 1 |
|
[ "$cpu_family" = 6 ] || return 1 |
|
if [ "$cpu_model" = $INTEL_FAM6_SKYLAKE_L ] || \ |
|
[ "$cpu_model" = $INTEL_FAM6_SKYLAKE ] || \ |
|
[ "$cpu_model" = $INTEL_FAM6_SKYLAKE_X ] || \ |
|
[ "$cpu_model" = $INTEL_FAM6_KABYLAKE_L ] || \ |
|
[ "$cpu_model" = $INTEL_FAM6_KABYLAKE ]; then |
|
return 0 |
|
fi |
|
return 1 |
|
} |
|
|
|
is_vulnerable_to_empty_rsb() |
|
{ |
|
if is_intel && [ -z "$capabilities_rsba" ]; then |
|
_warn "is_vulnerable_to_empty_rsb() called before ARCH CAPABILITIES MSR was read" |
|
fi |
|
if is_skylake_cpu || [ "$capabilities_rsba" = 1 ]; then |
|
return 0 |
|
fi |
|
return 1 |
|
} |
|
|
|
is_zen_cpu() |
|
{ |
|
# is this CPU from the AMD ZEN family ? (ryzen, epyc, ...) |
|
parse_cpu_details |
|
is_amd || return 1 |
|
[ "$cpu_family" = 23 ] && return 0 |
|
return 1 |
|
} |
|
is_moksha_cpu() |
|
{ |
|
parse_cpu_details |
|
is_hygon || return 1 |
|
[ "$cpu_family" = 24 ] && return 0 |
|
return 1 |
|
} |
|
|
|
# Test if the current host is a Xen PV Dom0 / DomU |
|
is_xen() { |
|
if [ ! -d "$procfs/xen" ]; then |
|
return 1 |
|
fi |
|
|
|
# XXX do we have a better way that relying on dmesg? |
|
dmesg_grep 'Booting paravirtualized kernel on Xen$'; ret=$? |
|
if [ $ret -eq 2 ]; then |
|
_warn "dmesg truncated, Xen detection will be unreliable. Please reboot and relaunch this script" |
|
return 1 |
|
elif [ $ret -eq 0 ]; then |
|
return 0 |
|
else |
|
return 1 |
|
fi |
|
} |
|
|
|
is_xen_dom0() |
|
{ |
|
if ! is_xen; then |
|
return 1 |
|
fi |
|
|
|
if [ -e "$procfs/xen/capabilities" ] && grep -q "control_d" "$procfs/xen/capabilities"; then |
|
return 0 |
|
else |
|
return 1 |
|
fi |
|
} |
|
|
|
is_xen_domU() |
|
{ |
|
if ! is_xen; then |
|
return 1 |
|
fi |
|
|
|
# PVHVM guests also print 'Booting paravirtualized kernel', so we need this check. |
|
dmesg_grep 'Xen HVM callback vector for event delivery is enabled$'; ret=$? |
|
if [ $ret -eq 0 ]; then |
|
return 1 |
|
fi |
|
|
|
if ! is_xen_dom0; then |
|
return 0 |
|
else |
|
return 1 |
|
fi |
|
} |
|
|
|
builtin_dbversion=$(awk '/^# %%% MCEDB / { print $4 }' "$0") |
|
if [ -r "$mcedb_cache" ]; then |
|
# we have a local cache file, but it might be older than the builtin version we have |
|
local_dbversion=$( awk '/^# %%% MCEDB / { print $4 }' "$mcedb_cache") |
|
# sort -V sorts by version number |
|
older_dbversion=$(printf "%b\n%b" "$local_dbversion" "$builtin_dbversion" | sort -V | head -n1) |
|
if [ "$older_dbversion" = "$builtin_dbversion" ]; then |
|
mcedb_source="$mcedb_cache" |
|
mcedb_info="local firmwares DB $local_dbversion" |
|
fi |
|
fi |
|
# if mcedb_source is not set, either we don't have a local cached db, or it is older than the builtin db |
|
if [ -z "$mcedb_source" ]; then |
|
mcedb_source="$0" |
|
mcedb_info="builtin firmwares DB $builtin_dbversion" |
|
fi |
|
read_mcedb() |
|
{ |
|
awk '{ if (DELIM==1) { print $2 } } /^# %%% MCEDB / { DELIM=1 }' "$mcedb_source" |
|
} |
|
|
|
is_latest_known_ucode() |
|
{ |
|
# 0: yes, 1: no, 2: unknown |
|
parse_cpu_details |
|
if [ "$cpu_cpuid" = 0 ]; then |
|
ucode_latest="couldn't get your cpuid" |
|
return 2 |
|
fi |
|
ucode_latest="latest microcode version for your CPU model is unknown" |
|
if is_intel; then |
|
cpu_brand_prefix=I |
|
elif is_amd; then |
|
cpu_brand_prefix=A |
|
else |
|
return 2 |
|
fi |
|
for tuple in $(read_mcedb | grep "$(printf "^$cpu_brand_prefix,0x%08X," "$cpu_cpuid")") |
|
do |
|
ucode=$(( $(echo "$tuple" | cut -d, -f3) )) |
|
ucode_date=$(echo "$tuple" | cut -d, -f4 | sed -r 's=(....)(..)(..)=\1/\2/\3=') |
|
_debug "is_latest_known_ucode: with cpuid $cpu_cpuid has ucode $cpu_ucode, last known is $ucode from $ucode_date" |
|
ucode_latest=$(printf "latest version is 0x%x dated $ucode_date according to $mcedb_info" "$ucode") |
|
if [ "$cpu_ucode" -ge "$ucode" ]; then |
|
return 0 |
|
else |
|
return 1 |
|
fi |
|
done |
|
_debug "is_latest_known_ucode: this cpuid is not referenced ($cpu_cpuid)" |
|
return 2 |
|
} |
|
|
|
get_cmdline() |
|
{ |
|
if [ -n "$kernel_cmdline" ]; then |
|
return |
|
fi |
|
|
|
if [ -n "$SMC_MOCK_CMDLINE" ]; then |
|
mocked=1 |
|
_debug "get_cmdline: using mocked cmdline '$SMC_MOCK_CMDLINE'" |
|
kernel_cmdline="$SMC_MOCK_CMDLINE" |
|
return |
|
else |
|
kernel_cmdline=$(cat "$procfs/cmdline") |
|
mockme=$(printf "%b\n%b" "$mockme" "SMC_MOCK_CMDLINE='$kernel_cmdline'") |
|
fi |
|
} |
|
|
|
# ENTRYPOINT |
|
|
|
# we can't do anything useful under WSL |
|
if uname -a | grep -qE -- '-Microsoft #[0-9]+-Microsoft '; then |
|
_warn "This script doesn't work under Windows Subsystem for Linux" |
|
_warn "You should use the official Microsoft tool instead." |
|
_warn "It can be found under https://aka.ms/SpeculationControlPS" |
|
exit 1 |
|
fi |
|
|
|
# check for mode selection inconsistency |
|
if [ "$opt_hw_only" = 1 ]; then |
|
if [ "$opt_cve_all" = 0 ]; then |
|
show_usage |
|
echo "$0: error: incompatible modes specified, --hw-only vs --variant" >&2 |
|
exit 255 |
|
else |
|
opt_cve_all=0 |
|
opt_cve_list='' |
|
fi |
|
fi |
|
|
|
# coreos mode |
|
if [ "$opt_coreos" = 1 ]; then |
|
if ! is_coreos; then |
|
_warn "CoreOS mode asked, but we're not under CoreOS!" |
|
exit 255 |
|
fi |
|
_warn "CoreOS mode, starting an ephemeral toolbox to launch the script" |
|
load_msr |
|
load_cpuid |
|
mount_debugfs |
|
toolbox --ephemeral --bind-ro /dev/cpu:/dev/cpu -- sh -c "dnf install -y binutils which && /media/root$PWD/$0 $* --coreos-within-toolbox" |
|
exitcode=$? |
|
exit $exitcode |
|
else |
|
if is_coreos; then |
|
_warn "You seem to be running CoreOS, you might want to use the --coreos option for better results" |
|
_warn |
|
fi |
|
fi |
|
|
|
# if we're under a BSD, try to mount linprocfs for "$procfs/cpuinfo" |
|
procfs=/proc |
|
if echo "$os" | grep -q BSD; then |
|
_debug "We're under BSD, check if we have procfs" |
|
procfs=$(mount | awk '/^linprocfs/ { print $3; exit; }') |
|
if [ -z "$procfs" ]; then |
|
_debug "we don't, try to mount it" |
|
procfs=/proc |
|
[ -d /compat/linux/proc ] && procfs=/compat/linux/proc |
|
test -d $procfs || mkdir $procfs |
|
if mount -t linprocfs linprocfs $procfs 2>/dev/null; then |
|
mounted_procfs=1 |
|
_debug "procfs just mounted at $procfs" |
|
else |
|
procfs='' |
|
fi |
|
else |
|
_debug "We do: $procfs" |
|
fi |
|
fi |
|
|
|
parse_cpu_details |
|
get_cmdline |
|
|
|
if [ "$opt_live" = 1 ]; then |
|
# root check (only for live mode, for offline mode, we already checked if we could read the files) |
|
if [ "$(id -u)" -ne 0 ]; then |
|
_warn "Note that you should launch this script with root privileges to get accurate information." |
|
_warn "We'll proceed but you might see permission denied errors." |
|
_warn "To run it as root, you can try the following command: sudo $0" |
|
_warn |
|
fi |
|
_info "Checking for vulnerabilities on current system" |
|
_info "Kernel is \033[35m$(uname -s) $(uname -r) $(uname -v) $(uname -m)\033[0m" |
|
_info "CPU is \033[35m$cpu_friendly_name\033[0m" |
|
|
|
# try to find the image of the current running kernel |
|
if [ -n "$opt_kernel" ]; then |
|
# specified by user on cmdline, with --live, don't override |
|
: |
|
# first, look for the BOOT_IMAGE hint in the kernel cmdline |
|
elif echo "$kernel_cmdline" | grep -q 'BOOT_IMAGE='; then |
|
opt_kernel=$(echo "$kernel_cmdline" | grep -Eo 'BOOT_IMAGE=[^ ]+' | cut -d= -f2) |
|
_debug "found opt_kernel=$opt_kernel in $procfs/cmdline" |
|
# if the boot partition is within a btrfs subvolume, strip the subvolume name |
|
# if /boot is a separate subvolume, the remainder of the code in this section should handle it |
|
if echo "$opt_kernel" | grep -q "^/@"; then opt_kernel=$(echo "$opt_kernel" | sed "s:/@[^/]*::"); fi |
|
# if we have a dedicated /boot partition, our bootloader might have just called it / |
|
# so try to prepend /boot and see if we find anything |
|
[ -e "/boot/$opt_kernel" ] && opt_kernel="/boot/$opt_kernel" |
|
# special case for CoreOS if we're inside the toolbox |
|
[ -e "/media/root/boot/$opt_kernel" ] && opt_kernel="/media/root/boot/$opt_kernel" |
|
_debug "opt_kernel is now $opt_kernel" |
|
# else, the full path is already there (most probably /boot/something) |
|
fi |
|
# if we didn't find a kernel, default to guessing |
|
if [ ! -e "$opt_kernel" ]; then |
|
# Fedora: |
|
[ -e "/lib/modules/$(uname -r)/vmlinuz" ] && opt_kernel="/lib/modules/$(uname -r)/vmlinuz" |
|
# Slackware: |
|
[ -e "/boot/vmlinuz" ] && opt_kernel="/boot/vmlinuz" |
|
# Arch aarch64: |
|
[ -e "/boot/Image" ] && opt_kernel="/boot/Image" |
|
# Arch armv5/armv7: |
|
[ -e "/boot/zImage" ] && opt_kernel="/boot/zImage" |
|
# Arch arm7: |
|
[ -e "/boot/kernel7.img" ] && opt_kernel="/boot/kernel7.img" |
|
# Linux-Libre: |
|
[ -e "/boot/vmlinuz-linux-libre" ] && opt_kernel="/boot/vmlinuz-linux-libre" |
|
# pine64 |
|
[ -e "/boot/pine64/Image" ] && opt_kernel="/boot/pine64/Image" |
|
# generic: |
|
[ -e "/boot/vmlinuz-$(uname -r)" ] && opt_kernel="/boot/vmlinuz-$(uname -r)" |
|
[ -e "/boot/kernel-$( uname -r)" ] && opt_kernel="/boot/kernel-$( uname -r)" |
|
[ -e "/boot/bzImage-$(uname -r)" ] && opt_kernel="/boot/bzImage-$(uname -r)" |
|
# Gentoo: |
|
[ -e "/boot/kernel-genkernel-$(uname -m)-$(uname -r)" ] && opt_kernel="/boot/kernel-genkernel-$(uname -m)-$(uname -r)" |
|
# NixOS: |
|
[ -e "/run/booted-system/kernel" ] && opt_kernel="/run/booted-system/kernel" |
|
# systemd kernel-install: |
|
[ -e "/etc/machine-id" ] && [ -e "/boot/$(cat /etc/machine-id)/$(uname -r)/linux" ] && opt_kernel="/boot/$(cat /etc/machine-id)/$(uname -r)/linux" |
|
# Clear Linux: |
|
str_uname=$(uname -r) |
|
clear_linux_kernel="/lib/kernel/org.clearlinux.${str_uname##*.}.${str_uname%.*}" |
|
[ -e "$clear_linux_kernel" ] && opt_kernel=$clear_linux_kernel |
|
# Custom Arch seems to have the kernel path in its cmdline in the form "\directory\kernelimage", |
|
# with actual \'s instead of /'s: |
|
custom_arch_kernel=$(echo "$kernel_cmdline" | grep -Eo "(^|\s)\\\\[\\\\a-zA-Z0-9_.-]+" | tr "\\\\" "/" | tr -d '[:space:]') |
|
if [ -n "$custom_arch_kernel" ] && [ -e "$custom_arch_kernel" ]; then |
|
opt_kernel="$custom_arch_kernel" |
|
fi |
|
# FreeBSD: |
|
[ -e "/boot/kernel/kernel" ] && opt_kernel="/boot/kernel/kernel" |
|
fi |
|
|
|
# system.map |
|
if [ -n "$opt_map" ]; then |
|
# specified by user on cmdline, with --live, don't override |
|
: |
|
elif [ -e "$procfs/kallsyms" ] ; then |
|
opt_map="$procfs/kallsyms" |
|
elif [ -e "/lib/modules/$(uname -r)/System.map" ] ; then |
|
opt_map="/lib/modules/$(uname -r)/System.map" |
|
elif [ -e "/boot/System.map-$(uname -r)" ] ; then |
|
opt_map="/boot/System.map-$(uname -r)" |
|
elif [ -e "/lib/kernel/System.map-$(uname -r)" ]; then |
|
opt_map="/lib/kernel/System.map-$(uname -r)" |
|
fi |
|
|
|
# config |
|
if [ -n "$opt_config" ]; then |
|
# specified by user on cmdline, with --live, don't override |
|
: |
|
elif [ -e "$procfs/config.gz" ] ; then |
|
dumped_config="$(mktemp /tmp/config-XXXXXX)" |
|
gunzip -c "$procfs/config.gz" > "$dumped_config" |
|
# dumped_config will be deleted at the end of the script |
|
opt_config="$dumped_config" |
|
elif [ -e "/lib/modules/$(uname -r)/config" ]; then |
|
opt_config="/lib/modules/$(uname -r)/config" |
|
elif [ -e "/boot/config-$(uname -r)" ]; then |
|
opt_config="/boot/config-$(uname -r)" |
|
elif [ -e "/etc/kernels/kernel-config-$(uname -m)-$(uname -r)" ]; then |
|
opt_config="/etc/kernels/kernel-config-$(uname -m)-$(uname -r)" |
|
elif [ -e "/lib/kernel/config-$(uname -r)" ]; then |
|
opt_config="/lib/kernel/config-$(uname -r)" |
|
fi |
|
else |
|
_info "Checking for vulnerabilities against specified kernel" |
|
_info "CPU is \033[35m$cpu_friendly_name\033[0m" |
|
fi |
|
|
|
if [ -n "$opt_kernel" ]; then |
|
_verbose "Will use kernel image \033[35m$opt_kernel\033[0m" |
|
else |
|
_verbose "Will use no kernel image (accuracy might be reduced)" |
|
bad_accuracy=1 |
|
fi |
|
|
|
if [ "$os" = Linux ]; then |
|
if [ -n "$opt_config" ] && ! grep -q '^CONFIG_' "$opt_config"; then |
|
# given file is invalid! |
|
_warn "The kernel config file seems invalid, was expecting a plain-text file, ignoring it!" |
|
opt_config='' |
|
fi |
|
|
|
if [ -n "$dumped_config" ] && [ -n "$opt_config" ]; then |
|
_verbose "Will use kconfig \033[35m$procfs/config.gz (decompressed)\033[0m" |
|
elif [ -n "$opt_config" ]; then |
|
_verbose "Will use kconfig \033[35m$opt_config\033[0m" |
|
else |
|
_verbose "Will use no kconfig (accuracy might be reduced)" |
|
bad_accuracy=1 |
|
fi |
|
|
|
if [ -n "$opt_map" ]; then |
|
_verbose "Will use System.map file \033[35m$opt_map\033[0m" |
|
else |
|
_verbose "Will use no System.map file (accuracy might be reduced)" |
|
bad_accuracy=1 |
|
fi |
|
|
|
if [ "$bad_accuracy" = 1 ]; then |
|
_warn "We're missing some kernel info (see -v), accuracy might be reduced" |
|
fi |
|
fi |
|
|
|
if [ -e "$opt_kernel" ]; then |
|
if ! command -v "${opt_arch_prefix}readelf" >/dev/null 2>&1; then |
|
_debug "readelf not found" |
|
kernel_err="missing '${opt_arch_prefix}readelf' tool, please install it, usually it's in the 'binutils' package" |
|
elif [ "$opt_sysfs_only" = 1 ] || [ "$opt_hw_only" = 1 ]; then |
|
kernel_err='kernel image decompression skipped' |
|
else |
|
extract_kernel "$opt_kernel" |
|
fi |
|
else |
|
_debug "no opt_kernel defined" |
|
kernel_err="couldn't find your kernel image in /boot, if you used netboot, this is normal" |
|
fi |
|
if [ -z "$kernel" ] || [ ! -r "$kernel" ]; then |
|
[ -z "$kernel_err" ] && kernel_err="couldn't extract your kernel from $opt_kernel" |
|
else |
|
# vanilla kernels have with ^Linux version |
|
# also try harder with some kernels (such as Red Hat) that don't have ^Linux version before their version string |
|
# and check for FreeBSD |
|
kernel_version=$("${opt_arch_prefix}strings" "$kernel" 2>/dev/null | grep -E \ |
|
-e '^Linux version ' \ |
|
-e '^[[:alnum:]][^[:space:]]+ \([^[:space:]]+\) #[0-9]+ .+ (19|20)[0-9][0-9]$' \ |
|
-e '^FreeBSD [0-9]' | head -1) |
|
if [ -z "$kernel_version" ]; then |
|
# try even harder with some kernels (such as ARM) that split the release (uname -r) and version (uname -v) in 2 adjacent strings |
|
kernel_version=$("${opt_arch_prefix}strings" "$kernel" 2>/dev/null | grep -E -B1 '^#[0-9]+ .+ (19|20)[0-9][0-9]$' | tr "\n" " ") |
|
fi |
|
if [ -n "$kernel_version" ]; then |
|
# in live mode, check if the img we found is the correct one |
|
if [ "$opt_live" = 1 ]; then |
|
_verbose "Kernel image is \033[35m$kernel_version" |
|
if ! echo "$kernel_version" | grep -qF "$(uname -r)"; then |
|
_warn "Possible discrepancy between your running kernel '$(uname -r)' and the image '$kernel_version' we found ($opt_kernel), results might be incorrect" |
|
fi |
|
else |
|
_info "Kernel image is \033[35m$kernel_version" |
|
fi |
|
else |
|
_verbose "Kernel image version is unknown" |
|
fi |
|
fi |
|
|
|
_info |
|
|
|
# end of header stuff |
|
|
|
# now we define some util functions and the check_*() funcs, as |
|
# the user can choose to execute only some of those |
|
|
|
sys_interface_check() |
|
{ |
|
file="$1" |
|
regex="$2" |
|
mode="$3" |
|
msg='' |
|
fullmsg='' |
|
|
|
if [ "$opt_live" = 1 ] && [ "$opt_no_sysfs" = 0 ] && [ -r "$file" ]; then |
|
: |
|
else |
|
mockme=$(printf "%b\n%b" "$mockme" "SMC_MOCK_SYSFS_$(basename "$file")_RET=1") |
|
return 1 |
|
fi |
|
|
|
_mockvarname="SMC_MOCK_SYSFS_$(basename "$file")_RET" |
|
# shellcheck disable=SC2086 |
|
if [ -n "$(eval echo \$$_mockvarname)" ]; then |
|
_debug "sysfs: MOCKING enabled for $file func returns $(eval echo \$$_mockvarname)" |
|
mocked=1 |
|
return "$(eval echo \$$_mockvarname)" |
|
fi |
|
|
|
[ -n "$regex" ] || regex='.*' |
|
_mockvarname="SMC_MOCK_SYSFS_$(basename "$file")" |
|
# shellcheck disable=SC2086 |
|
if [ -n "$(eval echo \$$_mockvarname)" ]; then |
|
fullmsg="$(eval echo \$$_mockvarname)" |
|
msg=$(echo "$fullmsg" | grep -Eo "$regex") |
|
_debug "sysfs: MOCKING enabled for $file, will return $fullmsg" |
|
mocked=1 |
|
else |
|
fullmsg=$(cat "$file") |
|
msg=$(grep -Eo "$regex" "$file") |
|
mockme=$(printf "%b\n%b" "$mockme" "SMC_MOCK_SYSFS_$(basename "$file")='$fullmsg'") |
|
fi |
|
if [ "$mode" = silent ]; then |
|
return 0 |
|
elif [ "$mode" = quiet ]; then |
|
_info "* Information from the /sys interface: $fullmsg" |
|
return 0 |
|
fi |
|
_info_nol "* Mitigated according to the /sys interface: " |
|
if echo "$msg" | grep -qi '^not affected'; then |
|
# Not affected |
|
status=OK |
|
pstatus green YES "$fullmsg" |
|
elif echo "$msg" | grep -qEi '^(kvm: )?mitigation'; then |
|
# Mitigation: PTI |
|
status=OK |
|
pstatus green YES "$fullmsg" |
|
elif echo "$msg" | grep -qi '^vulnerable'; then |
|
# Vulnerable |
|
status=VULN |
|
pstatus yellow NO "$fullmsg" |
|
else |
|
status=UNK |
|
pstatus yellow UNKNOWN "$fullmsg" |
|
fi |
|
_debug "sys_interface_check: $file=$msg (re=$regex)" |
|
return 0 |
|
} |
|
|
|
number_of_cpus() |
|
{ |
|
if echo "$os" | grep -q BSD; then |
|
n=$(sysctl -n hw.ncpu 2>/dev/null || echo 1) |
|
elif [ -e "$procfs/cpuinfo" ]; then |
|
n=$(grep -c ^processor "$procfs/cpuinfo" 2>/dev/null || echo 1) |
|
else |
|
# if we don't know, default to 1 CPU |
|
n=1 |
|
fi |
|
return "$n" |
|
} |
|
|
|
# write_msr |
|
# param1 (mandatory): MSR, can be in hex or decimal. |
|
# param2 (optional): CPU index, starting from 0. Default 0. |
|
write_msr() |
|
{ |
|
_msr_dec=$(( $1 )) |
|
_msr=$(printf "0x%x" "$_msr_dec") |
|
_cpu="$2" |
|
[ -z "$_cpu" ] && _cpu=0 |
|
|
|
_mockvarname="SMC_MOCK_WRMSR_${_msr}_RET" |
|
# shellcheck disable=SC2086 |
|
if [ -n "$(eval echo \$$_mockvarname)" ]; then |
|
_debug "write_msr: MOCKING enabled for msr $_msr func returns $(eval echo \$$_mockvarname)" |
|
mocked=1 |
|
[ "$(eval echo \$$_mockvarname)" = 202 ] && msr_locked_down=1 |
|
return "$(eval echo \$$_mockvarname)" |
|
fi |
|
|
|
if [ "$os" != Linux ]; then |
|
cpucontrol -m "$_msr=0" "/dev/cpuctl$_cpu" >/dev/null 2>&1; ret=$? |
|
else |
|
# for Linux |
|
# convert to decimal |
|
if [ ! -w /dev/cpu/"$_cpu"/msr ]; then |
|
ret=200 # permission error |
|
# if wrmsr is available, use it |
|
elif command -v wrmsr >/dev/null 2>&1 && [ "$SMC_NO_WRMSR" != 1 ]; then |
|
_debug "write_msr: using wrmsr" |
|
wrmsr $_msr_dec 0 2>/dev/null; ret=$? |
|
# or if we have perl, use it, any 5.x version will work |
|
elif command -v perl >/dev/null 2>&1 && [ "$SMC_NO_PERL" != 1 ]; then |
|
_debug "write_msr: using perl" |
|
ret=1 |
|
perl -e "open(M,'>','/dev/cpu/$_cpu/msr') and seek(M,$_msr_dec,0) and exit(syswrite(M,pack('H16',0)))"; [ $? -eq 8 ] && ret=0 |
|
# fallback to dd if it supports seek_bytes |
|
elif dd if=/dev/null of=/dev/null bs=8 count=1 seek="$_msr_dec" oflag=seek_bytes 2>/dev/null; then |
|
_debug "write_msr: using dd" |
|
dd if=/dev/zero of=/dev/cpu/"$_cpu"/msr bs=8 count=1 seek="$_msr_dec" oflag=seek_bytes 2>/dev/null; ret=$? |
|
else |
|
_debug "write_msr: got no wrmsr, perl or recent enough dd!" |
|
mockme=$(printf "%b\n%b" "$mockme" "SMC_MOCK_WRMSR_${_msr}_RET=201") |
|
return 201 # missing tool error |
|
fi |
|
if [ "$ret" != 0 ]; then |
|
# * Fedora (and probably Red Hat) have a "kernel lock down" feature that prevents us to write to MSRs |
|
# when this mode is enabled and EFI secure boot is enabled (see issue #303) |
|
# https://src.fedoraproject.org/rpms/kernel/blob/master/f/efi-lockdown.patch |
|
# when this happens, any write will fail and dmesg will have a msg printed "msr: Direct access to MSR" |
|
# * A version of this patch also made it to vanilla in 5.4+, in that case the message is: 'raw MSR access is restricted' |
|
# * we don't use dmesg_grep() because we don't care if dmesg is truncated here, as the message has just been printed |
|
if dmesg | grep -qF "msr: Direct access to MSR"; then |
|
_debug "write_msr: locked down kernel detected (Red Hat / Fedora)" |
|
mockme=$(printf "%b\n%b" "$mockme" "SMC_MOCK_WRMSR_${_msr}_RET=202") |
|
msr_locked_down=1 |
|
msr_locked_down_msg="your kernel is locked down (Fedora/Red Hat), please reboot without secure boot and retry" |
|
return 202 # lockdown error |
|
elif dmesg | grep -qF "raw MSR access is restricted"; then |
|
_debug "write_msr: locked down kernel detected (vanilla)" |
|
mockme=$(printf "%b\n%b" "$mockme" "SMC_MOCK_WRMSR_${_msr}_RET=202") |
|
msr_locked_down=1 |
|
msr_locked_down_msg="your kernel is locked down, please reboot with lockdown=none in the kernel cmdline and retry" |
|
return 202 # lockdown error |
|
fi |
|
fi |
|
fi |
|
# normalize ret |
|
[ "$ret" != 0 ] && ret=1 |
|
_debug "write_msr: for cpu $_cpu on msr $_msr, ret=$ret" |
|
mockme=$(printf "%b\n%b" "$mockme" "SMC_MOCK_WRMSR_${_msr}_RET=$ret") |
|
return $ret |
|
} |
|
|
|
# read_msr |
|
# param1 (mandatory): MSR, can be in hex or decimal. |
|
# param2 (optional): CPU index, starting from 0. Default 0. |
|
read_msr() |
|
{ |
|
_msr_dec=$(( $1 )) |
|
_msr=$(printf "0x%x" "$_msr_dec") |
|
_cpu="$2" |
|
[ -z "$_cpu" ] && _cpu=0 |
|
|
|
read_msr_value='' |
|
|
|
_mockvarname="SMC_MOCK_RDMSR_${_msr}" |
|
# shellcheck disable=SC2086 |
|
if [ -n "$(eval echo \$$_mockvarname)" ]; then |
|
read_msr_value="$(eval echo \$$_mockvarname)" |
|
_debug "read_msr: MOCKING enabled for msr $_msr, returning $read_msr_value" |
|
mocked=1 |
|
return 0 |
|
fi |
|
|
|
_mockvarname="SMC_MOCK_RDMSR_${_msr}_RET" |
|
# shellcheck disable=SC2086 |
|
if [ -n "$(eval echo \$$_mockvarname)" ] && [ "$(eval echo \$$_mockvarname)" -ne 0 ]; then |
|
_debug "read_msr: MOCKING enabled for msr $_msr func returns $(eval echo \$$_mockvarname)" |
|
mocked=1 |
|
return "$(eval echo \$$_mockvarname)" |
|
fi |
|
|
|
if [ "$os" != Linux ]; then |
|
# for BSD |
|
_msr=$(cpucontrol -m "$_msr" "/dev/cpuctl$_cpu" 2>/dev/null); ret=$? |
|
if [ $ret -ne 0 ]; then |
|
mockme=$(printf "%b\n%b" "$mockme" "SMC_MOCK_RDMSR_${_msr}_RET=1") |
|
return 1 |
|
fi |
|
# MSR 0x10: 0x000003e1 0xb106dded |
|
_msr_h=$(echo "$_msr" | awk '{print $3}'); |
|
_msr_l=$(echo "$_msr" | awk '{print $4}'); |
|
read_msr_value=$(( _msr_h << 32 | _msr_l )) |
|
else |
|
# for Linux |
|
if [ ! -r /dev/cpu/"$_cpu"/msr ]; then |
|
mockme=$(printf "%b\n%b" "$mockme" "SMC_MOCK_RDMSR_${_msr}_RET=200") |
|
return 200 # permission error |
|
# if rdmsr is available, use it |
|
elif command -v rdmsr >/dev/null 2>&1 && [ "$SMC_NO_RDMSR" != 1 ]; then |
|
_debug "read_msr: using rdmsr on $_msr" |
|
read_msr_value=$(rdmsr -r $_msr_dec 2>/dev/null | od -t u8 -A n) |
|
# or if we have perl, use it, any 5.x version will work |
|
elif command -v perl >/dev/null 2>&1 && [ "$SMC_NO_PERL" != 1 ]; then |
|
_debug "read_msr: using perl on $_msr" |
|
read_msr_value=$(perl -e "open(M,'<','/dev/cpu/$_cpu/msr') and seek(M,$_msr_dec,0) and read(M,\$_,8) and print" | od -t u8 -A n) |
|
# fallback to dd if it supports skip_bytes |
|
elif dd if=/dev/null of=/dev/null bs=8 count=1 skip="$_msr_dec" iflag=skip_bytes 2>/dev/null; then |
|
_debug "read_msr: using dd on $_msr" |
|
read_msr_value=$(dd if=/dev/cpu/"$_cpu"/msr bs=8 count=1 skip="$_msr_dec" iflag=skip_bytes 2>/dev/null | od -t u8 -A n) |
|
else |
|
_debug "read_msr: got no rdmsr, perl or recent enough dd!" |
|
mockme=$(printf "%b\n%b" "$mockme" "SMC_MOCK_RDMSR_${_msr}_RET=201") |
|
return 201 # missing tool error |
|
fi |
|
if [ -z "$read_msr_value" ]; then |
|
# MSR doesn't exist, don't check for $? because some versions of dd still return 0! |
|
mockme=$(printf "%b\n%b" "$mockme" "SMC_MOCK_RDMSR_${_msr}_RET=1") |
|
return 1 |
|
fi |
|
# remove sparse spaces od might give us |
|
read_msr_value=$(( read_msr_value )) |
|
fi |
|
mockme=$(printf "%b\n%b" "$mockme" "SMC_MOCK_RDMSR_${_msr}='$read_msr_value'") |
|
_debug "read_msr: MSR=$_msr value is $read_msr_value" |
|
return 0 |
|
} |
|
|
|
check_cpu() |
|
{ |
|
_info "\033[1;34mHardware check\033[0m" |
|
|
|
if ! uname -m | grep -qwE 'x86_64|i[3-6]86|amd64'; then |
|
return |
|
fi |
|
|
|
_info "* Hardware support (CPU microcode) for mitigation techniques" |
|
_info " * Indirect Branch Restricted Speculation (IBRS)" |
|
_info_nol " * SPEC_CTRL MSR is available: " |
|
number_of_cpus |
|
ncpus=$? |
|
idx_max_cpu=$((ncpus-1)) |
|
if [ ! -e /dev/cpu/0/msr ] && [ ! -e /dev/cpuctl0 ]; then |
|
# try to load the module ourselves (and remember it so we can rmmod it afterwards) |
|
load_msr |
|
fi |
|
if [ ! -e /dev/cpu/0/msr ] && [ ! -e /dev/cpuctl0 ]; then |
|
spec_ctrl_msr=-1 |
|
pstatus yellow UNKNOWN "is msr kernel module available?" |
|
else |
|
# the new MSR 'SPEC_CTRL' is at offset 0x48 |
|
# we check if we have it for all cpus |
|
val=0 |
|
cpu_mismatch=0 |
|
for i in $(seq 0 "$idx_max_cpu") |
|
do |
|
read_msr 0x48 "$i"; ret=$? |
|
if [ "$i" -eq 0 ]; then |
|
val=$ret |
|
else |
|
if [ "$ret" -eq $val ]; then |
|
continue |
|
else |
|
cpu_mismatch=1 |
|
fi |
|
fi |
|
done |
|
if [ $val -eq 0 ]; then |
|
if [ $cpu_mismatch -eq 0 ]; then |
|
spec_ctrl_msr=1 |
|
pstatus green YES |
|
else |
|
spec_ctrl_msr=1 |
|
pstatus green YES "But not in all CPUs" |
|
fi |
|
elif [ $val -eq 200 ]; then |
|
pstatus yellow UNKNOWN "is msr kernel module available?" |
|
spec_ctrl_msr=-1 |
|
elif [ $val -eq 201 ]; then |
|
pstatus yellow UNKNOWN "missing tool, install either msr-tools or perl" |
|
spec_ctrl_msr=-1 |
|
elif [ $val -eq 202 ]; then |
|
pstatus yellow UNKNOWN "$msr_locked_down_msg" |
|
spec_ctrl_msr=-1 |
|
else |
|
spec_ctrl_msr=0 |
|
pstatus yellow NO |
|
fi |
|
fi |
|
|
|
_info_nol " * CPU indicates IBRS capability: " |
|
# from kernel src: { X86_FEATURE_SPEC_CTRL, CPUID_EDX,26, 0x00000007, 0 }, |
|
# amd: https://developer.amd.com/wp-content/resources/Architecture_Guidelines_Update_Indirect_Branch_Control.pdf |
|
# amd: 8000_0008 EBX[14]=1 |
|
if is_intel; then |
|
read_cpuid 0x7 $EDX 26 1 1; ret=$? |
|
if [ $ret -eq 0 ]; then |
|
pstatus green YES "SPEC_CTRL feature bit" |
|
cpuid_spec_ctrl=1 |
|
cpuid_ibrs='SPEC_CTRL' |
|
fi |
|
elif is_amd || is_hygon; then |
|
read_cpuid 0x80000008 $EBX 14 1 1; ret=$? |
|
if [ $ret -eq 0 ]; then |
|
pstatus green YES "IBRS_SUPPORT feature bit" |
|
cpuid_ibrs='IBRS_SUPPORT' |
|
fi |
|
else |
|
ret=-1 |
|
pstatus yellow UNKNOWN "unknown CPU" |
|
fi |
|
if [ $ret -eq 1 ]; then |
|
pstatus yellow NO |
|
elif [ $ret -eq 2 ]; then |
|
pstatus yellow UNKNOWN "is cpuid kernel module available?" |
|
cpuid_spec_ctrl=-1 |
|
fi |
|
|
|
if is_amd || is_hygon; then |
|
_info_nol " * CPU indicates preferring IBRS always-on: " |
|
# amd or hygon |
|
read_cpuid 0x80000008 $EBX 16 1 1; ret=$? |
|
if [ $ret -eq 0 ]; then |
|
pstatus green YES |
|
else |
|
pstatus yellow NO |
|
fi |
|
|
|
_info_nol " * CPU indicates preferring IBRS over retpoline: " |
|
# amd or hygon |
|
read_cpuid 0x80000008 $EBX 18 1 1; ret=$? |
|
if [ $ret -eq 0 ]; then |
|
pstatus green YES |
|
else |
|
pstatus yellow NO |
|
fi |
|
fi |
|
|
|
# IBPB |
|
_info " * Indirect Branch Prediction Barrier (IBPB)" |
|
_info_nol " * PRED_CMD MSR is available: " |
|
if [ ! -e /dev/cpu/0/msr ] && [ ! -e /dev/cpuctl0 ]; then |
|
pstatus yellow UNKNOWN "is msr kernel module available?" |
|
elif [ ! -r /dev/cpu/0/msr ] && [ ! -w /dev/cpuctl0 ]; then |
|
pstatus yellow UNKNOWN "are you root?" |
|
else |
|
# the new MSR 'PRED_CTRL' is at offset 0x49, write-only |
|
# we test if of all cpus |
|
val=0 |
|
cpu_mismatch=0 |
|
for i in $(seq 0 "$idx_max_cpu") |
|
do |
|
write_msr 0x49 "$i"; ret=$? |
|
if [ "$i" -eq 0 ]; then |
|
val=$ret |
|
else |
|
if [ "$ret" -eq $val ]; then |
|
continue |
|
else |
|
cpu_mismatch=1 |
|
fi |
|
fi |
|
done |
|
|
|
if [ $val -eq 0 ]; then |
|
if [ $cpu_mismatch -eq 0 ]; then |
|
pstatus green YES |
|
else |
|
pstatus green YES "But not in all CPUs" |
|
fi |
|
elif [ $val -eq 200 ]; then |
|
pstatus yellow UNKNOWN "is msr kernel module available?" |
|
elif [ $val -eq 201 ]; then |
|
pstatus yellow UNKNOWN "missing tool, install either msr-tools or perl" |
|
elif [ $val -eq 202 ]; then |
|
pstatus yellow UNKNOWN "$msr_locked_down_msg" |
|
else |
|
pstatus yellow NO |
|
fi |
|
fi |
|
|
|
_info_nol " * CPU indicates IBPB capability: " |
|
# CPUID EAX=0x80000008, ECX=0x00 return EBX[12] indicates support for just IBPB. |
|
if [ "$cpuid_spec_ctrl" = 1 ]; then |
|
# spec_ctrl implies ibpb |
|
cpuid_ibpb='SPEC_CTRL' |
|
pstatus green YES "SPEC_CTRL feature bit" |
|
elif is_intel; then |
|
if [ "$cpuid_spec_ctrl" = -1 ]; then |
|
pstatus yellow UNKNOWN "is cpuid kernel module available?" |
|
else |
|
pstatus yellow NO |
|
fi |
|
elif is_amd || is_hygon; then |
|
read_cpuid 0x80000008 $EBX 12 1 1; ret=$? |
|
if [ $ret -eq 0 ]; then |
|
cpuid_ibpb='IBPB_SUPPORT' |
|
pstatus green YES "IBPB_SUPPORT feature bit" |
|
elif [ $ret -eq 1 ]; then |
|
pstatus yellow NO |
|
else |
|
pstatus yellow UNKNOWN "is cpuid kernel module available?" |
|
fi |
|
fi |
|
|
|
# STIBP |
|
_info " * Single Thread Indirect Branch Predictors (STIBP)" |
|
_info_nol " * SPEC_CTRL MSR is available: " |
|
if [ "$spec_ctrl_msr" = 1 ]; then |
|
pstatus green YES |
|
elif [ "$spec_ctrl_msr" = 0 ]; then |
|
pstatus yellow NO |
|
else |
|
pstatus yellow UNKNOWN "is msr kernel module available?" |
|
fi |
|
|
|
_info_nol " * CPU indicates STIBP capability: " |
|
# intel: A processor supports STIBP if it enumerates CPUID (EAX=7H,ECX=0):EDX[27] as 1 |
|
# amd: 8000_0008 EBX[15]=1 |
|
if is_intel; then |
|
read_cpuid 0x7 $EDX 27 1 1; ret=$? |
|
if [ $ret -eq 0 ]; then |
|
pstatus green YES "Intel STIBP feature bit" |
|
#cpuid_stibp='Intel STIBP' |
|
fi |
|
elif is_amd; then |
|
read_cpuid 0x80000008 $EBX 15 1 1; ret=$? |
|
if [ $ret -eq 0 ]; then |
|
pstatus green YES "AMD STIBP feature bit" |
|
#cpuid_stibp='AMD STIBP' |
|
fi |
|
elif is_hygon; then |
|
read_cpuid 0x80000008 $EBX 15 1 1; ret=$? |
|
if [ $ret -eq 0 ]; then |
|
pstatus green YES "HYGON STIBP feature bit" |
|
#cpuid_stibp='HYGON STIBP' |
|
fi |
|
else |
|
ret=-1 |
|
pstatus yellow UNKNOWN "unknown CPU" |
|
fi |
|
if [ $ret -eq 1 ]; then |
|
pstatus yellow NO |
|
elif [ $ret -eq 2 ]; then |
|
pstatus yellow UNKNOWN "is cpuid kernel module available?" |
|
fi |
|
|
|
|
|
if is_amd || is_hygon; then |
|
_info_nol " * CPU indicates preferring STIBP always-on: " |
|
read_cpuid 0x80000008 $EBX 17 1 1; ret=$? |
|
if [ $ret -eq 0 ]; then |
|
pstatus green YES |
|
else |
|
pstatus yellow NO |
|
fi |
|
fi |
|
|
|
# variant 4 |
|
if is_intel; then |
|
_info " * Speculative Store Bypass Disable (SSBD)" |
|
_info_nol " * CPU indicates SSBD capability: " |
|
read_cpuid 0x7 $EDX 31 1 1; ret24=$?; ret25=$ret24 |
|
if [ $ret24 -eq 0 ]; then |
|
cpuid_ssbd='Intel SSBD' |
|
fi |
|
elif is_amd; then |
|
_info " * Speculative Store Bypass Disable (SSBD)" |
|
_info_nol " * CPU indicates SSBD capability: " |
|
read_cpuid 0x80000008 $EBX 24 1 1; ret24=$? |
|
read_cpuid 0x80000008 $EBX 25 1 1; ret25=$? |
|
if [ $ret24 -eq 0 ]; then |
|
cpuid_ssbd='AMD SSBD in SPEC_CTRL' |
|
#cpuid_ssbd_spec_ctrl=1 |
|
elif [ $ret25 -eq 0 ]; then |
|
cpuid_ssbd='AMD SSBD in VIRT_SPEC_CTRL' |
|
#cpuid_ssbd_virt_spec_ctrl=1 |
|
elif [ "$cpu_family" -ge 21 ] && [ "$cpu_family" -le 23 ]; then |
|
cpuid_ssbd='AMD non-architectural MSR' |
|
fi |
|
elif is_hygon; then |
|
_info " * Speculative Store Bypass Disable (SSBD)" |
|
_info_nol " * CPU indicates SSBD capability: " |
|
read_cpuid 0x80000008 $EBX 24 1 1; ret24=$? |
|
read_cpuid 0x80000008 $EBX 25 1 1; ret25=$? |
|
|
|
if [ $ret24 -eq 0 ]; then |
|
cpuid_ssbd='HYGON SSBD in SPEC_CTRL' |
|
#hygon cpuid_ssbd_spec_ctrl=1 |
|
elif [ $ret25 -eq 0 ]; then |
|
cpuid_ssbd='HYGON SSBD in VIRT_SPEC_CTRL' |
|
#hygon cpuid_ssbd_virt_spec_ctrl=1 |
|
elif [ "$cpu_family" -ge 24 ]; then |
|
cpuid_ssbd='HYGON non-architectural MSR' |
|
fi |
|
fi |
|
|
|
if [ -n "$cpuid_ssbd" ]; then |
|
pstatus green YES "$cpuid_ssbd" |
|
elif [ "$ret24" = 2 ] && [ "$ret25" = 2 ]; then |
|
pstatus yellow UNKNOWN "is cpuid kernel module available?" |
|
else |
|
pstatus yellow NO |
|
fi |
|
|
|
if is_amd; then |
|
# similar to SSB_NO for intel |
|
read_cpuid 0x80000008 $EBX 26 1 1; ret=$? |
|
if [ $ret -eq 0 ]; then |
|
amd_ssb_no=1 |
|
fi |
|
elif is_hygon; then |
|
# indicate when speculative store bypass disable is no longer needed to prevent speculative loads bypassing older stores |
|
read_cpuid 0x80000008 $EBX 26 1 1; ret=$? |
|
if [ $ret -eq 0 ]; then |
|
hygon_ssb_no=1 |
|
_debug "hygon_ssb_no=1" |
|
fi |
|
fi |
|
|
|
_info " * L1 data cache invalidation" |
|
_info_nol " * FLUSH_CMD MSR is available: " |
|
if [ ! -e /dev/cpu/0/msr ] && [ ! -e /dev/cpuctl0 ]; then |
|
pstatus yellow UNKNOWN "is msr kernel module available?" |
|
elif [ ! -r /dev/cpu/0/msr ] && [ ! -w /dev/cpuctl0 ]; then |
|
pstatus yellow UNKNOWN "are you root?" |
|
else |
|
# the new MSR 'FLUSH_CMD' is at offset 0x10b, write-only |
|
# we test if of all cpus |
|
val=0 |
|
cpu_mismatch=0 |
|
for i in $(seq 0 "$idx_max_cpu") |
|
do |
|
write_msr 0x10b "$i"; ret=$? |
|
if [ "$i" -eq 0 ]; then |
|
val=$ret |
|
else |
|
if [ "$ret" -eq $val ]; then |
|
continue |
|
else |
|
cpu_mismatch=1 |
|
fi |
|
fi |
|
done |
|
|
|
if [ $val -eq 0 ]; then |
|
if [ $cpu_mismatch -eq 0 ]; then |
|
pstatus green YES |
|
cpu_flush_cmd=1 |
|
else |
|
pstatus green YES "But not in all CPUs" |
|
fi |
|
elif [ $val -eq 200 ]; then |
|
pstatus yellow UNKNOWN "is msr kernel module available?" |
|
elif [ $val -eq 201 ]; then |
|
pstatus yellow UNKNOWN "missing tool, install either msr-tools or perl" |
|
elif [ $val -eq 202 ]; then |
|
pstatus yellow UNKNOWN "$msr_locked_down_msg" |
|
else |
|
pstatus yellow NO |
|
fi |
|
fi |
|
# CPUID of L1D |
|
_info_nol " * CPU indicates L1D flush capability: " |
|
read_cpuid 0x7 $EDX 28 1 1; ret=$? |
|
if [ $ret -eq 0 ]; then |
|
pstatus green YES "L1D flush feature bit" |
|
cpuid_l1df=1 |
|
elif [ $ret -eq 1 ]; then |
|
pstatus yellow NO |
|
elif [ $ret -eq 2 ]; then |
|
pstatus yellow UNKNOWN "is cpuid kernel module available?" |
|
fi |
|
|
|
if is_intel; then |
|
_info " * Microarchitectural Data Sampling" |
|
_info_nol " * VERW instruction is available: " |
|
read_cpuid 0x7 $EDX 10 1 1; ret=$? |
|
if [ $ret -eq 0 ]; then |
|
cpuid_md_clear=1 |
|
pstatus green YES "MD_CLEAR feature bit" |
|
elif [ $ret -eq 2 ]; then |
|
cpuid_md_clear=-1 |
|
pstatus yellow UNKNOWN "is cpuid kernel module available?" |
|
else |
|
cpuid_md_clear=0 |
|
pstatus yellow NO |
|
fi |
|
fi |
|
|
|
if is_intel; then |
|
_info " * Enhanced IBRS (IBRS_ALL)" |
|
_info_nol " * CPU indicates ARCH_CAPABILITIES MSR availability: " |
|
cpuid_arch_capabilities=-1 |
|
# A processor supports the ARCH_CAPABILITIES MSR if it enumerates CPUID (EAX=7H,ECX=0):EDX[29] as 1 |
|
read_cpuid 0x7 $EDX 29 1 1; ret=$? |
|
if [ $ret -eq 0 ]; then |
|
pstatus green YES |
|
cpuid_arch_capabilities=1 |
|
elif [ $ret -eq 2 ]; then |
|
pstatus yellow UNKNOWN "is cpuid kernel module available?" |
|
else |
|
pstatus yellow NO |
|
cpuid_arch_capabilities=0 |
|
fi |
|
|
|
_info_nol " * ARCH_CAPABILITIES MSR advertises IBRS_ALL capability: " |
|
capabilities_taa_no=-1 |
|
capabilities_mds_no=-1 |
|
capabilities_rdcl_no=-1 |
|
capabilities_ibrs_all=-1 |
|
capabilities_rsba=-1 |
|
capabilities_l1dflush_no=-1 |
|
capabilities_ssb_no=-1 |
|
capabilities_pschange_msc_no=-1 |
|
capabilities_tsx_ctrl_msr=-1 |
|
if [ "$cpuid_arch_capabilities" = -1 ]; then |
|
pstatus yellow UNKNOWN |
|
elif [ "$cpuid_arch_capabilities" != 1 ]; then |
|
capabilities_rdcl_no=0 |
|
capabilities_taa_no=0 |
|
capabilities_mds_no=0 |
|
capabilities_ibrs_all=0 |
|
capabilities_rsba=0 |
|
capabilities_l1dflush_no=0 |
|
capabilities_ssb_no=0 |
|
capabilities_pschange_msc_no=0 |
|
capabilities_tsx_ctrl_msr=0 |
|
pstatus yellow NO |
|
elif [ ! -e /dev/cpu/0/msr ] && [ ! -e /dev/cpuctl0 ]; then |
|
spec_ctrl_msr=-1 |
|
pstatus yellow UNKNOWN "is msr kernel module available?" |
|
else |
|
# the new MSR 'ARCH_CAPABILITIES' is at offset 0x10a |
|
# we check if we have it for all cpus |
|
val=0 |
|
val_cap_msr=0 |
|
cpu_mismatch=0 |
|
for i in $(seq 0 "$idx_max_cpu") |
|
do |
|
read_msr 0x10a "$i"; ret=$? |
|
capabilities=$read_msr_value |
|
if [ "$i" -eq 0 ]; then |
|
val=$ret |
|
val_cap_msr=$capabilities |
|
else |
|
if [ "$ret" -eq "$val" ] && [ "$capabilities" -eq "$val_cap_msr" ]; then |
|
continue |
|
else |
|
cpu_mismatch=1 |
|
fi |
|
fi |
|
done |
|
capabilities=$val_cap_msr |
|
capabilities_rdcl_no=0 |
|
capabilities_taa_no=0 |
|
capabilities_mds_no=0 |
|
capabilities_ibrs_all=0 |
|
capabilities_rsba=0 |
|
capabilities_l1dflush_no=0 |
|
capabilities_ssb_no=0 |
|
capabilities_pschange_msc_no=0 |
|
capabilities_tsx_ctrl_msr=0 |
|
if [ $val -eq 0 ]; then |
|
# https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/arch/x86/include/asm/msr-index.h#n82 |
|
_debug "capabilities MSR is $capabilities (decimal)" |
|
[ $(( capabilities >> 0 & 1 )) -eq 1 ] && capabilities_rdcl_no=1 |
|
[ $(( capabilities >> 1 & 1 )) -eq 1 ] && capabilities_ibrs_all=1 |
|
[ $(( capabilities >> 2 & 1 )) -eq 1 ] && capabilities_rsba=1 |
|
[ $(( capabilities >> 3 & 1 )) -eq 1 ] && capabilities_l1dflush_no=1 |
|
[ $(( capabilities >> 4 & 1 )) -eq 1 ] && capabilities_ssb_no=1 |
|
[ $(( capabilities >> 5 & 1 )) -eq 1 ] && capabilities_mds_no=1 |
|
[ $(( capabilities >> 6 & 1 )) -eq 1 ] && capabilities_pschange_msc_no=1 |
|
[ $(( capabilities >> 7 & 1 )) -eq 1 ] && capabilities_tsx_ctrl_msr=1 |
|
[ $(( capabilities >> 8 & 1 )) -eq 1 ] && capabilities_taa_no=1 |
|
_debug "capabilities says rdcl_no=$capabilities_rdcl_no ibrs_all=$capabilities_ibrs_all rsba=$capabilities_rsba l1dflush_no=$capabilities_l1dflush_no ssb_no=$capabilities_ssb_no mds_no=$capabilities_mds_no taa_no=$capabilities_taa_no pschange_msc_no=$capabilities_pschange_msc_no" |
|
if [ "$capabilities_ibrs_all" = 1 ]; then |
|
if [ $cpu_mismatch -eq 0 ]; then |
|
pstatus green YES |
|
else |
|
pstatus green YES "But not in all CPUs" |
|
fi |
|
else |
|
pstatus yellow NO |
|
fi |
|
elif [ $val -eq 200 ]; then |
|
< |