9 Commits
v0.38 ... v0.39

View File

@ -9,7 +9,7 @@
# #
# Stephane Lesimple # Stephane Lesimple
# #
VERSION='0.38' VERSION='0.39'
trap 'exit_cleanup' EXIT trap 'exit_cleanup' EXIT
trap '_warn "interrupted, cleaning up..."; exit_cleanup; exit 1' INT 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 elif which echo >/dev/null 2>&1; then
echo_cmd=$(which echo) echo_cmd=$(which echo)
else else
# which command is broken? # maybe the `which` command is broken?
[ -x /bin/echo ] && echo_cmd=/bin/echo [ -x /bin/echo ] && echo_cmd=/bin/echo
# for Android # for Android
[ -x /system/bin/echo ] && echo_cmd=/system/bin/echo [ -x /system/bin/echo ] && echo_cmd=/system/bin/echo
fi fi
# still empty ? fallback to builtin # still empty? fallback to builtin
[ -z "$echo_cmd" ] && echo_cmd=echo [ -z "$echo_cmd" ] && echo_cmd=echo
__echo() __echo()
{ {
@ -245,15 +245,13 @@ is_cpu_vulnerable_cached=0
_is_cpu_vulnerable_cached() _is_cpu_vulnerable_cached()
{ {
# shellcheck disable=SC2086 # shellcheck disable=SC2086
[ "$1" = 1 ] && return $variant1 {
# shellcheck disable=SC2086 [ "$1" = 1 ] && return $variant1
[ "$1" = 2 ] && return $variant2 [ "$1" = 2 ] && return $variant2
# shellcheck disable=SC2086 [ "$1" = 3 ] && return $variant3
[ "$1" = 3 ] && return $variant3 [ "$1" = 3a ] && return $variant3a
# shellcheck disable=SC2086 [ "$1" = 4 ] && return $variant4
[ "$1" = 3a ] && return $variant3a }
# shellcheck disable=SC2086
[ "$1" = 4 ] && return $variant4
echo "$0: error: invalid variant '$1' passed to is_cpu_vulnerable()" >&2 echo "$0: error: invalid variant '$1' passed to is_cpu_vulnerable()" >&2
exit 255 exit 255
} }
@ -889,9 +887,7 @@ load_cpuid()
} }
# shellcheck disable=SC2034 # shellcheck disable=SC2034
{
EAX=1; EBX=2; ECX=3; EDX=4; EAX=1; EBX=2; ECX=3; EDX=4;
}
read_cpuid() read_cpuid()
{ {
# leaf is the value of the eax register when calling the cpuid instruction: # leaf is the value of the eax register when calling the cpuid instruction:
@ -1210,6 +1206,17 @@ is_skylake_cpu()
return 1 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_zen_cpu()
{ {
# is this CPU from the AMD ZEN family ? (ryzen, epyc, ...) # is this CPU from the AMD ZEN family ? (ryzen, epyc, ...)
@ -1247,6 +1254,8 @@ if [ "$opt_hw_only" = 1 ]; then
opt_variant1=0 opt_variant1=0
opt_variant2=0 opt_variant2=0
opt_variant3=0 opt_variant3=0
opt_variant3a=0
opt_variant4=0
fi fi
fi fi
@ -1309,6 +1318,9 @@ if [ "$opt_live" = 1 ]; then
if [ -r /proc/cmdline ] && grep -q 'BOOT_IMAGE=' /proc/cmdline; then if [ -r /proc/cmdline ] && grep -q 'BOOT_IMAGE=' /proc/cmdline; then
opt_kernel=$(grep -Eo 'BOOT_IMAGE=[^ ]+' /proc/cmdline | cut -d= -f2) opt_kernel=$(grep -Eo 'BOOT_IMAGE=[^ ]+' /proc/cmdline | cut -d= -f2)
_debug "found opt_kernel=$opt_kernel in /proc/cmdline" _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 / # 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 # so try to prepend /boot and see if we find anything
[ -e "/boot/$opt_kernel" ] && opt_kernel="/boot/$opt_kernel" [ -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" [ -e "/boot/vmlinuz-linux" ] && opt_kernel="/boot/vmlinuz-linux"
# Arch aarch64: # Arch aarch64:
[ -e "/boot/Image" ] && opt_kernel="/boot/Image" [ -e "/boot/Image" ] && opt_kernel="/boot/Image"
# Arch armv5/armv7:
[ -e "/boot/zImage" ] && opt_kernel="/boot/zImage"
# Linux-Libre: # Linux-Libre:
[ -e "/boot/vmlinuz-linux-libre" ] && opt_kernel="/boot/vmlinuz-linux-libre" [ -e "/boot/vmlinuz-linux-libre" ] && opt_kernel="/boot/vmlinuz-linux-libre"
# pine64 # pine64
@ -1494,18 +1508,39 @@ number_of_cpus()
# $2 - cpu index # $2 - cpu index
write_msr() write_msr()
{ {
# _msr must be in hex, in the form 0x1234:
_msr="$1"
# cpu index, starting from 0:
_cpu="$2"
if [ "$os" != Linux ]; then 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 else
# for Linux
# convert to decimal # convert to decimal
_msrindex=$(( $1 )) _msr=$(( _msr ))
if [ ! -w /dev/cpu/"$2"/msr ]; then if [ ! -w /dev/cpu/"$_cpu"/msr ]; then
ret=200 # permission error 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 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
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 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 ))" _msr_l="$(( _msr_l >> 24 & 0xFF )) $(( _msr_l >> 16 & 0xFF )) $(( _msr_l >> 8 & 0xFF )) $(( _msr_l & 0xFF ))"
read_msr_value="$_msr_h $_msr_l" read_msr_value="$_msr_h $_msr_l"
else else
# for Linux
# convert to decimal # convert to decimal
_msr=$(( _msr )) _msr=$(( _msr ))
if [ ! -r /dev/cpu/"$_cpu"/msr ]; then if [ ! -r /dev/cpu/"$_cpu"/msr ]; then
return 200 # permission error 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 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 if [ -z "$read_msr_value" ]; then
# MSR doesn't exist, don't check for $? because some versions of dd still return 0! # MSR doesn't exist, don't check for $? because some versions of dd still return 0!
return 1 return 1
@ -1564,9 +1614,7 @@ check_cpu()
pstatus yellow UNKNOWN "is msr kernel module available?" pstatus yellow UNKNOWN "is msr kernel module available?"
else else
# the new MSR 'SPEC_CTRL' is at offset 0x48 # 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 # we check if we have it for all cpus
# 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
val=0 val=0
cpu_mismatch=0 cpu_mismatch=0
for i in $(seq 0 "$idx_max_cpu") for i in $(seq 0 "$idx_max_cpu")
@ -1593,6 +1641,9 @@ check_cpu()
elif [ $val -eq 200 ]; then elif [ $val -eq 200 ]; then
pstatus yellow UNKNOWN "is msr kernel module available?" pstatus yellow UNKNOWN "is msr kernel module available?"
spec_ctrl_msr=-1 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 else
spec_ctrl_msr=0 spec_ctrl_msr=0
pstatus yellow NO pstatus yellow NO
@ -1654,8 +1705,7 @@ check_cpu()
pstatus yellow UNKNOWN "is msr kernel module available?" pstatus yellow UNKNOWN "is msr kernel module available?"
else else
# the new MSR 'PRED_CTRL' is at offset 0x49, write-only # 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 # we test if of all cpus
# if we get a write error, the MSR is not there
val=0 val=0
cpu_mismatch=0 cpu_mismatch=0
for i in $(seq 0 "$idx_max_cpu") for i in $(seq 0 "$idx_max_cpu")
@ -1816,27 +1866,28 @@ check_cpu()
capabilities_rdcl_no=-1 capabilities_rdcl_no=-1
capabilities_ibrs_all=-1 capabilities_ibrs_all=-1
capabilities_ssb_no=-1 capabilities_ssb_no=-1
capabilities_rsba=-1
if [ "$cpuid_arch_capabilities" = -1 ]; then if [ "$cpuid_arch_capabilities" = -1 ]; then
pstatus yellow UNKNOWN pstatus yellow UNKNOWN
elif [ "$cpuid_arch_capabilities" != 1 ]; then elif [ "$cpuid_arch_capabilities" != 1 ]; then
capabilities_rdcl_no=0 capabilities_rdcl_no=0
capabilities_ibrs_all=0 capabilities_ibrs_all=0
capabilities_ssb_no=0 capabilities_ssb_no=0
capabilities_rsba=0
pstatus yellow NO pstatus yellow NO
elif [ ! -e /dev/cpu/0/msr ] && [ ! -e /dev/cpuctl0 ]; then elif [ ! -e /dev/cpu/0/msr ] && [ ! -e /dev/cpuctl0 ]; then
spec_ctrl_msr=-1 spec_ctrl_msr=-1
pstatus yellow UNKNOWN "is msr kernel module available?" pstatus yellow UNKNOWN "is msr kernel module available?"
else else
# the new MSR 'ARCH_CAPABILITIES' is at offset 0x10a # 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 # we check if we have it for all cpus
# if we get a read error, the MSR is not there. bs has to be 8 for msr
val=0 val=0
val_cap_msr=0 val_cap_msr=0
cpu_mismatch=0 cpu_mismatch=0
for i in $(seq 0 "$idx_max_cpu") for i in $(seq 0 "$idx_max_cpu")
do do
read_msr 0x10a "$i"; ret=$? read_msr 0x10a "$i"; ret=$?
capabilities=$(echo "$read_msr_value" | awk '{print $8}') capabilities=$read_msr_value
if [ "$i" -eq 0 ]; then if [ "$i" -eq 0 ]; then
val=$ret val=$ret
val_cap_msr=$capabilities val_cap_msr=$capabilities
@ -1852,12 +1903,14 @@ check_cpu()
capabilities_rdcl_no=0 capabilities_rdcl_no=0
capabilities_ibrs_all=0 capabilities_ibrs_all=0
capabilities_ssb_no=0 capabilities_ssb_no=0
capabilities_rsba=0
if [ $val -eq 0 ]; then if [ $val -eq 0 ]; then
_debug "capabilities MSR lower byte is $capabilities (decimal)" _debug "capabilities MSR is $capabilities (decimal)"
[ $(( capabilities & 1 )) -eq 1 ] && capabilities_rdcl_no=1 [ $(( capabilities >> 0 & 1 )) -eq 1 ] && capabilities_rdcl_no=1
[ $(( capabilities & 2 )) -eq 2 ] && capabilities_ibrs_all=1 [ $(( capabilities >> 1 & 1 )) -eq 1 ] && capabilities_ibrs_all=1
[ $(( capabilities & 16 )) -eq 16 ] && capabilities_ssb_no=1 [ $(( capabilities >> 2 & 1 )) -eq 1 ] && capabilities_rsba=1
_debug "capabilities says rdcl_no=$capabilities_rdcl_no ibrs_all=$capabilities_ibrs_all ssb_no=$capabilities_ssb_no" [ $(( 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 [ "$capabilities_ibrs_all" = 1 ]; then
if [ $cpu_mismatch -eq 0 ]; then if [ $cpu_mismatch -eq 0 ]; then
pstatus green YES pstatus green YES
@ -1869,6 +1922,8 @@ check_cpu()
fi fi
elif [ $val -eq 200 ]; then elif [ $val -eq 200 ]; then
pstatus yellow UNKNOWN "is msr kernel module available?" 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 else
pstatus yellow NO pstatus yellow NO
fi fi
@ -1893,6 +1948,15 @@ check_cpu()
pstatus yellow NO pstatus yellow NO
fi 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: " _info_nol " * CPU microcode is known to cause stability problems: "
if is_ucode_blacklisted; then if is_ucode_blacklisted; then
pstatus red YES "$ucode_found" pstatus red YES "$ucode_found"
@ -2514,7 +2578,7 @@ check_variant2_linux()
fi fi
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: " _info_nol " * Kernel supports RSB filling: "
if ! which "${opt_arch_prefix}strings" >/dev/null 2>&1; then 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" 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 # override status & msg in case CPU is not vulnerable after all
pvulnstatus $cve OK "your CPU vendor reported your CPU model as not vulnerable" pvulnstatus $cve OK "your CPU vendor reported your CPU model as not vulnerable"
else 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" 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" pvulnstatus $cve OK "Full retpoline is mitigating the vulnerability"
if [ -n "$cpuid_ibpb" ]; then if [ -n "$cpuid_ibpb" ]; then
_warn "You should enable IBPB to complete retpoline as a Variant 2 mitigation" _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 we arrive here and didn't already call pvulnstatus, then it's VULN, let's explain why
if [ "$pvulnstatus_last_cve" != "$cve" ]; then if [ "$pvulnstatus_last_cve" != "$cve" ]; then
# explain what's needed for this CPU # 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" 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." 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 elif is_zen_cpu; then