mirror of
https://github.com/speed47/spectre-meltdown-checker.git
synced 2024-10-03 22:18:02 +02:00
34c6095912
Issue #490 is about retpoline but other options have also changed, as reported by a comment on the issue, this commit fixes these other options: Breno Leitao (10): x86/bugs: Rename CONFIG_GDS_FORCE_MITIGATION => CONFIG_MITIGATION_GDS_FORCE x86/bugs: Rename CONFIG_CPU_IBPB_ENTRY => CONFIG_MITIGATION_IBPB_ENTRY x86/bugs: Rename CONFIG_CALL_DEPTH_TRACKING => CONFIG_MITIGATION_CALL_DEPTH_TRACKING x86/bugs: Rename CONFIG_PAGE_TABLE_ISOLATION => CONFIG_MITIGATION_PAGE_TABLE_ISOLATION x86/bugs: Rename CONFIG_RETPOLINE => CONFIG_MITIGATION_RETPOLINE x86/bugs: Rename CONFIG_SLS => CONFIG_MITIGATION_SLS x86/bugs: Rename CONFIG_CPU_UNRET_ENTRY => CONFIG_MITIGATION_UNRET_ENTRY x86/bugs: Rename CONFIG_CPU_IBRS_ENTRY => CONFIG_MITIGATION_IBRS_ENTRY x86/bugs: Rename CONFIG_CPU_SRSO => CONFIG_MITIGATION_SRSO x86/bugs: Rename CONFIG_RETHUNK => CONFIG_MITIGATION_RETHUNK
7437 lines
288 KiB
Bash
Executable File
7437 lines
288 KiB
Bash
Executable File
#! /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.46+'
|
|
|
|
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"
|
|
[ -n "${linuxfw_tmp:-}" ] && [ -f "$linuxfw_tmp" ] && rm -f "$linuxfw_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.
|
|
can be used multiple times (e.g. --variant 3a --variant l1tf)
|
|
for a list of supported VARIANT parameters, use --variant help
|
|
--cve CVE specify which CVE you'd like to check, by default all supported CVEs are checked
|
|
can be used multiple times (e.g. --cve CVE-2017-5753 --cve CVE-2020-0543)
|
|
--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
|
|
--no-intel-db don't use the builtin Intel DB of affected processors
|
|
--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 "transient execution" (aka "speculative execution") vulnerabilities that started to appear
|
|
since early 2018 with the infamous Spectre & Meltdown.
|
|
|
|
This tool does NOT attempt to run any kind of exploit, and can't 100% 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 affectability to a given vulnerability depends on your CPU model and CPU microcode version, whereas the
|
|
mitigations in place depend on your CPU (model and microcode), your kernel version, and both the runtime configuration
|
|
of your CPU (through bits set through the MSRs) and your kernel. The script attempts to explain everything for each
|
|
vulnerability, so you know where your system stands. For a given vulnerability, detailed information is sometimes
|
|
available using the \`--explain\` switch.
|
|
|
|
Please also note that for the Spectre-like vulnerabilities, all software can possibly be exploited, in which case
|
|
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 it can't be done in a simple way.
|
|
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.
|
|
|
|
For more information and answers to related questions, please refer to the FAQ.md file.
|
|
|
|
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
|
|
opt_intel_db=1
|
|
|
|
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
|
|
CVE-2023-20593
|
|
CVE-2022-40982
|
|
CVE-2023-20569
|
|
CVE-2023-23583'
|
|
|
|
# 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)";;
|
|
CVE-2023-20593) echo "Zenbleed, cross-process information leak";;
|
|
CVE-2022-40982) echo "Downfall, gather data sampling (GDS)";;
|
|
CVE-2023-20569) echo "Inception, return address security (RAS)";;
|
|
CVE-2023-23583) echo "Reptar, redundant prefix issue";;
|
|
*) 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;;
|
|
CVE-2023-20593) return $variant_zenbleed;;
|
|
CVE-2022-40982) return $variant_downfall;;
|
|
CVE-2023-20569) return $variant_inception;;
|
|
CVE-2023-23583) return $variant_reptar;;
|
|
*) 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 CPU is Intel and is in our dump of the Intel official affected CPUs page, use it:
|
|
if is_intel; then
|
|
cpuid_hex=$(printf "0x%08X" $(( cpu_cpuid )) )
|
|
if [ "${intel_line:-}" = "no" ]; then
|
|
_debug "is_cpu_affected: $cpuid_hex not in Intel database (cached)"
|
|
elif [ -z "$intel_line" ]; then
|
|
intel_line=$(read_inteldb | grep -F "$cpuid_hex," | head -n1)
|
|
if [ -z "$intel_line" ]; then
|
|
intel_line=no
|
|
_debug "is_cpu_affected: $cpuid_hex not in Intel database"
|
|
fi
|
|
fi
|
|
if [ "$intel_line" != "no" ]; then
|
|
_result=$(echo "$intel_line" | grep -Eo ,"$(echo "$1" | cut -c5-)"'=[^,]+' | cut -d= -f2)
|
|
_debug "is_cpu_affected: inteldb for $1 says '$_result'"
|
|
|
|
# handle special case for Foreshadow SGX (CVE-2018-3615):
|
|
# even if we are affected to L1TF (CVE-2018-3620/CVE-2018-3646), if there's no SGX on our CPU,
|
|
# then we're not affected to the original Foreshadow.
|
|
if [ "$1" = "CVE-2018-3615" ] && [ "$cpuid_sgx" = 0 ]; then
|
|
# not affected
|
|
return 1
|
|
fi
|
|
# /special case
|
|
|
|
if [ "$_result" = "N" ]; then
|
|
# not affected
|
|
return 1
|
|
elif [ -n "$_result" ]; then
|
|
# non-empty string != N means affected
|
|
return 0
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# Otherwise, do it ourselves
|
|
|
|
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=''
|
|
# Zenbleed and Inception are both AMD specific, look for "is_amd" below:
|
|
variant_zenbleed=immune
|
|
variant_inception=immune
|
|
# Downfall & Reptar are Intel specific, look for "is_intel" below:
|
|
variant_downfall=immune
|
|
variant_reptar=immune
|
|
|
|
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
|
|
# Downfall
|
|
if [ "$capabilities_gds_no" = 1 ]; then
|
|
# capability bit for future Intel processors that will explicitly state
|
|
# that they're unaffected by GDS. Also set by hypervisors on virtual CPUs
|
|
# so that the guest kernel doesn't try to mitigate GDS when it's already mitigated on the host
|
|
_debug "is_cpu_affected: downfall: not affected (GDS_NO)"
|
|
elif [ "$cpu_family" = 6 ]; then
|
|
# list from https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=64094e7e3118aff4b0be8ff713c242303e139834
|
|
set -u
|
|
if [ "$cpu_model" = "$INTEL_FAM6_SKYLAKE_X" ] || \
|
|
[ "$cpu_model" = "$INTEL_FAM6_KABYLAKE_L" ] || \
|
|
[ "$cpu_model" = "$INTEL_FAM6_KABYLAKE" ] || \
|
|
[ "$cpu_model" = "$INTEL_FAM6_ICELAKE_L" ] || \
|
|
[ "$cpu_model" = "$INTEL_FAM6_ICELAKE_D" ] || \
|
|
[ "$cpu_model" = "$INTEL_FAM6_ICELAKE_X" ] || \
|
|
[ "$cpu_model" = "$INTEL_FAM6_COMETLAKE" ] || \
|
|
[ "$cpu_model" = "$INTEL_FAM6_COMETLAKE_L" ] || \
|
|
[ "$cpu_model" = "$INTEL_FAM6_TIGERLAKE_L" ] || \
|
|
[ "$cpu_model" = "$INTEL_FAM6_TIGERLAKE" ] || \
|
|
[ "$cpu_model" = "$INTEL_FAM6_ROCKETLAKE" ]; then
|
|
_debug "is_cpu_affected: downfall: affected"
|
|
variant_downfall=vuln
|
|
elif [ "$has_avx2" = 0 ] && [ "$has_avx512" = 0 ]; then
|
|
_debug "is_cpu_affected: downfall: no avx; immune"
|
|
else
|
|
# old Intel CPU (not in their DB), not listed as being affected by the Linux kernel,
|
|
# but with AVX2 or AVX512: unclear for now
|
|
_debug "is_cpu_affected: downfall: unclear, defaulting to non-affected for now"
|
|
fi
|
|
set +u
|
|
fi
|
|
# Reptar
|
|
# the only way to know whether a CPU is vuln, is to check whether there is a known ucode update for it,
|
|
# as the mitigation is only ucode-based and there's no flag exposed by the kernel or by an updated ucode.
|
|
# we have to hardcode the truthtable of affected CPUs vs updated ucodes...
|
|
# https://www.intel.com/content/www/us/en/developer/articles/technical/software-security-guidance/advisory-guidance/redundant-prefix-issue.html
|
|
# list taken from:
|
|
# https://github.com/intel/Intel-Linux-Processor-Microcode-Data-Files/commit/ece0d294a29a1375397941a4e6f2f7217910bc89#diff-e6fad0f2abbac6c9603b2e8f88fe1d151a83de708aeca1c1d93d881c958ecba4R26
|
|
# both pages have a lot of inconsistencies, I've tried to fix the errors the best I could, the logic being: if it's not in the
|
|
# blog page, then the microcode update in the commit is not related to reptar, if microcode versions differ, then the one in github is correct,
|
|
# if a stepping exists in the blog page but not in the commit, then the blog page is right
|
|
reptar_ucode_list='
|
|
06-97-02/07,00000032
|
|
06-97-05/07,00000032
|
|
06-9a-03/80,00000430
|
|
06-9a-04/80,00000430
|
|
06-6c-01/10,01000268
|
|
06-6a-06/87,0d0003b9
|
|
06-7e-05/80,000000c2
|
|
06-ba-02/e0,0000411c
|
|
06-b7-01/32,0000011d
|
|
06-a7-01/02,0000005d
|
|
06-bf-05/07,00000032
|
|
06-bf-02/07,00000032
|
|
06-ba-03/e0,0000411c
|
|
06-8f-08/87,2b0004d0
|
|
06-8f-07/87,2b0004d0
|
|
06-8f-06/87,2b0004d0
|
|
06-8f-05/87,2b0004d0
|
|
06-8f-04/87,2b0004d0
|
|
06-8f-08/10,2c000290
|
|
06-8c-01/80,000000b4
|
|
06-8c-00/ff,000000b4
|
|
06-8d-01/c2,0000004e
|
|
06-8d-00/c2,0000004e
|
|
06-8c-02/c2,00000034
|
|
'
|
|
for tuple in $reptar_ucode_list; do
|
|
fixed_ucode_ver=$(( 0x$(echo "$tuple" | cut -d, -f2) ))
|
|
affected_fmspi=$(echo "$tuple" | cut -d, -f1)
|
|
affected_fms=$(echo "$affected_fmspi" | cut -d/ -f1)
|
|
ucode_platformid_mask=0x$(echo "$affected_fmspi" | cut -d/ -f2)
|
|
affected_cpuid=$(fms2cpuid \
|
|
0x"$(echo "$affected_fms" | cut -d- -f1)" \
|
|
0x"$(echo "$affected_fms" | cut -d- -f2)" \
|
|
0x"$(echo "$affected_fms" | cut -d- -f3)" \
|
|
)
|
|
if [ "$cpu_cpuid" = "$affected_cpuid" ] && [ $((cpu_platformid & ucode_platformid_mask)) -gt 0 ]; then
|
|
# this is not perfect as Intel never tells about their EOL CPUs, so more CPUs might be affected but there's no way to tell
|
|
variant_reptar=vuln
|
|
reptar_fixed_ucode_version=$fixed_ucode_ver
|
|
break
|
|
fi
|
|
done
|
|
|
|
|
|
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
|
|
|
|
# Zenbleed
|
|
amd_legacy_erratum "$(amd_model_range 0x17 0x30 0x0 0x4f 0xf)" && variant_zenbleed=vuln
|
|
amd_legacy_erratum "$(amd_model_range 0x17 0x60 0x0 0x7f 0xf)" && variant_zenbleed=vuln
|
|
amd_legacy_erratum "$(amd_model_range 0x17 0xa0 0x0 0xaf 0xf)" && variant_zenbleed=vuln
|
|
|
|
# Inception (according to kernel, zen 1 to 4)
|
|
if [ "$cpu_family" = $(( 0x17 )) ] || [ "$cpu_family" = $(( 0x19 )) ]; then
|
|
variant_inception=vuln
|
|
fi
|
|
|
|
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 Neoverse-N1 Neoverse-V2
|
|
# part ? ? c08 c09 c0d c0f c0e d07 d08 d09 d0a d0b d0d d0c d40 d49 d4f
|
|
# arch 7? 7? 7 7 7 7 7 8 8 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 -e 0xd49 -e 0xd4f; then
|
|
variant1=vuln
|
|
[ -z "$variant2" ] && variant2=immune
|
|
[ -z "$variant3" ] && variant3=immune
|
|
[ -z "$variant3a" ] && variant3a=immune
|
|
[ -z "$variant4" ] && variant4=immune
|
|
_debug "checking cpu$i: armv8 NeoverseN2/V1/V2 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
|
|
[ "$variant_zenbleed" = "immune" ] && variant_zenbleed=1 || variant_zenbleed=0
|
|
[ "$variant_downfall" = "immune" ] && variant_downfall=1 || variant_downfall=0
|
|
[ "$variant_inception" = "immune" ] && variant_inception=1 || variant_inception=0
|
|
[ "$variant_reptar" = "immune" ] && variant_reptar=1 || variant_reptar=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
|
|
}
|
|
|
|
# Family-Model-Stepping to CPUID
|
|
# prints CPUID in base-10 to stdout
|
|
fms2cpuid()
|
|
{
|
|
_family="$1"
|
|
_model="$2"
|
|
_stepping="$3"
|
|
|
|
if [ "$(( _family ))" -le 15 ]; then
|
|
_extfamily=0
|
|
_lowfamily=$(( _family ))
|
|
else
|
|
# when we have a family > 0xF, then lowfamily is stuck at 0xF
|
|
# and extfamily is ADDED to it (as in "+"), to ensure old software
|
|
# never sees a lowfamily < 0xF for newer families
|
|
_lowfamily=15
|
|
_extfamily=$(( (_family) - 15 ))
|
|
fi
|
|
_extmodel=$(( (_model & 0xF0 ) >> 4 ))
|
|
_lowmodel=$(( (_model & 0x0F ) >> 0 ))
|
|
echo $(( (_stepping & 0x0F) | (_lowmodel << 4) | (_lowfamily << 8) | (_extmodel << 16) | (_extfamily << 20) ))
|
|
}
|
|
|
|
download_file()
|
|
{
|
|
_url="$1"
|
|
_file="$2"
|
|
if command -v wget >/dev/null 2>&1; then
|
|
wget -q "$_url" -O "$_file"; ret=$?
|
|
elif command -v curl >/dev/null 2>&1; then
|
|
curl -sL "$_url" -o "$_file"; ret=$?
|
|
elif command -v fetch >/dev/null 2>&1; then
|
|
fetch -q "$_url" -o "$_file"; ret=$?
|
|
else
|
|
echo ERROR "please install one of \`wget\`, \`curl\` of \`fetch\` programs"
|
|
unset _file _url
|
|
return 1
|
|
fi
|
|
unset _file _url
|
|
if [ "$ret" != 0 ]; then
|
|
echo ERROR "error $ret"
|
|
return $ret
|
|
fi
|
|
echo DONE
|
|
}
|
|
|
|
[ -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... "
|
|
download_file "$mcedb_url" "$mcedb_tmp" || return $?
|
|
|
|
# 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
|
|
download_file "$intel_url" "$intel_tmp/fw.zip" || return $?
|
|
|
|
# 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" "ALTER TABLE \"Intel\" ADD COLUMN \"pfmask\" TEXT"
|
|
sqlite3 "$mcedb_tmp" "ALTER TABLE \"AMD\" ADD COLUMN \"origin\" TEXT"
|
|
sqlite3 "$mcedb_tmp" "ALTER TABLE \"AMD\" ADD COLUMN \"pfmask\" TEXT"
|
|
sqlite3 "$mcedb_tmp" "UPDATE \"Intel\" SET \"origin\"='mce'"
|
|
sqlite3 "$mcedb_tmp" "UPDATE \"Intel\" SET \"pfmask\"='FF'"
|
|
sqlite3 "$mcedb_tmp" "UPDATE \"AMD\" SET \"origin\"='mce'"
|
|
sqlite3 "$mcedb_tmp" "UPDATE \"AMD\" SET \"pfmask\"='FF'"
|
|
|
|
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
|
|
_cpuid=$(echo "$_line" | grep -Eio 'sig 0x[0-9a-f]+' | awk '{print $2}')
|
|
_cpuid=$(( _cpuid ))
|
|
_cpuid=$(printf "%08X" "$_cpuid")
|
|
_pfmask=$(echo "$_line" | grep -Eio 'pf_mask 0x[0-9a-f]+' | awk '{print $2}')
|
|
_pfmask=$(( _pfmask ))
|
|
_pfmask=$(printf "%02X" $_pfmask)
|
|
_date=$(echo "$_line" | grep -Eo '(19|20)[0-9][0-9]-[01][0-9]-[0-3][0-9]' | tr -d '-')
|
|
_version=$(echo "$_line" | grep -Eio 'rev 0x[0-9a-f]+' | awk '{print $2}')
|
|
_version=$(( _version ))
|
|
_version=$(printf "%08X" "$_version")
|
|
# 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\" = '$_cpuid';"
|
|
# then insert our version
|
|
sqlite3 "$mcedb_tmp" "INSERT INTO \"Intel\" (\"origin\",\"cpuid\",\"pfmask\",\"version\",\"yyyymmdd\") VALUES ('intel','$_cpuid','$_pfmask','$_version','$_date');"
|
|
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)"
|
|
|
|
# now parse the most recent linux-firmware amd-ucode README file
|
|
_info_nol "Fetching latest amd-ucode README from linux-firmware project... "
|
|
linuxfw_url="https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git/plain/amd-ucode/README"
|
|
linuxfw_tmp=$(mktemp -t smc-linuxfw-XXXXXX)
|
|
download_file "$linuxfw_url" "$linuxfw_tmp" || return $?
|
|
|
|
_info_nol "Parsing the README... "
|
|
nbfound=0
|
|
for line in $(grep -E 'Family=0x[0-9a-f]+ Model=0x[0-9a-f]+ Stepping=0x[0-9a-f]+: Patch=0x[0-9a-f]+' "$linuxfw_tmp" | tr " " ","); do
|
|
_debug "Parsing line $line"
|
|
_family=$( echo "$line" | grep -Eoi 'Family=0x[0-9a-f]+' | cut -d= -f2)
|
|
_model=$( echo "$line" | grep -Eoi 'Model=0x[0-9a-f]+' | cut -d= -f2)
|
|
_stepping=$(echo "$line" | grep -Eoi 'Stepping=0x[0-9a-f]+' | cut -d= -f2)
|
|
_version=$( echo "$line" | grep -Eoi 'Patch=0x[0-9a-f]+' | cut -d= -f2)
|
|
_version=$(printf "%08X" "$(( _version ))")
|
|
_cpuid=$(fms2cpuid "$_family" "$_model" "$_stepping")
|
|
_cpuid=$(printf "%08X" "$_cpuid")
|
|
_sqlstm="INSERT INTO \"AMD\" (\"origin\",\"cpuid\",\"pfmask\",\"version\",\"yyyymmdd\") VALUES ('linux-firmware','$_cpuid','FF','$_version','20000101')"
|
|
_debug "family $_family model $_model stepping $_stepping cpuid $_cpuid"
|
|
_debug "$_sqlstm"
|
|
sqlite3 "$mcedb_tmp" "$_sqlstm"
|
|
nbfound=$((nbfound + 1))
|
|
unset _family _model _stepping _version _cpuid _date _sqlstm
|
|
done
|
|
echo "found $nbfound microcodes"
|
|
unset nbfound
|
|
|
|
dbversion="$mcedb_revision+i$_intel_latest_date"
|
|
linuxfw_hash=$(md5sum "$linuxfw_tmp" 2>/dev/null | cut -c1-4)
|
|
if [ -n "$linuxfw_hash" ]; then
|
|
dbversion="$dbversion+$linuxfw_hash"
|
|
fi
|
|
|
|
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";
|
|
# we'll use the more recent fw for Intel and AMD
|
|
sqlite3 "$mcedb_tmp" "SELECT '# I,0x'||\"t1\".\"cpuid\"||',0x'||\"t1\".\"pfmask\"||',0x'||MAX(\"t1\".\"version\")||','||\"t1\".\"yyyymmdd\" FROM \"Intel\" AS \"t1\" LEFT OUTER JOIN \"Intel\" AS \"t2\" ON \"t2\".\"cpuid\"=\"t1\".\"cpuid\" AND \"t2\".\"pfmask\"=\"t1\".\"pfmask\" AND \"t2\".\"yyyymmdd\" > \"t1\".\"yyyymmdd\" WHERE \"t2\".\"yyyymmdd\" IS NULL GROUP BY \"t1\".\"cpuid\",\"t1\".\"pfmask\" ORDER BY \"t1\".\"cpuid\",\"t1\".\"pfmask\" ASC;" | grep -v '^# .,0x00000000,';
|
|
sqlite3 "$mcedb_tmp" "SELECT '# A,0x'||\"t1\".\"cpuid\"||',0x'||\"t1\".\"pfmask\"||',0x'||MAX(\"t1\".\"version\")||','||\"t1\".\"yyyymmdd\" FROM \"AMD\" AS \"t1\" LEFT OUTER JOIN \"AMD\" AS \"t2\" ON \"t2\".\"cpuid\"=\"t1\".\"cpuid\" AND \"t2\".\"pfmask\"=\"t1\".\"pfmask\" AND \"t2\".\"yyyymmdd\" > \"t1\".\"yyyymmdd\" WHERE \"t2\".\"yyyymmdd\" IS NULL GROUP BY \"t1\".\"cpuid\",\"t1\".\"pfmask\" ORDER BY \"t1\".\"cpuid\",\"t1\".\"pfmask\" 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" = "--no-intel-db" ]; then
|
|
opt_intel_db=0
|
|
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 (see --variant help)" >&2
|
|
exit 255
|
|
fi
|
|
case "$2" in
|
|
help) echo "The following parameters are supported for --variant (can be used multiple times):";
|
|
echo "1, 2, 3, 3a, 4, msbds, mfbds, mlpds, mdsum, l1tf, taa, mcepsc, srbds, zenbleed, downfall, inception";
|
|
exit 0;;
|
|
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;;
|
|
zenbleed) opt_cve_list="$opt_cve_list CVE-2023-20593"; opt_cve_all=0;;
|
|
downfall) opt_cve_list="$opt_cve_list CVE-2022-40982"; opt_cve_all=0;;
|
|
inception) opt_cve_list="$opt_cve_list CVE-2023-20569"; opt_cve_all=0;;
|
|
reptar) opt_cve_list="$opt_cve_list CVE-2023-23583"; opt_cve_all=0;;
|
|
*)
|
|
echo "$0: error: invalid parameter '$2' for --variant, see --variant help for a list" >&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";;
|
|
CVE-2023-20593) aka="ZENBLEED";;
|
|
CVE-2022-40982) aka="DOWNFALL";;
|
|
CVE-2023-20569) aka="INCEPTION";;
|
|
CVE-2023-23583) aka="REPTAR";;
|
|
*) 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
|
|
}
|
|
|
|
# write_msr
|
|
# param1 (mandatory): MSR, can be in hex or decimal.
|
|
# param2 (optional): value to write, can be in hex or decimal.
|
|
# param3 (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")
|
|
_value_dec=$(( $3 ))
|
|
_value=$(printf "0x%x" "$_value_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=$_value" "/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 $_value_dec 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"
|
|
awk "BEGIN{printf \"%c\", $_value_dec}" | dd 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 awk "BEGIN{printf \"%c\", $_value_dec}" | dd 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(v4,$_value_dec)))"; [ $? -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, value=$_value, 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
|
|
}
|
|
|
|
|
|
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 ))
|
|
|
|
has_avx2=0
|
|
has_avx512=0
|
|
if [ -e "$procfs/cpuinfo" ]; then
|
|
if grep -qw avx2 "$procfs/cpuinfo" 2>/dev/null; then has_avx2=1; fi
|
|
if grep -qw avx512 "$procfs/cpuinfo" 2>/dev/null; then has_avx512=1; fi
|
|
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
|
|
|
|
# Intel processors have a 3bit Platform ID field in MSR(17H) that specifies the platform type for up to 8 types
|
|
# see https://elixir.bootlin.com/linux/v6.0/source/arch/x86/kernel/cpu/microcode/intel.c#L694
|
|
# Set it to 8 (impossible value as it is 3 bit long) by default
|
|
cpu_platformid=8
|
|
if [ "$cpu_vendor" = GenuineIntel ] && [ "$cpu_model" -ge 5 ]; then
|
|
read_msr 0x17; ret=$?
|
|
if [ $ret = $READ_MSR_RET_OK ]; then
|
|
cpu_platformid=$(( 1 << ( (read_msr_value >> 18) & 7) ))
|
|
fi
|
|
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
|
|
if [ -n "${SMC_MOCK_CPU_PLATFORMID:-}" ]; then
|
|
cpu_platformid="$SMC_MOCK_CPU_PLATFORMID"
|
|
_debug "parse_cpu_details: MOCKING cpu platformid name to $cpu_platformid"
|
|
mocked=1
|
|
else
|
|
mockme=$(printf "%b\n%b" "$mockme" "SMC_MOCK_CPU_PLATFORMID='$cpu_platformid'")
|
|
fi
|
|
|
|
# get raw cpuid, it's always useful (referenced in the Intel doc for firmware updates for example)
|
|
if [ "$mocked" != 1 ] && read_cpuid 0x1 0x0 $EAX 0 0xFFFFFFFF; then
|
|
cpu_cpuid="$read_cpuid_value"
|
|
else
|
|
# try to build it by ourselves
|
|
_debug "parse_cpu_details: build the CPUID by ourselves"
|
|
cpu_cpuid=$(fms2cpuid "$cpu_family" "$cpu_model" "$cpu_stepping")
|
|
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 pfid 0x%x" \
|
|
"$cpu_family" "$cpu_model" "$cpu_stepping" "$cpu_ucode" "$cpu_cpuid" "$cpu_platformid")
|
|
|
|
# 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()
|
|
{
|
|
parse_cpu_details
|
|
[ "$cpu_vendor" = HygonGenuine ] && return 0
|
|
return 1
|
|
}
|
|
|
|
is_amd()
|
|
{
|
|
parse_cpu_details
|
|
[ "$cpu_vendor" = AuthenticAMD ] && return 0
|
|
return 1
|
|
}
|
|
|
|
is_intel()
|
|
{
|
|
parse_cpu_details
|
|
[ "$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
|
|
|
|
# 2024-01-09 update: https://github.com/speed47/spectre-meltdown-checker/issues/475
|
|
# this time the tuple is cpuid,microcode
|
|
for tuple in \
|
|
0xB0671,0x119 \
|
|
0xB06A2,0x4119 \
|
|
0xB06A3,0x4119
|
|
do
|
|
cpuid=$(( $(echo "$tuple" | cut -d, -f1) ))
|
|
ucode=$(( $(echo "$tuple" | cut -d, -f2) ))
|
|
if [ "$cpu_cpuid" = "$cpuid" ] && [ "$cpu_ucode" = "$ucode" ]; then
|
|
_debug "is_ucode_blacklisted: we have a match! ($cpuid/$ucode)"
|
|
return 0
|
|
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
|
|
}
|
|
|
|
# mimick the Linux macro
|
|
##define AMD_MODEL_RANGE(f, m_start, s_start, m_end, s_end) \
|
|
# ((f << 24) | (m_start << 16) | (s_start << 12) | (m_end << 4) | (s_end))
|
|
amd_model_range()
|
|
{
|
|
echo $(( ($1 << 24) | ($2 << 16) | ($3 << 12) | ($4 << 4) | ($5) ))
|
|
}
|
|
|
|
# mimick the Linux func, usage:
|
|
# amd_legacy_erratum $(amd_model_range 0x17 0x30 0x0 0x4f 0xf)
|
|
# return true (0) if the current CPU is affected by this erratum, 1 otherwise
|
|
amd_legacy_erratum()
|
|
{
|
|
_range="$1"
|
|
_ms=$((cpu_model << 4 | cpu_stepping))
|
|
if [ "$cpu_family" = $(( ( (_range) >> 24) & 0xff )) ] && \
|
|
[ $_ms -ge $(( ( (_range) >> 12) & 0xfff )) ] && \
|
|
[ $_ms -le $(( (_range) & 0xfff )) ]; then
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
# returns 0 (true) if yes, 1 otherwise
|
|
# returns 2 if not applicable
|
|
has_zenbleed_fixed_firmware()
|
|
{
|
|
# return cached data
|
|
[ -n "$zenbleed_fw" ] && return "$zenbleed_fw"
|
|
# or compute it:
|
|
zenbleed_fw=2 # unknown
|
|
# only amd
|
|
if ! is_amd; then
|
|
zenbleed_fw=1
|
|
return $zenbleed_fw
|
|
fi
|
|
# list of known fixed firmwares, from commit 522b1d69219d8f083173819fde04f994aa051a98
|
|
_tuples="
|
|
0x30,0x3f,0x0830107a
|
|
0x60,0x67,0x0860010b
|
|
0x68,0x6f,0x08608105
|
|
0x70,0x7f,0x08701032
|
|
0xa0,0xaf,0x08a00008
|
|
"
|
|
for tuple in $_tuples; do
|
|
_model_low=$( echo "$tuple" | cut -d, -f1)
|
|
_model_high=$(echo "$tuple" | cut -d, -f2)
|
|
_fwver=$( echo "$tuple" | cut -d, -f3)
|
|
if [ $((cpu_model)) -ge $((_model_low)) ] && [ $((cpu_model)) -le $((_model_high)) ]; then
|
|
if [ $((cpu_ucode)) -ge $((_fwver)) ]; then
|
|
zenbleed_fw=0 # true
|
|
break
|
|
else
|
|
zenbleed_fw=1 # false
|
|
zenbleed_fw_required=$_fwver
|
|
fi
|
|
fi
|
|
done
|
|
unset _tuples
|
|
return $zenbleed_fw
|
|
}
|
|
|
|
# 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"
|
|
}
|
|
|
|
read_inteldb()
|
|
{
|
|
if [ "$opt_intel_db" = 1 ]; then
|
|
awk '/^# %%% ENDOFINTELDB/ { exit } { if (DELIM==1) { print $2 } } /^# %%% INTELDB/ { DELIM=1 }' "$0"
|
|
fi
|
|
# otherwise don't output nothing, it'll be as if the database is empty
|
|
}
|
|
|
|
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
|
|
# skip if the pfmask doesn't match our platformid
|
|
pfmask=$(echo "$tuple" | cut -d, -f3)
|
|
if is_intel && [ $((cpu_platformid & pfmask)) -eq 0 ]; then
|
|
continue
|
|
fi
|
|
ucode=$(( $(echo "$tuple" | cut -d, -f4) ))
|
|
ucode_date=$(echo "$tuple" | cut -d, -f5 | 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
|
|
_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" |