9 Commits
v0.38 ... v0.39

View File

@ -9,7 +9,7 @@
#
# Stephane Lesimple
#
VERSION='0.38'
VERSION='0.39'
trap 'exit_cleanup' EXIT
trap '_warn "interrupted, cleaning up..."; exit_cleanup; exit 1' INT
@ -150,12 +150,12 @@ if which printf >/dev/null 2>&1; then
elif which echo >/dev/null 2>&1; then
echo_cmd=$(which echo)
else
# which command is broken?
# 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
# still empty? fallback to builtin
[ -z "$echo_cmd" ] && echo_cmd=echo
__echo()
{
@ -245,15 +245,13 @@ is_cpu_vulnerable_cached=0
_is_cpu_vulnerable_cached()
{
# shellcheck disable=SC2086
{
[ "$1" = 1 ] && return $variant1
# shellcheck disable=SC2086
[ "$1" = 2 ] && return $variant2
# shellcheck disable=SC2086
[ "$1" = 3 ] && return $variant3
# shellcheck disable=SC2086
[ "$1" = 3a ] && return $variant3a
# shellcheck disable=SC2086
[ "$1" = 4 ] && return $variant4
}
echo "$0: error: invalid variant '$1' passed to is_cpu_vulnerable()" >&2
exit 255
}
@ -889,9 +887,7 @@ load_cpuid()
}
# shellcheck disable=SC2034
{
EAX=1; EBX=2; ECX=3; EDX=4;
}
read_cpuid()
{
# leaf is the value of the eax register when calling the cpuid instruction:
@ -1210,6 +1206,17 @@ is_skylake_cpu()
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, ...)
@ -1247,6 +1254,8 @@ if [ "$opt_hw_only" = 1 ]; then
opt_variant1=0
opt_variant2=0
opt_variant3=0
opt_variant3a=0
opt_variant4=0
fi
fi
@ -1309,6 +1318,9 @@ if [ "$opt_live" = 1 ]; then
if [ -r /proc/cmdline ] && grep -q 'BOOT_IMAGE=' /proc/cmdline; then
opt_kernel=$(grep -Eo 'BOOT_IMAGE=[^ ]+' /proc/cmdline | cut -d= -f2)
_debug "found opt_kernel=$opt_kernel in /proc/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"
@ -1327,6 +1339,8 @@ if [ "$opt_live" = 1 ]; then
[ -e "/boot/vmlinuz-linux" ] && opt_kernel="/boot/vmlinuz-linux"
# Arch aarch64:
[ -e "/boot/Image" ] && opt_kernel="/boot/Image"
# Arch armv5/armv7:
[ -e "/boot/zImage" ] && opt_kernel="/boot/zImage"
# Linux-Libre:
[ -e "/boot/vmlinuz-linux-libre" ] && opt_kernel="/boot/vmlinuz-linux-libre"
# pine64
@ -1494,18 +1508,39 @@ number_of_cpus()
# $2 - cpu index
write_msr()
{
# _msr must be in hex, in the form 0x1234:
_msr="$1"
# cpu index, starting from 0:
_cpu="$2"
if [ "$os" != Linux ]; then
cpucontrol -m "$1=0" "/dev/cpuctl$2" >/dev/null 2>&1; ret=$?
cpucontrol -m "$_msr=0" "/dev/cpuctl$_cpu" >/dev/null 2>&1; ret=$?
else
# for Linux
# convert to decimal
_msrindex=$(( $1 ))
if [ ! -w /dev/cpu/"$2"/msr ]; then
_msr=$(( _msr ))
if [ ! -w /dev/cpu/"$_cpu"/msr ]; then
ret=200 # permission error
# if wrmsr is available, use it
elif which wrmsr >/dev/null 2>&1 && [ "$SMC_NO_WRMSR" != 1 ]; then
_debug "write_msr: using wrmsr"
wrmsr $_msr 0 2>/dev/null; ret=$?
# or if we have perl, use it, any 5.x version will work
elif which perl >/dev/null 2>&1 && [ "$SMC_NO_PERL" != 1 ]; then
_debug "write_msr: using perl"
ret=1
perl -e "open(M,'>','/dev/cpu/$_cpu/msr') and seek(M,$_msr,0) and exit(syswrite(M,pack('H16',0)))"; [ $? -eq 8 ] && ret=0
# fallback to dd if it supports seek_bytes
elif dd if=/dev/null of=/dev/null bs=8 count=1 seek="$_msr" oflag=seek_bytes 2>/dev/null; then
_debug "write_msr: using dd"
dd if=/dev/zero of=/dev/cpu/"$_cpu"/msr bs=8 count=1 seek="$_msr" oflag=seek_bytes 2>/dev/null; ret=$?
else
dd if=/dev/zero of=/dev/cpu/"$2"/msr bs=8 count=1 seek="$_msrindex" oflag=seek_bytes 2>/dev/null; ret=$?
_debug "write_msr: got no wrmsr, perl or recent enough dd!"
return 201 # missing tool error
fi
fi
_debug "write_msr: for cpu $2 on msr $1 ($_msrindex), ret=$ret"
# normalize ret
[ "$ret" != 0 ] && ret=1
_debug "write_msr: for cpu $_cpu on msr $_msr, ret=$ret"
return $ret
}
@ -1526,12 +1561,27 @@ read_msr()
_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
# for Linux
# convert to decimal
_msr=$(( _msr ))
if [ ! -r /dev/cpu/"$_cpu"/msr ]; then
return 200 # permission error
# if rdmsr is available, use it
elif which rdmsr >/dev/null 2>&1 && [ "$SMC_NO_RDMSR" != 1 ]; then
_debug "read_msr: using rdmsr"
read_msr_value=$(rdmsr -r $_msr 2>/dev/null | od -t u8 -A n)
# or if we have perl, use it, any 5.x version will work
elif which perl >/dev/null 2>&1 && [ "$SMC_NO_PERL" != 1 ]; then
_debug "read_msr: using perl"
read_msr_value=$(perl -e "open(M,'<','/dev/cpu/$_cpu/msr') and seek(M,$_msr,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" iflag=skip_bytes 2>/dev/null; then
_debug "read_msr: using dd"
read_msr_value=$(dd if=/dev/cpu/"$_cpu"/msr bs=8 count=1 skip="$_msr" iflag=skip_bytes 2>/dev/null | od -t u8 -A n)
else
_debug "read_msr: got no rdmsr, perl or recent enough dd!"
return 201 # missing tool error
fi
read_msr_value=$(dd if=/dev/cpu/"$_cpu"/msr bs=8 count=1 skip="$_msr" iflag=skip_bytes 2>/dev/null | od -t u1 -A n)
if [ -z "$read_msr_value" ]; then
# MSR doesn't exist, don't check for $? because some versions of dd still return 0!
return 1
@ -1564,9 +1614,7 @@ check_cpu()
pstatus yellow UNKNOWN "is msr kernel module available?"
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
# if we get a read error, the MSR is not there. bs has to be 8 for msr
# skip=9 because 8*9=72=0x48
# we check if we have it for all cpus
val=0
cpu_mismatch=0
for i in $(seq 0 "$idx_max_cpu")
@ -1593,6 +1641,9 @@ check_cpu()
elif [ $val -eq 200 ]; then
pstatus yellow UNKNOWN "is msr kernel module available?"
spec_ctrl_msr=-1
elif [ $val -eq 201 ]; then
pstatus yellow UNKNOWN "missing tool, install either msr-tools or perl"
spec_ctrl_msr=-1
else
spec_ctrl_msr=0
pstatus yellow NO
@ -1654,8 +1705,7 @@ check_cpu()
pstatus yellow UNKNOWN "is msr kernel module available?"
else
# the new MSR 'PRED_CTRL' is at offset 0x49, write-only
# here we use dd, it's the same as using 'wrmsr 0x49 0' but without needing the wrmsr tool
# if we get a write error, the MSR is not there
# we test if of all cpus
val=0
cpu_mismatch=0
for i in $(seq 0 "$idx_max_cpu")
@ -1816,27 +1866,28 @@ check_cpu()
capabilities_rdcl_no=-1
capabilities_ibrs_all=-1
capabilities_ssb_no=-1
capabilities_rsba=-1
if [ "$cpuid_arch_capabilities" = -1 ]; then
pstatus yellow UNKNOWN
elif [ "$cpuid_arch_capabilities" != 1 ]; then
capabilities_rdcl_no=0
capabilities_ibrs_all=0
capabilities_ssb_no=0
capabilities_rsba=0
pstatus yellow NO
elif [ ! -e /dev/cpu/0/msr ] && [ ! -e /dev/cpuctl0 ]; then
spec_ctrl_msr=-1
pstatus yellow UNKNOWN "is msr kernel module available?"
else
# the new MSR 'ARCH_CAPABILITIES' is at offset 0x10a
# here we use dd, it's the same as using 'rdmsr 0x10a' but without needing the rdmsr tool
# if we get a read error, the MSR is not there. bs has to be 8 for msr
# we check if we have it for all cpus
val=0
val_cap_msr=0
cpu_mismatch=0
for i in $(seq 0 "$idx_max_cpu")
do
read_msr 0x10a "$i"; ret=$?
capabilities=$(echo "$read_msr_value" | awk '{print $8}')
capabilities=$read_msr_value
if [ "$i" -eq 0 ]; then
val=$ret
val_cap_msr=$capabilities
@ -1852,12 +1903,14 @@ check_cpu()
capabilities_rdcl_no=0
capabilities_ibrs_all=0
capabilities_ssb_no=0
capabilities_rsba=0
if [ $val -eq 0 ]; then
_debug "capabilities MSR lower byte is $capabilities (decimal)"
[ $(( capabilities & 1 )) -eq 1 ] && capabilities_rdcl_no=1
[ $(( capabilities & 2 )) -eq 2 ] && capabilities_ibrs_all=1
[ $(( capabilities & 16 )) -eq 16 ] && capabilities_ssb_no=1
_debug "capabilities says rdcl_no=$capabilities_rdcl_no ibrs_all=$capabilities_ibrs_all ssb_no=$capabilities_ssb_no"
_debug "capabilities MSR is $capabilities (decimal)"
[ $(( capabilities >> 0 & 1 )) -eq 1 ] && capabilities_rdcl_no=1
[ $(( capabilities >> 1 & 1 )) -eq 1 ] && capabilities_ibrs_all=1
[ $(( capabilities >> 2 & 1 )) -eq 1 ] && capabilities_rsba=1
[ $(( capabilities >> 4 & 1 )) -eq 1 ] && capabilities_ssb_no=1
_debug "capabilities says rdcl_no=$capabilities_rdcl_no ibrs_all=$capabilities_ibrs_all ssb_no=$capabilities_ssb_no rsba=$capabilities_rsba"
if [ "$capabilities_ibrs_all" = 1 ]; then
if [ $cpu_mismatch -eq 0 ]; then
pstatus green YES
@ -1869,6 +1922,8 @@ check_cpu()
fi
elif [ $val -eq 200 ]; then
pstatus yellow UNKNOWN "is msr kernel module available?"
elif [ $val -eq 201 ]; then
pstatus yellow UNKNOWN "missing tool, install either msr-tools or perl"
else
pstatus yellow NO
fi
@ -1893,6 +1948,15 @@ check_cpu()
pstatus yellow NO
fi
_info_nol " * Hypervisor indicates host CPU might be vulnerable to RSB underflow (RSBA): "
if [ "$capabilities_rsba" = -1 ]; then
pstatus yellow UNKNOWN
elif [ "$capabilities_rsba" = 1 ]; then
pstatus yellow YES
else
pstatus blue NO
fi
_info_nol " * CPU microcode is known to cause stability problems: "
if is_ucode_blacklisted; then
pstatus red YES "$ucode_found"
@ -2514,7 +2578,7 @@ check_variant2_linux()
fi
fi
if is_skylake_cpu || [ "$opt_verbose" -ge 2 ]; then
if is_vulnerable_to_empty_rsb || [ "$opt_verbose" -ge 2 ]; then
_info_nol " * Kernel supports RSB filling: "
if ! which "${opt_arch_prefix}strings" >/dev/null 2>&1; then
pstatus yellow UNKNOWN "missing '${opt_arch_prefix}strings' tool, please install it, usually it's in the binutils package"
@ -2541,9 +2605,9 @@ check_variant2_linux()
# 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
if [ "$retpoline" = 1 ] && [ "$retpoline_compiler" = 1 ] && [ "$retp_enabled" != 0 ] && [ -n "$ibpb_enabled" ] && [ "$ibpb_enabled" -ge 1 ] && ( ! is_skylake_cpu || [ -n "$rsb_filling" ] ); then
if [ "$retpoline" = 1 ] && [ "$retpoline_compiler" = 1 ] && [ "$retp_enabled" != 0 ] && [ -n "$ibpb_enabled" ] && [ "$ibpb_enabled" -ge 1 ] && ( ! is_vulnerable_to_empty_rsb || [ -n "$rsb_filling" ] ); then
pvulnstatus $cve OK "Full retpoline + IBPB are mitigating the vulnerability"
elif [ "$retpoline" = 1 ] && [ "$retpoline_compiler" = 1 ] && [ "$retp_enabled" != 0 ] && [ "$opt_paranoid" = 0 ] && ( ! is_skylake_cpu || [ -n "$rsb_filling" ] ); then
elif [ "$retpoline" = 1 ] && [ "$retpoline_compiler" = 1 ] && [ "$retp_enabled" != 0 ] && [ "$opt_paranoid" = 0 ] && ( ! is_vulnerable_to_empty_rsb || [ -n "$rsb_filling" ] ); then
pvulnstatus $cve OK "Full retpoline is mitigating the vulnerability"
if [ -n "$cpuid_ibpb" ]; then
_warn "You should enable IBPB to complete retpoline as a Variant 2 mitigation"
@ -2573,7 +2637,7 @@ check_variant2_linux()
# if we arrive here and didn't already call pvulnstatus, then it's VULN, let's explain why
if [ "$pvulnstatus_last_cve" != "$cve" ]; then
# explain what's needed for this CPU
if is_skylake_cpu; then
if is_vulnerable_to_empty_rsb; then
pvulnstatus $cve VULN "IBRS+IBPB or retpoline+IBPB+RBS filling, is needed to mitigate the vulnerability"
explain "To mitigate this vulnerability, you need either IBRS + IBPB, both requiring hardware support from your CPU microcode in addition to kernel support, or a kernel compiled with retpoline and IBPB, with retpoline requiring a retpoline-aware compiler (re-run this script with -v to know if your version of gcc is retpoline-aware) and IBPB requiring hardware support from your CPU microcode. You also need a recent-enough kernel that supports RSB filling if you plan to use retpoline. For Skylake+ CPUs, the IBRS + IBPB approach is generally preferred as it guarantees complete protection, and the performance impact is not as high as with older CPUs in comparison with retpoline. More information about how to enable the missing bits for those two possible mitigations on your system follow. You only need to take one of the two approaches."
elif is_zen_cpu; then