feat: implement support for NetBSD/FreeBSD/DragonFlyBSD

This commit is contained in:
Stéphane Lesimple 2018-03-25 12:23:46 +02:00
parent 889172dbb1
commit ece25b98a1
2 changed files with 376 additions and 124 deletions

View File

@ -1,10 +1,13 @@
Spectre & Meltdown Checker
==========================
A simple shell script to tell if your Linux installation is vulnerable against the 3 "speculative execution" CVEs that were made public early 2018.
A shell script to tell if your system is vulnerable against the 3 "speculative execution" CVEs that were made public early 2018.
Without options, it'll inspect your currently running kernel.
You can also specify a kernel image on the command line, if you'd like to inspect a kernel you're not running.
Supported systems:
- Linux (all versions and flavors)
- FreeBSD
- NetBSD
- DragonFlyBSD
The script will do its best to detect mitigations, including backported non-vanilla patches, regardless of the advertised kernel version number.

View File

@ -20,8 +20,10 @@ exit_cleanup()
[ -n "$vmlinuxtmp" ] && [ -f "$vmlinuxtmp" ] && rm -f "$vmlinuxtmp"
[ -n "$vmlinuxtmp2" ] && [ -f "$vmlinuxtmp2" ] && rm -f "$vmlinuxtmp2"
[ "$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
}
show_usage()
@ -41,9 +43,9 @@ show_usage()
Second mode is the "offline" mode, where you can inspect a non-running kernel.
You'll need to specify the location of the vmlinux file, config and System.map files:
--kernel vmlinux_file Specify a (possibly compressed) vmlinux file
--config kernel_config Specify a kernel config file
--map kernel_map_file Specify a kernel System.map file
--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)
Options:
--no-color Don't use color codes
@ -100,6 +102,8 @@ This tool has been released in the hope that it'll be useful, but don't use it t
EOF
}
os=$(uname -s)
# parse options
opt_kernel=''
opt_config=''
@ -123,12 +127,19 @@ global_critical=0
global_unknown=0
nrpe_vuln=""
# find a sane `echo` command
# 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
if which echo >/dev/null 2>&1; then
echo_cmd_type=echo
if which printf >/dev/null 2>&1; then
echo_cmd=$(which printf)
echo_cmd_type=printf
elif which echo >/dev/null 2>&1; then
echo_cmd=$(which echo)
else
# 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
@ -143,11 +154,24 @@ __echo()
# strip ANSI color codes
# some sed versions (i.e. toybox) can't seem to handle
# \033 aka \x1B correctly, so do it for them.
_ctrlchar=$($echo_cmd -e "\033")
_msg=$($echo_cmd -e "$_msg" | sed -r "s/$_ctrlchar\[([0-9][0-9]?(;[0-9][0-9]?)?)?m//g")
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
# shellcheck disable=SC2086
$echo_cmd $opt -e "$_msg"
}
_echo()
@ -234,7 +258,7 @@ is_cpu_vulnerable()
# https://github.com/crozone/SpectrePoC/issues/1 ^F E5200 => spectre 2 not vulnerable
# https://github.com/paboldin/meltdown-exploit/issues/19 ^F E5200 => meltdown vulnerable
# model name : Pentium(R) Dual-Core CPU E5200 @ 2.50GHz
if grep -qE '^model name.+ Pentium\(R\) Dual-Core[[:space:]]+CPU[[:space:]]+E[0-9]{4}K? ' /proc/cpuinfo; then
if grep -qE '^model name.+ Pentium\(R\) Dual-Core[[:space:]]+CPU[[:space:]]+E[0-9]{4}K? ' "$procfs/cpuinfo"; then
variant1=vuln
[ -z "$variant2" ] && variant2=immune
variant3=vuln
@ -669,14 +693,32 @@ mount_debugfs()
load_msr()
{
modprobe msr 2>/dev/null && insmod_msr=1
_debug "attempted to load module msr, insmod_msr=$insmod_msr"
if [ "$os" = Linux ]; then
modprobe msr 2>/dev/null && insmod_msr=1
_debug "attempted to load module msr, insmod_msr=$insmod_msr"
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()
{
modprobe cpuid 2>/dev/null && insmod_cpuid=1
_debug "attempted to load module cpuid, insmod_cpuid=$insmod_cpuid"
if [ "$os" = Linux ]; then
modprobe cpuid 2>/dev/null && insmod_cpuid=1
_debug "attempted to load module cpuid, insmod_cpuid=$insmod_cpuid"
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
}
read_cpuid()
@ -685,28 +727,70 @@ read_cpuid()
_bytenum="$2"
_and_operand="$3"
if [ ! -e /dev/cpu/0/cpuid ]; then
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
return 2
if [ -e /dev/cpu/0/cpuid ]; then
# Linux
# we need _leaf to be converted to decimal for dd
_leaf=$(( _leaf ))
if [ "$opt_verbose" -ge 3 ]; then
dd if=/dev/cpu/0/cpuid bs=16 skip="$_leaf" iflag=skip_bytes count=1 >/dev/null 2>/dev/null
_debug "cpuid: reading leaf$_leaf of cpuid on cpu0, ret=$?"
_debug "cpuid: leaf$_leaf eax-ebx-ecx-edx: $( dd if=/dev/cpu/0/cpuid bs=16 skip="$_leaf" iflag=skip_bytes count=1 2>/dev/null | od -x -A n)"
fi
# getting proper byte of edx on leaf$_leaf of cpuinfo in decimal
_reg_byte=$(dd if=/dev/cpu/0/cpuid bs=16 skip="$_leaf" iflag=skip_bytes count=1 2>/dev/null | dd bs=1 skip="$_bytenum" count=1 2>/dev/null | od -t u1 -A n | awk '{print $1}')
_debug "cpuid: leaf$_leaf byte $_bytenum: $_reg_byte (decimal)"
_reg_bit=$(( _reg_byte & _and_operand ))
_debug "cpuid: leaf$_leaf byte $_bytenum & $_and_operand = $_reg_bit"
[ "$_reg_bit" -eq 0 ] && return 1
# $_reg_bit is > 0, so the bit was found: return true (aka 0)
return 0
elif [ -e /dev/cpuctl0 ]; then
# BSD
_cpuid=$(cpucontrol -i "$_leaf" /dev/cpuctl0 2>/dev/null | awk '{print $4,$5,$6,$7}')
# cpuid level 0x1: 0x000306d4 0x00100800 0x4dfaebbf 0xbfebfbff
_debug "cpuid: got $_cpuid for leaf $_leaf"
if [ "$_bytenum" -lt 4 ]; then
_reg_byte=$(echo "$_cpuid" | awk '{print $1}')
_debug "cpuid: $_bytenum is part of EAX ($_reg_byte)"
elif [ "$_bytenum" -lt 8 ]; then
_reg_byte=$(echo "$_cpuid" | awk '{print $2}')
_debug "cpuid: $_bytenum is part of EBX ($_reg_byte)"
elif [ "$_bytenum" -lt 12 ]; then
_reg_byte=$(echo "$_cpuid" | awk '{print $3}')
_debug "cpuid: $_bytenum is part of ECX ($_reg_byte)"
elif [ "$_bytenum" -lt 16 ]; then
_reg_byte=$(echo "$_cpuid" | awk '{print $4}')
_debug "cpuid: $_bytenum is part of EDX ($_reg_byte)"
else
_warn "read_cpuid: error in the program, please report to the developer ($_leaf/$_bytenum/$_and_operand)"
exit 1
fi
_bytenum=$(( _bytenum % 4 ))
case "$_bytenum" in
0) _reg_byte=0x$(echo "$_reg_byte" | cut -c9-10) ;;
1) _reg_byte=0x$(echo "$_reg_byte" | cut -c7-8) ;;
2) _reg_byte=0x$(echo "$_reg_byte" | cut -c5-6) ;;
3) _reg_byte=0x$(echo "$_reg_byte" | cut -c3-4) ;;
*) exit 8;
esac
_debug "cpuid: wanted byte is $_reg_byte"
# convert to decimal
_reg_byte=$(( _reg_byte ))
_debug "cpuid: decimal value is $_reg_byte"
_reg_bit=$(( _reg_byte & _and_operand ))
_debug "cpuid: leaf$_leaf byte $_bytenum & $_and_operand = $_reg_bit"
[ "$_reg_bit" -eq 0 ] && return 1
# $_reg_bit is > 0, so the bit was found: return true (aka 0)
return 0
fi
if [ "$opt_verbose" -ge 3 ]; then
dd if=/dev/cpu/0/cpuid bs=16 skip="$_leaf" iflag=skip_bytes count=1 >/dev/null 2>/dev/null
_debug "cpuid: reading leaf$_leaf of cpuid on cpu0, ret=$?"
_debug "cpuid: leaf$_leaf eax-ebx-ecx-edx: $( dd if=/dev/cpu/0/cpuid bs=16 skip="$_leaf" iflag=skip_bytes count=1 2>/dev/null | od -x -A n)"
_debug "cpuid: leaf$_leaf edx higher byte is: $(dd if=/dev/cpu/0/cpuid bs=16 skip="$_leaf" iflag=skip_bytes count=1 2>/dev/null | dd bs=1 skip="$_bytenum" count=1 2>/dev/null | od -x -A n)"
fi
# getting proper byte of edx on leaf$_leaf of cpuinfo in decimal
_reg_byte=$(dd if=/dev/cpu/0/cpuid bs=16 skip="$_leaf" iflag=skip_bytes count=1 2>/dev/null | dd bs=1 skip="$_bytenum" count=1 2>/dev/null | od -t u1 -A n | awk '{print $1}')
_debug "cpuid: leaf$_leaf byte $_bytenum: $_reg_byte (decimal)"
_reg_bit=$(( _reg_byte & _and_operand ))
_debug "cpuid: leaf$_leaf byte $_bytenum & $_and_operand = $_reg_bit"
[ "$_reg_bit" -eq 0 ] && return 1
# $_reg_bit is > 0, so the bit was found: return true (aka 0)
return 0
return 2
}
dmesg_grep()
@ -714,7 +798,7 @@ dmesg_grep()
# grep for something in dmesg, ensuring that the dmesg buffer
# has not been truncated
dmesg_grepped=''
if ! dmesg | grep -qE '(^|\] )Linux version [0-9]'; then
if ! dmesg | grep -qE -e '(^|\] )Linux version [0-9]' -e '^FreeBSD is a registered' ; then
# dmesg truncated
return 2
fi
@ -734,31 +818,36 @@ is_coreos()
parse_cpu_details()
{
[ "$parse_cpu_details_done" = 1 ] && return 0
cpu_vendor=$( grep '^vendor_id' /proc/cpuinfo | awk '{print $3}' | head -1)
cpu_friendly_name=$(grep '^model name' /proc/cpuinfo | cut -d: -f2- | head -1 | sed -e 's/^ *//')
# special case for ARM follows
if grep -qi 'CPU implementer[[:space:]]*:[[:space:]]*0x41' /proc/cpuinfo; then
cpu_vendor='ARM'
# some devices (phones or other) have several ARMs and as such different part numbers,
# an example is "bigLITTLE", so we need to store the whole list, this is needed for is_cpu_vulnerable
cpu_part_list=$(awk '/CPU part/ {print $4}' /proc/cpuinfo)
cpu_arch_list=$(awk '/CPU architecture/ {print $3}' /proc/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"
fi
cpu_family=$( grep '^cpu family' /proc/cpuinfo | awk '{print $4}' | grep -E '^[0-9]+$' | head -1)
cpu_model=$( grep '^model' /proc/cpuinfo | awk '{print $3}' | grep -E '^[0-9]+$' | head -1)
cpu_stepping=$(grep '^stepping' /proc/cpuinfo | awk '{print $3}' | grep -E '^[0-9]+$' | head -1)
cpu_ucode=$( grep '^microcode' /proc/cpuinfo | awk '{print $3}' | head -1)
echo "$cpu_ucode" | grep -q ^0x && cpu_ucode_decimal=$(( cpu_ucode ))
if [ -e "$procfs/cpuinfo" ]; then
cpu_vendor=$( grep '^vendor_id' "$procfs/cpuinfo" | awk '{print $3}' | head -1)
cpu_friendly_name=$(grep '^model name' "$procfs/cpuinfo" | cut -d: -f2- | head -1 | sed -e 's/^ *//')
# special case for ARM follows
if grep -qi 'CPU implementer[[:space:]]*:[[:space:]]*0x41' "$procfs/cpuinfo"; then
cpu_vendor='ARM'
# some devices (phones or other) have several ARMs and as such different part numbers,
# an example is "bigLITTLE", so we need to store the whole list, this is needed for is_cpu_vulnerable
cpu_part_list=$(awk '/CPU part/ {print $4}' "$procfs/cpuinfo")
cpu_arch_list=$(awk '/CPU architecture/ {print $3}' "$procfs/cpuinfo")
# take the first one to fill the friendly name, do NOT quote the vars below
# shellcheck disable=SC2086
cpu_arch=$(echo $cpu_arch_list | awk '{ print $1 }')
# shellcheck disable=SC2086
cpu_part=$(echo $cpu_part_list | awk '{ print $1 }')
[ "$cpu_arch" = "AArch64" ] && cpu_arch=8
cpu_friendly_name="ARM"
[ -n "$cpu_arch" ] && cpu_friendly_name="$cpu_friendly_name v$cpu_arch"
[ -n "$cpu_part" ] && cpu_friendly_name="$cpu_friendly_name model $cpu_part"
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)
echo "$cpu_ucode" | grep -q ^0x && cpu_ucode_decimal=$(( cpu_ucode ))
else
cpu_friendly_name=$(sysctl -n hw.model)
fi
# also define those that we will need in other funcs
# taken from ttps://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/include/asm/intel-family.h
@ -903,6 +992,16 @@ is_skylake_cpu()
return 1
}
# ENTRYPOINT
# we can't do anything useful under WSL
if uname -a | grep -qE -- '-Microsoft #[0-9]+-Microsoft '; then
_warn "This script doesn't work under Windows Subsystem for Linux"
_warn "You should use the official Microsoft tool instead."
_warn "It can be found under https://aka.ms/SpeculationControlPS"
exit 1
fi
# check for mode selection inconsistency
if [ "$opt_live_explicit" = 1 ]; then
if [ -n "$opt_kernel" ] || [ -n "$opt_config" ] || [ -n "$opt_map" ]; then
@ -932,6 +1031,27 @@ else
fi
fi
# if we're under a BSD, try to mount linprocfs for "$procfs/cpuinfo"
procfs=/proc
if echo "$os" | grep -q BSD; then
_debug "We're under BSD, check if we have procfs"
procfs=$(mount | awk '/^linprocfs/ { print $3; exit; }')
if [ -z "$procfs" ]; then
_debug "we don't, try to mount it"
procfs=/proc
[ -d /compat/linux/proc ] && procfs=/compat/linux/proc
test -d $procfs || mkdir $procfs
if mount -t linprocfs linprocfs $procfs 2>/dev/null; then
mounted_procfs=1
_debug "procfs just mounted at $procfs"
else
procfs=''
fi
else
_debug "We do: $procfs"
fi
fi
parse_cpu_details
if [ "$opt_live" = 1 ]; then
# root check (only for live mode, for offline mode, we already checked if we could read the files)
@ -1014,30 +1134,32 @@ else
bad_accuracy=1
fi
if [ -n "$opt_config" ] && ! grep -q '^CONFIG_' "$opt_config"; then
# given file is invalid!
_warn "The kernel config file seems invalid, was expecting a plain-text file, ignoring it!"
opt_config=''
fi
if [ "$os" = Linux ]; then
if [ -n "$opt_config" ] && ! grep -q '^CONFIG_' "$opt_config"; then
# given file is invalid!
_warn "The kernel config file seems invalid, was expecting a plain-text file, ignoring it!"
opt_config=''
fi
if [ -n "$dumped_config" ] && [ -n "$opt_config" ]; then
_verbose "Will use kconfig \033[35m/proc/config.gz (decompressed)\033[0m"
elif [ -n "$opt_config" ]; then
_verbose "Will use kconfig \033[35m$opt_config\033[0m"
else
_verbose "Will use no kconfig (accuracy might be reduced)"
bad_accuracy=1
fi
if [ -n "$dumped_config" ] && [ -n "$opt_config" ]; then
_verbose "Will use kconfig \033[35m/proc/config.gz (decompressed)\033[0m"
elif [ -n "$opt_config" ]; then
_verbose "Will use kconfig \033[35m$opt_config\033[0m"
else
_verbose "Will use no kconfig (accuracy might be reduced)"
bad_accuracy=1
fi
if [ -n "$opt_map" ]; then
_verbose "Will use System.map file \033[35m$opt_map\033[0m"
else
_verbose "Will use no System.map file (accuracy might be reduced)"
bad_accuracy=1
fi
if [ -n "$opt_map" ]; then
_verbose "Will use System.map file \033[35m$opt_map\033[0m"
else
_verbose "Will use no System.map file (accuracy might be reduced)"
bad_accuracy=1
fi
if [ "$bad_accuracy" = 1 ]; then
_info "We're missing some kernel info (see -v), accuracy might be reduced"
if [ "$bad_accuracy" = 1 ]; then
_info "We're missing some kernel info (see -v), accuracy might be reduced"
fi
fi
if [ -e "$opt_kernel" ]; then
@ -1065,13 +1187,16 @@ else
# try even harder with some kernels (such as ARM) that split the release (uname -r) and version (uname -v) in 2 adjacent strings
vmlinux_version=$("${opt_arch_prefix}strings" "$vmlinux" 2>/dev/null | grep -E -B1 '^#[0-9]+ .+ (19|20)[0-9][0-9]$' | tr "\n" " ")
fi
if [ -z "$vmlinux_version" ]; then
# FreeBSD?
vmlinux_version=$("${opt_arch_prefix}strings" "$vmlinux" 2>/dev/null | grep -E '^FreeBSD [0-9]' | head -1)
fi
if [ -n "$vmlinux_version" ]; then
# in live mode, check if the img we found is the correct one
if [ "$opt_live" = 1 ]; then
_verbose "Kernel image is \033[35m$vmlinux_version"
if ! echo "$vmlinux_version" | grep -qF "$(uname -r)" || \
! echo "$vmlinux_version" | grep -qF "$(uname -v)"; then
_warn "Possible disrepancy between your running kernel and the image we found ($opt_kernel), results might be incorrect"
if ! echo "$vmlinux_version" | grep -qF "$(uname -r)"; then
_warn "Possible disrepancy between your running kernel '$(uname -r)' and the image '$vmlinux_version' we found ($opt_kernel), results might be incorrect"
fi
else
_info "Kernel image is \033[35m$vmlinux_version"
@ -1115,24 +1240,29 @@ sys_interface_check()
number_of_cpus()
{
n=$(grep -c ^processor /proc/cpuinfo)
if echo "$os" | grep BSD; then
n=$(sysctl -n hw.ncpu 2>/dev/null || echo 1)
elif [ -e "$procfs/cpuinfo" ]; then
n=$(grep -c ^processor "$procfs/cpuinfo" 2>/dev/null || echo 1)
else
# if we don't know, default to 1 CPU
n=1
fi
return "$n"
}
# $1 - msr number
# $2 - cpu index
check_msr_enable()
write_msr()
{
dd if=/dev/cpu/"$2"/msr of=/dev/null bs=8 count=1 skip="$1" iflag=skip_bytes 2>/dev/null; ret=$?
return $ret
}
# $1 - msr number
# $2 - cpu index
# $3 - value
write_to_msr()
{
$echo_cmd -ne "$3" | dd of=/dev/cpu/"$2"/msr bs=8 count=1 seek="$1" oflag=seek_bytes 2>/dev/null; ret=$?
if [ "$os" != Linux ]; then
cpucontrol -m "$1=0" "/dev/cpuctl$2" >/dev/null 2>&1; ret=$?
else
# convert to decimal
_msrindex=$(( $1 ))
_echo_nol -9 "\0\0\0\0\0\0\0\0" | dd of=/dev/cpu/"$2"/msr bs=8 count=1 seek="$_msrindex" oflag=seek_bytes 2>/dev/null; ret=$?
fi
_debug "write_msr: for cpu $2 on msr $1 ($_msrindex), ret=$ret"
return $ret
}
@ -1140,8 +1270,26 @@ write_to_msr()
# $2 - cpu index
read_msr()
{
msr=$(dd if=/dev/cpu/"$2"/msr bs=8 count=1 skip="$1" iflag=skip_bytes 2>/dev/null | od -t u1 -A n | awk '{print $8}');
return "$msr"
read_msr_value=''
if [ "$os" != Linux ]; then
_msr=$(cpucontrol -m "$1" "/dev/cpuctl$2" 2>/dev/null); ret=$?
[ $ret -ne 0 ] && return 1
# MSR 0x10: 0x000003e1 0xb106dded
_msr_h=$(echo "$_msr" | awk '{print $3}');
_msr_h="$(( _msr_h >> 24 & 0xFF )) $(( _msr_h >> 16 & 0xFF )) $(( _msr_h >> 8 & 0xFF )) $(( _msr_h & 0xFF ))"
_msr_l=$(echo "$_msr" | awk '{print $4}');
_msr_l="$(( _msr_l >> 24 & 0xFF )) $(( _msr_l >> 16 & 0xFF )) $(( _msr_l >> 8 & 0xFF )) $(( _msr_l & 0xFF ))"
read_msr_value="$_msr_h $_msr_l"
else
# convert to decimal
_msrindex=$(( $1 ))
if ! dd if=/dev/cpu/"$2"/msr bs=8 count=1 skip="$_msrindex" iflag=skip_bytes 2>/dev/null; then
return 1
fi
read_msr_value=$(dd if=/dev/cpu/"$2"/msr bs=8 count=1 skip="$_msrindex" iflag=skip_bytes 2>/dev/null | od -t u1 -A n)
fi
_debug "read_msr: MSR=$1 value is $read_msr_value"
return 0
}
@ -1159,13 +1307,13 @@ check_cpu()
number_of_cpus
ncpus=$?
idx_max_cpu=$((ncpus-1))
if [ ! -e /dev/cpu/0/msr ]; then
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 ]; then
if [ ! -e /dev/cpu/0/msr ] && [ ! -e /dev/cpuctl0 ]; then
spec_ctrl_msr=-1
pstatus yellow UNKNOWN "couldn't read /dev/cpu/0/msr, is msr support enabled in your kernel?"
pstatus yellow UNKNOWN "couldn't read MSR, is MSR support enabled in your kernel?"
else
# the new MSR 'SPEC_CTRL' is at offset 0x48
# here we use dd, it's the same as using 'rdmsr 0x48' but without needing the rdmsr tool
@ -1175,8 +1323,7 @@ check_cpu()
cpu_mismatch=0
for i in $(seq 0 "$idx_max_cpu")
do
check_msr_enable 72 "$i"
ret=$?
read_msr 0x48 "$i"; ret=$?
if [ "$i" -eq 0 ]; then
val=$ret
else
@ -1203,7 +1350,7 @@ check_cpu()
_info_nol " * CPU indicates IBRS capability: "
# from kernel src: { X86_FEATURE_SPEC_CTRL, CPUID_EDX,26, 0x00000007, 0 },
read_cpuid 7 15 4; ret=$?
read_cpuid 0x7 15 4; ret=$?
if [ $ret -eq 0 ]; then
pstatus green YES "SPEC_CTRL feature bit"
cpuid_spec_ctrl=1
@ -1220,7 +1367,7 @@ check_cpu()
# but let's check it anyway (in verbose mode only)
_verbose_nol " * Kernel has set the spec_ctrl flag in cpuinfo: "
if [ "$opt_live" = 1 ]; then
if grep ^flags /proc/cpuinfo | grep -qw spec_ctrl; then
if grep ^flags "$procfs/cpuinfo" | grep -qw spec_ctrl; then
pstatus green YES
else
pstatus blue NO
@ -1233,7 +1380,7 @@ check_cpu()
# IBPB
_info " * Indirect Branch Prediction Barrier (IBPB)"
_info_nol " * PRED_CMD MSR is available: "
if [ ! -e /dev/cpu/0/msr ]; then
if [ ! -e /dev/cpu/0/msr ] && [ ! -e /dev/cpuctl0 ]; then
pstatus yellow UNKNOWN "couldn't read /dev/cpu/0/msr, is msr support enabled in your kernel?"
else
# the new MSR 'PRED_CTRL' is at offset 0x49, write-only
@ -1243,8 +1390,7 @@ check_cpu()
cpu_mismatch=0
for i in $(seq 0 "$idx_max_cpu")
do
write_to_msr 73 "$i" "\0\0\0\0\0\0\0\0"
ret=$?
write_msr 0x49 "$i"; ret=$?
if [ "$i" -eq 0 ]; then
val=$ret
else
@ -1269,7 +1415,7 @@ check_cpu()
_info_nol " * CPU indicates IBPB capability: "
# CPUID EAX=0x80000008, ECX=0x00 return EBX[12] indicates support for just IBPB.
read_cpuid 2147483656 5 16; ret=$?
read_cpuid 0x80000008 5 16; ret=$?
if [ $ret -eq 0 ]; then
pstatus green YES "IBPB_SUPPORT feature bit"
elif [ "$cpuid_spec_ctrl" = 1 ]; then
@ -1293,7 +1439,7 @@ check_cpu()
_info_nol " * CPU indicates STIBP capability: "
# A processor supports STIBP if it enumerates CPUID (EAX=7H,ECX=0):EDX[27] as 1
read_cpuid 7 15 8; ret=$?
read_cpuid 0x7 15 8; ret=$?
if [ $ret -eq 0 ]; then
pstatus green YES
elif [ $ret -eq 2 ]; then
@ -1306,7 +1452,7 @@ check_cpu()
_info_nol " * CPU indicates ARCH_CAPABILITIES MSR availability: "
cpuid_arch_capabilities=-1
# A processor supports STIBP if it enumerates CPUID (EAX=7H,ECX=0):EDX[27] as 1
read_cpuid 7 15 32; ret=$?
read_cpuid 0x7 15 32; ret=$?
if [ $ret -eq 0 ]; then
pstatus green YES
cpuid_arch_capabilities=1
@ -1326,7 +1472,7 @@ check_cpu()
capabilities_rdcl_no=0
capabilities_ibrs_all=0
pstatus red NO
elif [ ! -e /dev/cpu/0/msr ]; then
elif [ ! -e /dev/cpu/0/msr ] && [ ! -e /dev/cpuctl0 ]; then
spec_ctrl_msr=-1
pstatus yellow UNKNOWN "couldn't read /dev/cpu/0/msr, is msr support enabled in your kernel?"
else
@ -1338,10 +1484,8 @@ check_cpu()
cpu_mismatch=0
for i in $(seq 0 "$idx_max_cpu")
do
check_msr_enable 266 "$i"
ret=$?
read_msr 266 "$i"
capabilities=$?
read_msr 0x10a "$i"; ret=$?
capabilities=$(echo "$read_msr_value" | awk '{print $8}')
if [ "$i" -eq 0 ]; then
val=$ret
val_cap_msr=$capabilities
@ -1445,7 +1589,17 @@ check_redhat_canonical_spectre()
check_variant1()
{
_info "\033[1;34mCVE-2017-5753 [bounds check bypass] aka 'Spectre Variant 1'\033[0m"
if [ "$os" = Linux ]; then
check_variant1_linux
elif echo "$os" | grep -q BSD; then
check_variant1_bsd
else
_warn "Unsupported OS ($os)"
fi
}
check_variant1_linux()
{
status=UNK
sys_interface_available=0
msg=''
@ -1566,12 +1720,34 @@ check_variant1()
fi
}
check_variant1_bsd()
{
cve='CVE-2017-5753'
if ! is_cpu_vulnerable 1; then
# override status & msg in case CPU is not vulnerable after all
pvulnstatus $cve OK "your CPU vendor reported your CPU model as not vulnerable"
else
pvulnstatus $cve VULN "no mitigation for BSD yet"
fi
}
###################
# SPECTRE VARIANT 2
check_variant2()
{
_info "\033[1;34mCVE-2017-5715 [branch target injection] aka 'Spectre Variant 2'\033[0m"
if [ "$os" = Linux ]; then
check_variant2_linux
elif echo "$os" | grep -q BSD; then
check_variant2_bsd
else
_warn "Unsupported OS ($os)"
fi
}
check_variant2_linux()
{
status=UNK
sys_interface_available=0
msg=''
@ -1613,13 +1789,13 @@ check_variant2()
_debug "ibrs: $dir/ibrs_enabled file doesn't exist"
fi
done
# on some newer kernels, the spec_ctrl_ibrs flag in /proc/cpuinfo
# on some newer kernels, the spec_ctrl_ibrs flag in "$procfs/cpuinfo"
# is set when ibrs has been administratively enabled (usually from cmdline)
# which in that case means ibrs is supported *and* enabled for kernel & user
# as per the ibrs patch series v3
if [ "$ibrs_supported" = 0 ]; then
if grep ^flags /proc/cpuinfo | grep -qw spec_ctrl_ibrs; then
_debug "ibrs: found spec_ctrl_ibrs flag in /proc/cpuinfo"
if grep ^flags "$procfs/cpuinfo" | grep -qw spec_ctrl_ibrs; then
_debug "ibrs: found spec_ctrl_ibrs flag in $procfs/cpuinfo"
ibrs_supported=1
# enabled=2 -> kernel & user
ibrs_enabled=2
@ -1851,12 +2027,55 @@ check_variant2()
fi
}
check_variant2_bsd()
{
_info_nol "* Kernel supports IBRS: "
ibrs_disabled=$(sysctl -n hw.ibrs_disable 2>/dev/null)
if [ -z "$ibrs_disabled" ]; then
pstatus red NO
else
pstatus green YES
fi
_info_nol "* IBRS enabled and active: "
ibrs_active=$(sysctl -n hw.ibrs_active 2>/dev/null)
if [ "$ibrs_active" = 1 ]; then
pstatus green YES
else
pstatus red NO
fi
cve='CVE-2017-5715'
if ! is_cpu_vulnerable 2; then
# override status & msg in case CPU is not vulnerable after all
pvulnstatus $cve OK "your CPU vendor reported your CPU model as not vulnerable"
elif [ "$ibrs_active" = 1 ]; then
pvulnstatus $cve OK "IBRS mitigates the vulnerability"
elif [ "$ibrs_disabled" = 0 ]; then
pvulnstatus $cve VULN "IBRS is supported by your kernel but your CPU microcode lacks support"
elif [ "$ibrs_disabled" = 1 ]; then
pvulnstatus $cve VULN "IBRS is supported but administratively disabled on your system"
else
pvulnstatus $cve VULN "IBRS is needed to mitigate the vulnerability but your kernel is missing support"
fi
}
########################
# MELTDOWN aka VARIANT 3
check_variant3()
{
_info "\033[1;34mCVE-2017-5754 [rogue data cache load] aka 'Meltdown' aka 'Variant 3'\033[0m"
if [ "$os" = Linux ]; then
check_variant3_linux
elif echo "$os" | grep -q BSD; then
check_variant3_bsd
else
_warn "Unsupported OS ($os)"
fi
}
check_variant3_linux()
{
status=UNK
sys_interface_available=0
msg=''
@ -1913,13 +2132,13 @@ check_variant3()
dmesg_grep="Kernel/User page tables isolation: enabled"
dmesg_grep="$dmesg_grep|Kernel page table isolation enabled"
dmesg_grep="$dmesg_grep|x86/pti: Unmapping kernel while in userspace"
if grep ^flags /proc/cpuinfo | grep -qw pti; then
if grep ^flags "$procfs/cpuinfo" | grep -qw pti; then
# vanilla PTI patch sets the 'pti' flag in cpuinfo
_debug "kpti_enabled: found 'pti' flag in /proc/cpuinfo"
_debug "kpti_enabled: found 'pti' flag in $procfs/cpuinfo"
kpti_enabled=1
elif grep ^flags /proc/cpuinfo | grep -qw kaiser; then
elif grep ^flags "$procfs/cpuinfo" | grep -qw kaiser; then
# kernel line 4.9 sets the 'kaiser' flag in cpuinfo
_debug "kpti_enabled: found 'kaiser' flag in /proc/cpuinfo"
_debug "kpti_enabled: found 'kaiser' flag in $procfs/cpuinfo"
kpti_enabled=1
elif [ -e /sys/kernel/debug/x86/pti_enabled ]; then
# Red Hat Backport creates a dedicated file, see https://access.redhat.com/articles/3311301
@ -1960,13 +2179,13 @@ check_variant3()
if [ "$opt_verbose" -ge 2 ]; then
_info "* Performance impact if PTI is enabled"
_info_nol " * CPU supports PCID: "
if grep ^flags /proc/cpuinfo | grep -qw pcid; then
if grep ^flags "$procfs/cpuinfo" | grep -qw pcid; then
pstatus green YES 'performance degradation with PTI will be limited'
else
pstatus blue NO 'no security impact but performance will be degraded with PTI'
fi
_info_nol " * CPU supports INVPCID: "
if grep ^flags /proc/cpuinfo | grep -qw invpcid; then
if grep ^flags "$procfs/cpuinfo" | grep -qw invpcid; then
pstatus green YES 'performance degradation with PTI will be limited'
else
pstatus blue NO 'no security impact but performance will be degraded with PTI'
@ -2058,6 +2277,36 @@ check_variant3()
fi
}
check_variant3_bsd()
{
_info_nol "* Kernel supports Page Table Isolation (PTI): "
kpti_enabled=$(sysctl -n vm.pmap.pti 2>/dev/null)
if [ -z "$kpti_enabled" ]; then
pstatus red NO
else
pstatus green YES
fi
_info_nol "* PTI enabled and active: "
if [ "$kpti_enabled" = 1 ]; then
pstatus green YES
else
pstatus red NO
fi
cve='CVE-2017-5754'
if ! is_cpu_vulnerable 3; then
# override status & msg in case CPU is not vulnerable after all
pvulnstatus $cve OK "your CPU vendor reported your CPU model as not vulnerable"
elif [ "$kpti_enabled" = 1 ]; then
pvulnstatus $cve OK "PTI mitigates the vulnerability"
elif [ -n "$kpti_enabled" ]; then
pvulnstatus $cve VULN "PTI is supported but disabled on your system"
else
pvulnstatus $cve VULN "PTI is needed to mitigate the vulnerability"
fi
}
check_cpu
check_cpu_vulnerabilities
_info