You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
6304 lines
229 KiB
Bash
6304 lines
229 KiB
Bash
#! /bin/sh
|
|
# SPDX-License-Identifier: GPL-3.0-only
|
|
# vim: set ts=4 sw=4 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.45'
|
|
|
|
trap 'exit_cleanup' EXIT
|
|
trap '_warn "interrupted, cleaning up..."; exit_cleanup; exit 1' INT
|
|
exit_cleanup()
|
|
{
|
|
saved_ret=$?
|
|
# 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
|
|
exit $saved_ret
|
|
}
|
|
|
|
# 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
|
|
--allow-msr-write allow probing for write-only MSRs, this might produce kernel logs or be blocked by your system
|
|
--cpu [#,all] interact with CPUID and MSR of CPU core number #, or all (default: CPU core 0)
|
|
--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_allow_msr_write=0
|
|
opt_cpu=0
|
|
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_affected_cached=0
|
|
_is_cpu_affected_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_affected()" >&2; exit 255;;
|
|
esac
|
|
}
|
|
|
|
is_cpu_affected()
|
|
{
|
|
# param: one of the $supported_cve_list items
|
|
# returns 0 if affected, 1 if not affected
|
|
# (note that in shell, a return of 0 is success)
|
|
# by default, everything is affected, we work in a "whitelist" logic here.
|
|
# usage: is_cpu_affected CVE-xxxx-yyyy && do something if affected
|
|
if [ "$is_cpu_affected_cached" = 1 ]; then
|
|
_is_cpu_affected_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_affected: cpu not affected by Microarchitectural Data Sampling"
|
|
fi
|
|
|
|
if is_cpu_taa_free; then
|
|
[ -z "$variant_taa" ] && variant_taa=immune
|
|
_debug "is_cpu_affected: cpu not affected by TSX Asynhronous Abort"
|
|
fi
|
|
|
|
if is_cpu_srbds_free; then
|
|
[ -z "$variant_srbds" ] && variant_srbds=immune
|
|
_debug "is_cpu_affected: 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 affected
|
|
# https://github.com/paboldin/meltdown-exploit/issues/19 ^F E5200 => meltdown affected
|
|
# 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 affected to Meltdown
|
|
# this var is set in check_cpu()
|
|
[ -z "$variant3" ] && variant3=immune
|
|
[ -z "$variantl1tf" ] && variantl1tf=immune
|
|
_debug "is_cpu_affected: 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 affected to Variant 4
|
|
# this var is set in check_cpu()
|
|
[ -z "$variant4" ] && variant4=immune
|
|
_debug "is_cpu_affected: SSB_NO is set so not vuln to variant4"
|
|
fi
|
|
if is_cpu_ssb_free; then
|
|
[ -z "$variant4" ] && variant4=immune
|
|
_debug "is_cpu_affected: 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_affected: 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 affected
|
|
# => goldmont ARE affected
|
|
_debug "is_cpu_affected: 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_affected: intel family 6 but model known to be immune to l1tf"
|
|
[ -z "$variantl1tf" ] && variantl1tf=immune
|
|
else
|
|
_debug "is_cpu_affected: intel family 6 is vuln to l1tf"
|
|
variantl1tf=vuln
|
|
fi
|
|
elif [ "$cpu_family" -lt 6 ]; then
|
|
_debug "is_cpu_affected: intel family < 6 is immune to l1tf"
|
|
[ -z "$variantl1tf" ] && variantl1tf=immune
|
|
fi
|
|
elif is_amd || is_hygon; then
|
|
# AMD revised their statement about variant2 => affected
|
|
# 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_affected: 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" = PHYTIUM ]; 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 A77 Neoverse-N1 Neoverse-V1
|
|
# part ? ? c08 c09 c0d c0f c0e d07 d08 d09 d0a d0b d0d d0c d40
|
|
# arch 7? 7? 7 7 7 7 7 8 8 8 8 8 8 8 8
|
|
#
|
|
# Whitelist identified non-affected processors, use vulnerability information from
|
|
# https://developer.arm.com/support/arm-security-updates/speculative-processor-vulnerability
|
|
# Partnumbers can be found here:
|
|
# https://github.com/gcc-mirror/gcc/blob/master/gcc/config/arm/arm-cpus.in
|
|
#
|
|
# Maintain cumulative check of vulnerabilities -
|
|
# if at least one of the cpu is affected, then the system is affected
|
|
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 affected 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 affected 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 affected 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 affected 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 affected to variant 3a"
|
|
elif [ "$cpuarch" = 8 ] && echo "$cpupart" | grep -q -w -e 0xd0b -e 0xd0c -e 0xd0d; then
|
|
variant1=vuln
|
|
[ -z "$variant2" ] && variant2=immune
|
|
[ -z "$variant3" ] && variant3=immune
|
|
[ -z "$variant3a" ] && variant3a=immune
|
|
variant4=vuln
|
|
_debug "checking cpu$i: armv8 A76/A77/NeoverseN1 non affected to variant 2, 3 & 3a"
|
|
elif [ "$cpuarch" = 8 ] && echo "$cpupart" | grep -q -w -e 0xd40; then
|
|
variant1=vuln
|
|
[ -z "$variant2" ] && variant2=immune
|
|
[ -z "$variant3" ] && variant3=immune
|
|
[ -z "$variant3a" ] && variant3a=immune
|
|
[ -z "$variant4" ] && variant4=immune
|
|
_debug "checking cpu$i: armv8 NeoverseV1 non affected to variant 2, 3, 3a & 4"
|
|
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_affected: 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_affected: intel family 6 but model known to be immune to itlbmh"
|
|
[ -z "$variant_itlbmh" ] && variant_itlbmh=immune
|
|
else
|
|
_debug "is_cpu_affected: intel family 6 is vuln to itlbmh"
|
|
variant_itlbmh=vuln
|
|
fi
|
|
elif [ "$cpu_family" -lt 6 ]; then
|
|
_debug "is_cpu_affected: intel family < 6 is immune to itlbmh"
|
|
[ -z "$variant_itlbmh" ] && variant_itlbmh=immune
|
|
fi
|
|
else
|
|
_debug "is_cpu_affected: non-intel not affected to itlbmh"
|
|
[ -z "$variant_itlbmh" ] && variant_itlbmh=immune
|
|
fi
|
|
|
|
_debug "is_cpu_affected: 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 affected to L1TF, if there's no SGX, we're not affected to the original foreshadow
|
|
[ "$cpuid_sgx" = 0 ] && variantl1tf_sgx=1
|
|
_debug "is_cpu_affected: final results are <$variant1> <$variant2> <$variant3> <$variant3a> <$variant4> <$variantl1tf> <$variantl1tf_sgx>"
|
|
is_cpu_affected_cached=1
|
|
_is_cpu_affected_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 affected
|
|
# 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" = PHYTIUM ]; 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
|
|
|
|
set -e
|
|
|
|
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 -t smc-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 -t smc-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\"")
|
|
if [ -z "$mcedb_revision" ]; then
|
|
echo ERROR "downloaded file seems invalid"
|
|
return 1
|
|
fi
|
|
sqlite3 "$mcedb_tmp" "ALTER TABLE \"Intel\" ADD COLUMN \"origin\" TEXT"
|
|
sqlite3 "$mcedb_tmp" "UPDATE \"Intel\" SET \"origin\"='mce'"
|
|
|
|
echo OK "MCExtractor database revision $mcedb_revision"
|
|
|
|
# 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\" (\"origin\",\"cpuid\",\"version\",\"yyyymmdd\") VALUES ('%s','%s','%s','%s');" "intel" "$(printf "%08X" "$_cpuid")" "$(printf "%08X" "$_version")" "$_date")"
|
|
sqlite3 "$mcedb_tmp" "$_sqlstm"
|
|
done
|
|
_intel_timestamp=$(stat -c %Y "$intel_tmp/Intel-Linux-Processor-Microcode-Data-Files-main/license" 2>/dev/null)
|
|
if [ -n "$_intel_timestamp" ]; then
|
|
# use this date, it matches the last commit date
|
|
_intel_latest_date=$(date +%Y%m%d -d @"$_intel_timestamp")
|
|
else
|
|
echo "Falling back to the latest microcode date"
|
|
_intel_latest_date=$(sqlite3 "$mcedb_tmp" "SELECT \"yyyymmdd\" FROM \"Intel\" WHERE \"origin\"='intel' ORDER BY \"yyyymmdd\" DESC LIMIT 1;")
|
|
fi
|
|
echo DONE "(version $_intel_latest_date)"
|
|
|
|
dbversion="$mcedb_revision+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";
|
|
# ensure the official Intel DB always has precedence over mcedb, even if mcedb has seen a more recent fw
|
|
sqlite3 "$mcedb_tmp" "DELETE FROM \"Intel\" WHERE \"origin\"!='intel' AND \"cpuid\" IN (SELECT \"cpuid\" FROM \"Intel\" WHERE \"origin\"='intel' GROUP BY \"cpuid\" ORDER BY \"cpuid\" ASC);"
|
|
# we'll use the more recent fw for Intel and AMD
|
|
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 -t smc-builtin-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" = "--allow-msr-write" ]; then
|
|
opt_allow_msr_write=1
|
|
shift
|
|
elif [ "$1" = "--cpu" ]; then
|
|
opt_cpu=$2
|
|
if [ "$opt_cpu" != all ]; then
|
|
if echo "$opt_cpu" | grep -Eq '^[0-9]+'; then
|
|
opt_cpu=$(( opt_cpu ))
|
|
else
|
|
echo "$0: error: --cpu should be an integer or 'all', got '$opt_cpu'" >&2
|
|
exit 255
|
|
fi
|
|
fi
|
|
shift 2
|
|
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
|
|
# param1: color
|
|
# param2: message to print
|
|
# param3(optional): supplement message to print between ()
|
|
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 "
|
|
: "${final_summary:=}"
|
|
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:-normal}"
|
|
# 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 -t smc-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 -t smc-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 might already have been populated by try_decompress() if we're missing one of the tools
|
|
if [ -z "$kernel_err" ]; then
|
|
kernel_err="kernel compression format is unknown or image is invalid"
|
|
fi
|
|
_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()
|
|
{
|
|
# only attempt to do it once even if called multiple times
|
|
[ "${load_msr_once:-}" = 1 ] && return
|
|
load_msr_once=1
|
|
|
|
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()
|
|
{
|
|
# only attempt to do it once even if called multiple times
|
|
[ "${load_cpuid_once:-}" = 1 ] && return
|
|
load_cpuid_once=1
|
|
|
|
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_RET_OK=0
|
|
READ_CPUID_RET_KO=1
|
|
READ_CPUID_RET_ERR=2
|
|
read_cpuid()
|
|
{
|
|
if [ "$opt_cpu" != all ]; then
|
|
# we only have one core to read, do it and return the result
|
|
read_cpuid_one_core $opt_cpu "$@"
|
|
return $?
|
|
fi
|
|
|
|
# otherwise we must read all cores
|
|
for _core in $(seq 0 "$max_core_id"); do
|
|
read_cpuid_one_core "$_core" "$@"; ret=$?
|
|
if [ "$_core" = 0 ]; then
|
|
# save the result of the first core, for comparison with the others
|
|
_first_core_ret=$ret
|
|
_first_core_value=$read_cpuid_value
|
|
else
|
|
# compare first core with the other ones
|
|
if [ $_first_core_ret != $ret ] || [ "$_first_core_value" != "$read_cpuid_value" ]; then
|
|
read_cpuid_msg="result is not homogeneous between all cores, at least core 0 and $_core differ!"
|
|
return $READ_CPUID_RET_ERR
|
|
fi
|
|
fi
|
|
done
|
|
# if we're here, all cores agree, return the result
|
|
return $ret
|
|
}
|
|
|
|
read_cpuid_one_core()
|
|
{
|
|
# on which core to send the CPUID instruction
|
|
_core="$1"
|
|
# leaf is the value of the eax register when calling the cpuid instruction:
|
|
_leaf="$2"
|
|
# subleaf is the value of the ecx register when calling the cpuid instruction:
|
|
_subleaf="$3"
|
|
# eax=1 ebx=2 ecx=3 edx=4:
|
|
_register="$4"
|
|
# number of bits to shift the register right to, 0-31:
|
|
_shift="$5"
|
|
# mask to apply as an AND operand to the shifted register value
|
|
_mask="$6"
|
|
# wanted value (optional), if present we return 0(true) if the obtained value is equal, 1 otherwise:
|
|
_wanted="${7:-}"
|
|
# in any case, the read value is globally available in $read_cpuid_value
|
|
read_cpuid_value=''
|
|
read_cpuid_msg='unknown error'
|
|
|
|
if [ $# -lt 6 ]; then
|
|
read_cpuid_msg="read_cpuid: missing arguments, got only $#, expected at least 6: $*"
|
|
return $READ_CPUID_RET_ERR
|
|
fi
|
|
if [ "$_register" -gt 4 ]; then
|
|
read_cpuid_msg="read_cpuid: register must be 0-4, got $_register"
|
|
return $READ_CPUID_RET_ERR
|
|
fi
|
|
if [ "$_shift" -gt 32 ]; then
|
|
read_cpuid_msg="read_cpuid: shift must be 0-31, got $_shift"
|
|
return $READ_CPUID_RET_ERR
|
|
fi
|
|
|
|
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
|
|
read_cpuid_msg="Couldn't load cpuid module"
|
|
return $READ_CPUID_RET_ERR
|
|
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,
|
|
# we use that fact to load the module if dd returns an error
|
|
if ! dd if=/dev/cpu/0/cpuid bs=16 count=1 >/dev/null 2>&1; then
|
|
load_cpuid
|
|
fi
|
|
# we need _leaf to be converted to decimal for dd
|
|
_leaf=$(( _leaf ))
|
|
_subleaf=$(( _subleaf ))
|
|
_position=$(( _leaf + (_subleaf << 32) ))
|
|
# to avoid using iflag=skip_bytes, which doesn't exist on old versions of dd, seek to the closer multiple-of-16
|
|
_ddskip=$(( _position / 16 ))
|
|
_odskip=$(( _position - _ddskip * 16 ))
|
|
# now read the value
|
|
_cpuid=$(dd if="/dev/cpu/$_core/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
|
|
read_cpuid_msg="Couldn't read cpuid info from cpuctl"
|
|
return $READ_CPUID_RET_ERR
|
|
fi
|
|
_cpuid=$(cpucontrol -i "$_leaf","$_subleaf" "/dev/cpuctl$_core" 2>/dev/null | cut -d: -f2-)
|
|
# cpuid level 0x4, level_type 0x2: 0x1c004143 0x01c0003f 0x000001ff 0x00000000
|
|
else
|
|
read_cpuid_msg="Found no way to read cpuid info"
|
|
return $READ_CPUID_RET_ERR
|
|
fi
|
|
|
|
_debug "cpuid: leaf$_leaf subleaf$_subleaf on cpu$_core, eax-ebx-ecx-edx: $_cpuid"
|
|
_mockvarname="SMC_MOCK_CPUID_${_leaf}_${_subleaf}"
|
|
# shellcheck disable=SC1083
|
|
if [ -n "$(eval echo \${$_mockvarname:-})" ]; then
|
|
_cpuid="$(eval echo \$$_mockvarname)"
|
|
_debug "read_cpuid: MOCKING enabled for leaf $_leaf subleaf $_subleaf, will return $_cpuid"
|
|
mocked=1
|
|
else
|
|
mockme=$(printf "%b\n%b" "$mockme" "SMC_MOCK_CPUID_${_leaf}_${_subleaf}='$_cpuid'")
|
|
fi
|
|
if [ -z "$_cpuid" ]; then
|
|
read_cpuid_msg="Failed to get cpuid data"
|
|
return $READ_CPUID_RET_ERR
|
|
fi
|
|
|
|
# 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 $READ_CPUID_RET_OK
|
|
else
|
|
return $READ_CPUID_RET_KO
|
|
fi
|
|
fi
|
|
|
|
return $READ_CPUID_RET_OK
|
|
}
|
|
|
|
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 command -v nproc >/dev/null; then
|
|
number_of_cores=$(nproc)
|
|
elif echo "$os" | grep -q BSD; then
|
|
number_of_cores=$(sysctl -n hw.ncpu 2>/dev/null || echo 1)
|
|
elif [ -e "$procfs/cpuinfo" ]; then
|
|
number_of_cores=$(grep -c ^processor "$procfs/cpuinfo" 2>/dev/null || echo 1)
|
|
else
|
|
# if we don't know, default to 1 CPU
|
|
number_of_cores=1
|
|
fi
|
|
max_core_id=$(( number_of_cores - 1 ))
|
|
|
|
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_affected
|
|
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'
|
|
elif grep -qi 'CPU implementer[[:space:]]*:[[:space:]]*0x70' "$procfs/cpuinfo"; then
|
|
cpu_vendor='PHYTIUM'
|
|
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 0x0 $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
|
|
: "${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}' | sed -re 's/ +$//'
|
|
# 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 )) # /* Sky Lake */
|
|
INTEL_FAM6_SKYLAKE=$(( 0x5E )) # /* Sky Lake */
|
|
INTEL_FAM6_SKYLAKE_X=$(( 0x55 )) # /* Sky Lake */
|
|
INTEL_FAM6_KABYLAKE_L=$(( 0x8E )) # /* Sky Lake */
|
|
INTEL_FAM6_KABYLAKE=$(( 0x9E )) # /* Sky Lake */
|
|
INTEL_FAM6_COMETLAKE=$(( 0xA5 )) # /* Sky Lake */
|
|
INTEL_FAM6_COMETLAKE_L=$(( 0xA6 )) # /* Sky Lake */
|
|
INTEL_FAM6_CANNONLAKE_L=$(( 0x66 )) # /* Palm Cove */
|
|
INTEL_FAM6_ICELAKE_X=$(( 0x6A )) # /* Sunny Cove */
|
|
INTEL_FAM6_ICELAKE_D=$(( 0x6C )) # /* Sunny Cove */
|
|
INTEL_FAM6_ICELAKE=$(( 0x7D )) # /* Sunny Cove */
|
|
INTEL_FAM6_ICELAKE_L=$(( 0x7E )) # /* Sunny Cove */
|
|
INTEL_FAM6_ICELAKE_NNPI=$(( 0x9D )) # /* Sunny Cove */
|
|
INTEL_FAM6_LAKEFIELD=$(( 0x8A )) # /* Sunny Cove / Tremont */
|
|
INTEL_FAM6_ROCKETLAKE=$(( 0xA7 )) # /* Cypress Cove */
|
|
INTEL_FAM6_TIGERLAKE_L=$(( 0x8C )) # /* Willow Cove */
|
|
INTEL_FAM6_TIGERLAKE=$(( 0x8D )) # /* Willow Cove */
|
|
INTEL_FAM6_SAPPHIRERAPIDS_X=$(( 0x8F )) # /* Golden Cove */
|
|
INTEL_FAM6_ALDERLAKE=$(( 0x97 )) # /* Golden Cove / Gracemont */
|
|
INTEL_FAM6_ALDERLAKE_L=$(( 0x9A )) # /* Golden Cove / Gracemont */
|
|
INTEL_FAM6_RAPTORLAKE=$(( 0xB7 )) #
|
|
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_ATOM_TREMONT_L=$(( 0x9C )) # /* Jasper Lake */
|
|
INTEL_FAM6_XEON_PHI_KNL=$(( 0x57 )) # /* Knights Landing */
|
|
INTEL_FAM6_XEON_PHI_KNM=$(( 0x85 )) # /* Knights Mill */
|
|
}
|
|
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,0x0B,0x80 \
|
|
$INTEL_FAM6_KABYLAKE,0x0A,0x80 \
|
|
$INTEL_FAM6_KABYLAKE,0x09,0x80 \
|
|
$INTEL_FAM6_KABYLAKE_L,0x0A,0x80 \
|
|
$INTEL_FAM6_KABYLAKE_L,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,0x04,0x28 \
|
|
$INTEL_FAM6_BROADWELL_G,0x01,0x1b \
|
|
$INTEL_FAM6_BROADWELL_D,0x02,0x14 \
|
|
$INTEL_FAM6_BROADWELL_D,0x03,0x07000011 \
|
|
$INTEL_FAM6_BROADWELL_X,0x01,0x0b000025 \
|
|
$INTEL_FAM6_HASWELL_L,0x01,0x21 \
|
|
$INTEL_FAM6_HASWELL_G,0x01,0x18 \
|
|
$INTEL_FAM6_HASWELL,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
|
|
|
|
# or other UNIX-ish OSes non-Linux non-supported-BSDs
|
|
if [ "$os" = Darwin ] || [ "$os" = VMkernel ]; then
|
|
_warn "You're running under the $os OS, but this script"
|
|
_warn "only works under Linux and some BSD systems, sorry."
|
|
_warn "Please read the README and FAQ for more information."
|
|
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
|
|
|
|
# define a few vars we might reference later without these being inited
|
|
mockme=''
|
|
mocked=0
|
|
specex_knob_dir=/dev/no_valid_path
|
|
|
|
# if /tmp doesn't exist and TMPDIR is not set, try to set it to a sane default for Android
|
|
if [ -z "${TMPDIR:-}" ] && ! [ -d "/tmp" ] && [ -d "/data/local/tmp" ]; then
|
|
TMPDIR=/data/local/tmp
|
|
export TMPDIR
|
|
fi
|
|
|
|
parse_cpu_details
|
|
get_cmdline
|
|
|
|
if [ "$opt_cpu" != all ] && [ "$opt_cpu" -gt "$max_core_id" ]; then
|
|
echo "$0: error: --cpu can't be higher than $max_core_id, got $opt_cpu" >&2
|
|
exit 255
|
|
fi
|
|
|
|
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$os $(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"
|
|
# Guix System:
|
|
[ -e "/run/booted-system/kernel/bzImage" ] && opt_kernel="/run/booted-system/kernel/bzImage"
|
|
# 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 -t smc-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:=0}" = 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,SC1083
|
|
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,SC1083
|
|
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
|
|
}
|
|
|
|
# write_msr
|
|
# param1 (mandatory): MSR, can be in hex or decimal.
|
|
# param2 (optional): CPU index, starting from 0. Default 0.
|
|
WRITE_MSR_RET_OK=0
|
|
WRITE_MSR_RET_KO=1
|
|
WRITE_MSR_RET_ERR=2
|
|
WRITE_MSR_RET_LOCKDOWN=3
|
|
write_msr()
|
|
{
|
|
if [ "$opt_cpu" != all ]; then
|
|
# we only have one core to write to, do it and return the result
|
|
write_msr_one_core $opt_cpu "$@"
|
|
return $?
|
|
fi
|
|
|
|
# otherwise we must write on all cores
|
|
for _core in $(seq 0 $max_core_id); do
|
|
write_msr_one_core "$_core" "$@"; ret=$?
|
|
if [ "$_core" = 0 ]; then
|
|
# save the result of the first core, for comparison with the others
|
|
_first_core_ret=$ret
|
|
else
|
|
# compare first core with the other ones
|
|
if [ $_first_core_ret != $ret ]; then
|
|
write_msr_msg="result is not homogeneous between all cores, at least core 0 and $_core differ!"
|
|
return $WRITE_MSR_RET_ERR
|
|
fi
|
|
fi
|
|
done
|
|
# if we're here, all cores agree, return the result
|
|
return $ret
|
|
}
|
|
|
|
write_msr_one_core()
|
|
{
|
|
_core="$1"
|
|
_msr_dec=$(( $2 ))
|
|
_msr=$(printf "0x%x" "$_msr_dec")
|
|
|
|
write_msr_msg='unknown error'
|
|
: "${msr_locked_down:=0}"
|
|
|
|
_mockvarname="SMC_MOCK_WRMSR_${_msr}_RET"
|
|
# shellcheck disable=SC2086,SC1083
|
|
if [ -n "$(eval echo \${$_mockvarname:-})" ]; then
|
|
_debug "write_msr: MOCKING enabled for msr $_msr func returns $(eval echo \$$_mockvarname)"
|
|
mocked=1
|
|
[ "$(eval echo \$$_mockvarname)" = $WRITE_MSR_RET_LOCKDOWN ] && msr_locked_down=1
|
|
return "$(eval echo \$$_mockvarname)"
|
|
fi
|
|
|
|
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
|
|
read_msr_msg="is msr kernel module available?"
|
|
return $WRITE_MSR_RET_ERR
|
|
fi
|
|
|
|
_write_denied=0
|
|
if [ "$os" != Linux ]; then
|
|
cpucontrol -m "$_msr=0" "/dev/cpuctl$_core" >/dev/null 2>&1; ret=$?
|
|
else
|
|
# for Linux
|
|
# convert to decimal
|
|
if [ ! -w /dev/cpu/"$_core"/msr ]; then
|
|
write_msr_msg="No write permission on /dev/cpu/$_core/msr"
|
|
return $WRITE_MSR_RET_ERR
|
|
# 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=$?
|
|
# ret=4: msr doesn't exist, ret=127: msr.allow_writes=off
|
|
[ "$ret" = 127 ] && _write_denied=1
|
|
# or fallback to dd if it supports seek_bytes, we prefer it over perl because we can tell the difference between EPERM and EIO
|
|
elif dd if=/dev/null of=/dev/null bs=8 count=1 seek="$_msr_dec" oflag=seek_bytes 2>/dev/null && [ "${SMC_NO_DD:-}" != 1 ]; then
|
|
_debug "write_msr: using dd"
|
|
dd if=/dev/zero of=/dev/cpu/"$_core"/msr bs=8 count=1 seek="$_msr_dec" oflag=seek_bytes 2>/dev/null; ret=$?
|
|
# if it failed, inspect stderrto look for EPERM
|
|
if [ "$ret" != 0 ]; then
|
|
if dd if=/dev/zero of=/dev/cpu/"$_core"/msr bs=8 count=1 seek="$_msr_dec" oflag=seek_bytes 2>&1 | grep -qF 'Operation not permitted'; then
|
|
_write_denied=1
|
|
fi
|
|
fi
|
|
# 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/$_core/msr') and seek(M,$_msr_dec,0) and exit(syswrite(M,pack('H16',0)))"; [ $? -eq 8 ] && ret=0
|
|
else
|
|
_debug "write_msr: got no wrmsr, perl or recent enough dd!"
|
|
mockme=$(printf "%b\n%b" "$mockme" "SMC_MOCK_WRMSR_${_msr}_RET=$WRITE_MSR_RET_ERR")
|
|
write_msr_msg="missing tool, install either msr-tools or perl"
|
|
return $WRITE_MSR_RET_ERR
|
|
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
|
|
# yet more recent versions of the msr module can be set to msr.allow_writes=off, in which case no dmesg message is printed,
|
|
# but the write fails
|
|
if [ "$_write_denied" = 1 ]; then
|
|
_debug "write_msr: writing to msr has been denied"
|
|
mockme=$(printf "%b\n%b" "$mockme" "SMC_MOCK_WRMSR_${_msr}_RET=$WRITE_MSR_RET_LOCKDOWN")
|
|
msr_locked_down=1
|
|
write_msr_msg="your kernel is configured to deny writes to MSRs from user space"
|
|
return $WRITE_MSR_RET_LOCKDOWN
|
|
elif 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=$WRITE_MSR_RET_LOCKDOWN")
|
|
msr_locked_down=1
|
|
write_msr_msg="your kernel is locked down (Fedora/Red Hat), please reboot without secure boot and retry"
|
|
return $WRITE_MSR_RET_LOCKDOWN
|
|
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=$WRITE_MSR_RET_LOCKDOWN")
|
|
msr_locked_down=1
|
|
write_msr_msg="your kernel is locked down, please reboot with lockdown=none in the kernel cmdline and retry"
|
|
return $WRITE_MSR_RET_LOCKDOWN
|
|
fi
|
|
unset _write_denied
|
|
fi
|
|
fi
|
|
|
|
# normalize ret
|
|
if [ "$ret" = 0 ]; then
|
|
ret=$WRITE_MSR_RET_OK
|
|
else
|
|
ret=$WRITE_MSR_RET_KO
|
|
fi
|
|
_debug "write_msr: for cpu $_core 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.
|
|
# returned data is available in $read_msr_value
|
|
READ_MSR_RET_OK=0
|
|
READ_MSR_RET_KO=1
|
|
READ_MSR_RET_ERR=2
|
|
read_msr()
|
|
{
|
|
if [ "$opt_cpu" != all ]; then
|
|
# we only have one core to read, do it and return the result
|
|
read_msr_one_core $opt_cpu "$@"
|
|
return $?
|
|
fi
|
|
|
|
# otherwise we must read all cores
|
|
for _core in $(seq 0 $max_core_id); do
|
|
read_msr_one_core "$_core" "$@"; ret=$?
|
|
if [ "$_core" = 0 ]; then
|
|
# save the result of the first core, for comparison with the others
|
|
_first_core_ret=$ret
|
|
_first_core_value=$read_msr_value
|
|
else
|
|
# compare first core with the other ones
|
|
if [ $_first_core_ret != $ret ] || [ "$_first_core_value" != "$read_msr_value" ]; then
|
|
read_msr_msg="result is not homogeneous between all cores, at least core 0 and $_core differ!"
|
|
return $READ_MSR_RET_ERR
|
|
fi
|
|
fi
|
|
done
|
|
# if we're here, all cores agree, return the result
|
|
return $ret
|
|
}
|
|
|
|
read_msr_one_core()
|
|
{
|
|
_core="$1"
|
|
_msr_dec=$(( $2 ))
|
|
_msr=$(printf "0x%x" "$_msr_dec")
|
|
|
|
read_msr_value=''
|
|
read_msr_msg='unknown error'
|
|
|
|
_mockvarname="SMC_MOCK_RDMSR_${_msr}"
|
|
# shellcheck disable=SC2086,SC1083
|
|
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 $READ_MSR_RET_OK
|
|
fi
|
|
|
|
_mockvarname="SMC_MOCK_RDMSR_${_msr}_RET"
|
|
# shellcheck disable=SC2086,SC1083
|
|
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 [ ! -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
|
|
read_msr_msg="is msr kernel module available?"
|
|
return $READ_MSR_RET_ERR
|
|
fi
|
|
|
|
if [ "$os" != Linux ]; then
|
|
# for BSD
|
|
_msr=$(cpucontrol -m "$_msr" "/dev/cpuctl$_core" 2>/dev/null); ret=$?
|
|
if [ $ret -ne 0 ]; then
|
|
mockme=$(printf "%b\n%b" "$mockme" "SMC_MOCK_RDMSR_${_msr}_RET=$READ_MSR_RET_KO")
|
|
return $READ_MSR_RET_KO
|
|
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/"$_core"/msr ]; then
|
|
mockme=$(printf "%b\n%b" "$mockme" "SMC_MOCK_RDMSR_${_msr}_RET=$READ_MSR_RET_ERR")
|
|
read_msr_msg="No read permission for /dev/cpu/$_core/msr"
|
|
return $READ_MSR_RET_ERR
|
|
# 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/$_core/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/"$_core"/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=$READ_MSR_RET_ERR")
|
|
read_msr_msg='missing tool, install either msr-tools or perl'
|
|
return $READ_MSR_RET_ERR
|
|
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=$READ_MSR_RET_KO")
|
|
return $READ_MSR_RET_KO
|
|
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 $READ_MSR_RET_OK
|
|
}
|
|
|
|
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: "
|
|
# the new MSR 'SPEC_CTRL' is at offset 0x48
|
|
read_msr 0x48; ret=$?
|
|
if [ $ret = $READ_MSR_RET_OK ]; then
|
|
spec_ctrl_msr=1
|
|
pstatus green YES
|
|
elif [ $ret = $READ_MSR_RET_KO ]; then
|
|
spec_ctrl_msr=0
|
|
pstatus yellow NO
|
|
else
|
|
spec_ctrl_msr=-1
|
|
pstatus yellow UNKNOWN "$read_msr_msg"
|
|
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
|
|
cpuid_ibrs=''
|
|
if is_intel; then
|
|
read_cpuid 0x7 0x0 $EDX 26 1 1; ret=$?
|
|
if [ $ret = $READ_CPUID_RET_OK ]; 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 0x0 $EBX 14 1 1; ret=$?
|
|
if [ $ret = $READ_CPUID_RET_OK ]; then
|
|
pstatus green YES "IBRS_SUPPORT feature bit"
|
|
cpuid_ibrs='IBRS_SUPPORT'
|
|
fi
|
|
else
|
|
ret=invalid
|
|
pstatus yellow NO "unknown CPU"
|
|
fi
|
|
if [ $ret = $READ_CPUID_RET_KO ]; then
|
|
pstatus yellow NO
|
|
elif [ $ret = $READ_CPUID_RET_ERR ]; then
|
|
pstatus yellow UNKNOWN "$read_cpuid_msg"
|
|
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 0x0 $EBX 16 1 1; ret=$?
|
|
if [ $ret = $READ_CPUID_RET_OK ]; then
|
|
pstatus green YES
|
|
elif [ $ret = $READ_CPUID_RET_KO ]; then
|
|
pstatus yellow NO
|
|
else
|
|
pstatus yellow UNKNOWN "$read_cpuid_msg"
|
|
fi
|
|
|
|
_info_nol " * CPU indicates preferring IBRS over retpoline: "
|
|
# amd or hygon
|
|
read_cpuid 0x80000008 0x0 $EBX 18 1 1; ret=$?
|
|
if [ $ret = $READ_CPUID_RET_OK ]; then
|
|
pstatus green YES
|
|
elif [ $ret = $READ_CPUID_RET_KO ]; then
|
|
pstatus yellow NO
|
|
else
|
|
pstatus yellow UNKNOWN "$read_cpuid_msg"
|
|
fi
|
|
fi
|
|
|
|
# IBPB
|
|
_info " * Indirect Branch Prediction Barrier (IBPB)"
|
|
|
|
if [ "$opt_allow_msr_write" = 1 ]; then
|
|
_info_nol " * PRED_CMD MSR is available: "
|
|
# the new MSR 'PRED_CTRL' is at offset 0x49, write-only
|
|
write_msr 0x49; ret=$?
|
|
if [ $ret = $WRITE_MSR_RET_OK ]; then
|
|
pstatus green YES
|
|
elif [ $ret = $WRITE_MSR_RET_KO ]; then
|
|
pstatus yellow NO
|
|
else
|
|
pstatus yellow UNKNOWN "$write_msr_msg"
|
|
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 0x0 $EBX 12 1 1; ret=$?
|
|
if [ $ret = $READ_CPUID_RET_OK ]; then
|
|
cpuid_ibpb='IBPB_SUPPORT'
|
|
pstatus green YES "IBPB_SUPPORT feature bit"
|
|
elif [ $ret = $READ_CPUID_RET_KO ]; then
|
|
pstatus yellow NO
|
|
else
|
|
pstatus yellow UNKNOWN "$read_cpuid_msg"
|
|
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 0x0 $EDX 27 1 1; ret=$?
|
|
if [ $ret = $READ_CPUID_RET_OK ]; then
|
|
pstatus green YES "Intel STIBP feature bit"
|
|
#cpuid_stibp='Intel STIBP'
|
|
fi
|
|
elif is_amd; then
|
|
read_cpuid 0x80000008 0x0 $EBX 15 1 1; ret=$?
|
|
if [ $ret = $READ_CPUID_RET_OK ]; then
|
|
pstatus green YES "AMD STIBP feature bit"
|
|
#cpuid_stibp='AMD STIBP'
|
|
fi
|
|
elif is_hygon; then
|
|
read_cpuid 0x80000008 0x0 $EBX 15 1 1; ret=$?
|
|
if [ $ret = $READ_CPUID_RET_OK ]; then
|
|
pstatus green YES "HYGON STIBP feature bit"
|
|
#cpuid_stibp='HYGON STIBP'
|
|
fi
|
|
else
|
|
ret=invalid
|
|
pstatus yellow UNKNOWN "unknown CPU"
|
|
fi
|
|
if [ $ret = $READ_CPUID_RET_KO ]; then
|
|
pstatus yellow NO
|
|
elif [ $ret = $READ_CPUID_RET_ERR ]; then
|
|
pstatus yellow UNKNOWN "$read_cpuid_msg"
|
|
fi
|
|
|
|
|
|
if is_amd || is_hygon; then
|
|
_info_nol " * CPU indicates preferring STIBP always-on: "
|
|
read_cpuid 0x80000008 0x0 $EBX 17 1 1; ret=$?
|
|
if [ $ret = $READ_CPUID_RET_OK ]; then
|
|
pstatus green YES
|
|
elif [ $ret = $READ_CPUID_RET_KO ]; then
|
|
pstatus yellow NO
|
|
else
|
|
pstatus yellow UNKNOWN "$read_cpuid_msg"
|
|
fi
|
|
fi
|
|
|
|
# variant 4
|
|
if is_intel; then
|
|
_info " * Speculative Store Bypass Disable (SSBD)"
|
|
_info_nol " * CPU indicates SSBD capability: "
|
|
read_cpuid 0x7 0x0 $EDX 31 1 1; ret24=$?; ret25=$ret24
|
|
if [ $ret24 = $READ_CPUID_RET_OK ]; 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 0x0 $EBX 24 1 1; ret24=$?
|
|
read_cpuid 0x80000008 0x0 $EBX 25 1 1; ret25=$?
|
|
if [ $ret24 = $READ_CPUID_RET_OK ]; then
|
|
cpuid_ssbd='AMD SSBD in SPEC_CTRL'
|
|
#cpuid_ssbd_spec_ctrl=1
|
|
elif [ $ret25 = $READ_CPUID_RET_OK ]; 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 0x0 $EBX 24 1 1; ret24=$?
|
|
read_cpuid 0x80000008 0x0 $EBX 25 1 1; ret25=$?
|
|
|
|
if [ $ret24 = $READ_CPUID_RET_OK ]; then
|
|
cpuid_ssbd='HYGON SSBD in SPEC_CTRL'
|
|
#hygon cpuid_ssbd_spec_ctrl=1
|
|
elif [ $ret25 = $READ_CPUID_RET_OK ]; 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" = $READ_CPUID_RET_ERR ] && [ "$ret25" = $READ_CPUID_RET_ERR ]; then
|
|
pstatus yellow UNKNOWN "$read_cpuid_msg"
|
|
else
|
|
pstatus yellow NO
|
|
fi
|
|
|
|
amd_ssb_no=0
|
|
hygon_ssb_no=0
|
|
if is_amd; then
|
|
# similar to SSB_NO for intel
|
|
read_cpuid 0x80000008 0x0 $EBX 26 1 1; ret=$?
|
|
if [ $ret = $READ_CPUID_RET_OK ]; then
|
|
amd_ssb_no=1
|
|
elif [ $ret = $READ_CPUID_RET_ERR ]; 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 0x0 $EBX 26 1 1; ret=$?
|
|
if [ $ret = $READ_CPUID_RET_OK ]; then
|
|
hygon_ssb_no=1
|
|
elif [ $ret = $READ_CPUID_RET_ERR ]; then
|
|
hygon_ssb_no=-1
|
|
fi
|
|
fi
|
|
|
|
_info " * L1 data cache invalidation"
|
|
|
|
if [ "$opt_allow_msr_write" = 1 ]; then
|
|
_info_nol " * FLUSH_CMD MSR is available: "
|
|
# the new MSR 'FLUSH_CMD' is at offset 0x10b, write-only
|
|
write_msr 0x10b; ret=$?
|
|
if [ $ret = $WRITE_MSR_RET_OK ]; then
|
|
pstatus green YES
|
|
cpu_flush_cmd=1
|
|
elif [ $ret = $WRITE_MSR_RET_KO ]; then
|
|
pstatus yellow NO
|
|
cpu_flush_cmd=0
|
|
else
|
|
pstatus yellow UNKNOWN "$write_msr_msg"
|
|
cpu_flush_cmd=-1
|
|
fi
|
|
fi
|
|
|
|
# CPUID of L1D
|
|
_info_nol " * CPU indicates L1D flush capability: "
|
|
read_cpuid 0x7 0x0 $EDX 28 1 1; ret=$?
|
|
if [ $ret = $READ_CPUID_RET_OK ]; then
|
|
pstatus green YES "L1D flush feature bit"
|
|
cpuid_l1df=1
|
|
elif [ $ret = $READ_CPUID_RET_KO ]; then
|
|
pstatus yellow NO
|
|
cpuid_l1df=0
|
|
else
|
|
pstatus yellow UNKNOWN "$read_cpuid_msg"
|
|
cpuid_l1df=-1
|
|
fi
|
|
|
|
# if we weren't allowed to probe the write-only MSR but the CPUID
|
|
# bit says that it shoul be there, make the assumption that it is
|
|
if [ "$opt_allow_msr_write" != 1 ]; then
|
|
cpu_flush_cmd=$cpuid_l1df
|
|
fi
|
|
|
|
if is_intel; then
|
|
_info " * Microarchitectural Data Sampling"
|
|
_info_nol " * VERW instruction is available: "
|
|
read_cpuid 0x7 0x0 $EDX 10 1 1; ret=$?
|
|
if [ $ret = $READ_CPUID_RET_OK ]; then
|
|
cpuid_md_clear=1
|
|
pstatus green YES "MD_CLEAR feature bit"
|
|
elif [ $ret = $READ_CPUID_RET_KO ]; then
|
|
cpuid_md_clear=0
|
|
pstatus yellow NO
|
|
else
|
|
cpuid_md_clear=-1
|
|
pstatus yellow UNKNOWN "$read_cpuid_msg"
|
|
fi
|
|
fi
|
|
|
|
if is_intel; then
|
|
_info " * Indirect Branch Predictor Controls"
|
|
_info_nol " * Indirect Predictor Disable feature is available: "
|
|
read_cpuid 0x7 0x2 $EDX 1 1 1; ret=$?
|
|
if [ $ret = $READ_CPUID_RET_OK ]; then
|
|
cpuid_ipred=1
|
|
pstatus green YES "IPRED_CTRL feature bit"
|
|
elif [ $ret = $READ_CPUID_RET_KO ]; then
|
|
cpuid_ipred=0
|
|
pstatus yellow NO
|
|
else
|
|
cpuid_ipred=-1
|
|
pstatus yellow UNKNOWN "$read_cpuid_msg"
|
|
fi
|
|
|
|
_info_nol " * Bottomless RSB Disable feature is available: "
|
|
read_cpuid 0x7 0x2 $EDX 2 1 1; ret=$?
|
|
if [ $ret = $READ_CPUID_RET_OK ]; then
|
|
cpuid_rrsba=1
|
|
pstatus green YES "RRSBA_CTRL feature bit"
|
|
elif [ $ret = $READ_CPUID_RET_KO ]; then
|
|
cpuid_rrsba=0
|
|
pstatus yellow NO
|
|
else
|
|
cpuid_rrsba=-1
|
|
pstatus yellow UNKNOWN "$read_cpuid_msg"
|
|
fi
|
|
|
|
_info_nol " * BHB-Focused Indirect Predictor Disable feature is available: "
|
|
read_cpuid 0x7 0x2 $EDX 2 1 1; ret=$?
|
|
if [ $ret = $READ_CPUID_RET_OK ]; then
|
|
cpuid_bhi=1
|
|
pstatus green YES "BHI_CTRL feature bit"
|
|
elif [ $ret = $READ_CPUID_RET_KO ]; then
|
|
cpuid_bhi=0
|
|
pstatus yellow NO
|
|
else
|
|
cpuid_bhi=-1
|
|
pstatus yellow UNKNOWN "$read_cpuid_msg"
|
|
fi
|
|
|
|
# make shellcheck happy while we're not yet using these new cpuid values in our checks
|
|
export cpuid_ipred cpuid_rrsba cpuid_bhi
|
|
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 0x0 $EDX 29 1 1; ret=$?
|
|
if [ $r |