From 97f4d5f2bc8b34d07c7924fc63ec4a0d22461297 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Lesimple?= Date: Fri, 17 Nov 2023 12:17:12 +0100 Subject: [PATCH] feat(reptar): add detection and mitigation of Reptar --- .github/workflows/check.yml | 6 +- README.md | 7 + spectre-meltdown-checker.sh | 2222 +++++++++++++++++++---------------- 3 files changed, 1194 insertions(+), 1041 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index b652628..8e750e5 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -24,7 +24,7 @@ jobs: fi - name: check direct execution run: | - expected=18 + expected=19 nb=$(sudo ./spectre-meltdown-checker.sh --batch json | jq '.[]|.CVE' | wc -l) if [ "$nb" -ne "$expected" ]; then echo "Invalid number of CVEs reported: $nb instead of $expected" @@ -34,7 +34,7 @@ jobs: fi - name: check docker-compose run execution run: | - expected=18 + expected=19 docker-compose build nb=$(docker-compose run --rm spectre-meltdown-checker --batch json | jq '.[]|.CVE' | wc -l) if [ "$nb" -ne "$expected" ]; then @@ -45,7 +45,7 @@ jobs: fi - name: check docker run execution run: | - expected=18 + expected=19 docker build -t spectre-meltdown-checker . nb=$(docker run --rm --privileged -v /boot:/boot:ro -v /dev/cpu:/dev/cpu:ro -v /lib/modules:/lib/modules:ro spectre-meltdown-checker --batch json | jq '.[]|.CVE' | wc -l) if [ "$nb" -ne "$expected" ]; then diff --git a/README.md b/README.md index 4f56a0f..0e13e08 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ CVE [CVE-2022-40982](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-40982) | Gather Data Sampling | GDS, Downfall [CVE-2023-20569](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-20569) | Return Address Security | Inception, RAS, SRSO [CVE-2023-20593](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-20593) | Cross-Process Information Leak | Zenbleed +[CVE-2023-23583](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-23583) | Redundant Prefix issue | Reptar Supported operating systems: - Linux (all versions, flavors and distros) @@ -199,3 +200,9 @@ docker run --rm --privileged -v /boot:/boot:ro -v /dev/cpu:/dev/cpu:ro -v /lib/m - Impact: Kernel & all software - Mitigation: either kernel mitigation by disabling a CPU optimization through an MSR bit, or CPU microcode mitigation - Performance impact of the mitigation: TBD + +**CVE-2023-23583** Redundant Prefix issue (Reptar) + + - Impact: All software + - Mitigation: microcode update for the affected CPU + - Performance impact of the mitigation: low diff --git a/spectre-meltdown-checker.sh b/spectre-meltdown-checker.sh index 78ca6bd..0f25bfd 100755 --- a/spectre-meltdown-checker.sh +++ b/spectre-meltdown-checker.sh @@ -12,7 +12,7 @@ # # Stephane Lesimple # -VERSION='0.46' +VERSION='0.46+' trap 'exit_cleanup' EXIT trap '_warn "interrupted, cleaning up..."; exit_cleanup; exit 1' INT @@ -177,7 +177,26 @@ 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' +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 @@ -305,6 +324,7 @@ cve2name() 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 } @@ -332,6 +352,7 @@ _is_cpu_affected_cached() 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 } @@ -402,8 +423,9 @@ is_cpu_affected() # Zenbleed and Inception are both AMD specific, look for "is_amd" below: variant_zenbleed=immune variant_inception=immune - # Downfall is Intel specific, look for "is_intel" below: + # 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 @@ -542,6 +564,59 @@ is_cpu_affected() 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=$(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 @@ -727,6 +802,7 @@ is_cpu_affected() [ "$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 @@ -1319,6 +1395,7 @@ while [ -n "${1:-}" ]; do 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 @@ -1414,6 +1491,7 @@ pvulnstatus() 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 @@ -1822,1143 +1900,1157 @@ is_coreos() return 1 } -parse_cpu_details() +# 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() { - [ "${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 + 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 - 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' + # 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 +} - 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 +write_msr_one_core() +{ + _core="$1" + _msr_dec=$(( $2 )) + _msr=$(printf "0x%x" "$_msr_dec") + _value_dec=$(( $3 )) + _value=$(printf "0x%x" "$_value_dec") - 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" + 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 - else - mockme=$(printf "%b\n%b" "$mockme" "SMC_MOCK_CPU_FAMILY='$cpu_family'") + [ "$(eval echo \$$_mockvarname)" = $WRITE_MSR_RET_LOCKDOWN ] && msr_locked_down=1 + return "$(eval echo \$$_mockvarname)" 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'") + + 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 [ -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'") + if [ ! -e /dev/cpu/0/msr ] && [ ! -e /dev/cpuctl0 ]; then + read_msr_msg="is msr kernel module available?" + return $WRITE_MSR_RET_ERR fi - # get raw cpuid, it's always useful (referenced in the Intel doc for firmware updates for example) - if read_cpuid 0x1 0x0 $EAX 0 0xFFFFFFFF; then - cpu_cpuid="$read_cpuid_value" + _write_denied=0 + if [ "$os" != Linux ]; then + cpucontrol -m "$_msr=$_value" "/dev/cpuctl$_core" >/dev/null 2>&1; ret=$? 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") + # 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 - # 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 + # normalize ret + if [ "$ret" = 0 ]; then + ret=$WRITE_MSR_RET_OK else - mockme=$(printf "%b\n%b" "$mockme" "SMC_MOCK_CPU_UCODE='$cpu_ucode'") + 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 +} - echo "$cpu_ucode" | grep -q ^0x && cpu_ucode=$(( cpu_ucode )) - ucode_found=$(printf "family 0x%x model 0x%x stepping 0x%x ucode 0x%x cpuid 0x%x" "$cpu_family" "$cpu_model" "$cpu_stepping" "$cpu_ucode" "$cpu_cpuid") - - # also define those that we will need in other funcs - # taken from https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/include/asm/intel-family.h - # curl -s 'https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/plain/arch/x86/include/asm/intel-family.h' | awk '/#define INTEL_FAM6/ {print $2"=$(( "$3" )) # "$4,$5,$6,$7,$8,$9}' | sed -re 's/ +$//' - # shellcheck disable=SC2034 - { - INTEL_FAM6_CORE_YONAH=$(( 0x0E )) # - INTEL_FAM6_CORE2_MEROM=$(( 0x0F )) # - INTEL_FAM6_CORE2_MEROM_L=$(( 0x16 )) # - INTEL_FAM6_CORE2_PENRYN=$(( 0x17 )) # - INTEL_FAM6_CORE2_DUNNINGTON=$(( 0x1D )) # - INTEL_FAM6_NEHALEM=$(( 0x1E )) # - INTEL_FAM6_NEHALEM_G=$(( 0x1F )) # /* Auburndale / Havendale */ - INTEL_FAM6_NEHALEM_EP=$(( 0x1A )) # - INTEL_FAM6_NEHALEM_EX=$(( 0x2E )) # - INTEL_FAM6_WESTMERE=$(( 0x25 )) # - INTEL_FAM6_WESTMERE_EP=$(( 0x2C )) # - INTEL_FAM6_WESTMERE_EX=$(( 0x2F )) # - INTEL_FAM6_SANDYBRIDGE=$(( 0x2A )) # - INTEL_FAM6_SANDYBRIDGE_X=$(( 0x2D )) # - INTEL_FAM6_IVYBRIDGE=$(( 0x3A )) # - INTEL_FAM6_IVYBRIDGE_X=$(( 0x3E )) # - INTEL_FAM6_HASWELL=$(( 0x3C )) # - INTEL_FAM6_HASWELL_X=$(( 0x3F )) # - INTEL_FAM6_HASWELL_L=$(( 0x45 )) # - INTEL_FAM6_HASWELL_G=$(( 0x46 )) # - INTEL_FAM6_BROADWELL=$(( 0x3D )) # - INTEL_FAM6_BROADWELL_G=$(( 0x47 )) # - INTEL_FAM6_BROADWELL_X=$(( 0x4F )) # - INTEL_FAM6_BROADWELL_D=$(( 0x56 )) # - INTEL_FAM6_SKYLAKE_L=$(( 0x4E )) # /* Sky Lake */ - INTEL_FAM6_SKYLAKE=$(( 0x5E )) # /* Sky Lake */ - INTEL_FAM6_SKYLAKE_X=$(( 0x55 )) # /* Sky Lake */ - INTEL_FAM6_KABYLAKE_L=$(( 0x8E )) # /* Sky Lake */ - INTEL_FAM6_KABYLAKE=$(( 0x9E )) # /* Sky Lake */ - INTEL_FAM6_COMETLAKE=$(( 0xA5 )) # /* Sky Lake */ - INTEL_FAM6_COMETLAKE_L=$(( 0xA6 )) # /* Sky Lake */ - INTEL_FAM6_CANNONLAKE_L=$(( 0x66 )) # /* Palm Cove */ - INTEL_FAM6_ICELAKE_X=$(( 0x6A )) # /* Sunny Cove */ - INTEL_FAM6_ICELAKE_D=$(( 0x6C )) # /* Sunny Cove */ - INTEL_FAM6_ICELAKE=$(( 0x7D )) # /* Sunny Cove */ - INTEL_FAM6_ICELAKE_L=$(( 0x7E )) # /* Sunny Cove */ - INTEL_FAM6_ICELAKE_NNPI=$(( 0x9D )) # /* Sunny Cove */ - INTEL_FAM6_LAKEFIELD=$(( 0x8A )) # /* Sunny Cove / Tremont */ - INTEL_FAM6_ROCKETLAKE=$(( 0xA7 )) # /* Cypress Cove */ - INTEL_FAM6_TIGERLAKE_L=$(( 0x8C )) # /* Willow Cove */ - INTEL_FAM6_TIGERLAKE=$(( 0x8D )) # /* Willow Cove */ - INTEL_FAM6_SAPPHIRERAPIDS_X=$(( 0x8F )) # /* Golden Cove */ - INTEL_FAM6_ALDERLAKE=$(( 0x97 )) # /* Golden Cove / Gracemont */ - INTEL_FAM6_ALDERLAKE_L=$(( 0x9A )) # /* Golden Cove / Gracemont */ - INTEL_FAM6_RAPTORLAKE=$(( 0xB7 )) # - INTEL_FAM6_ATOM_BONNELL=$(( 0x1C )) # /* Diamondville, Pineview */ - INTEL_FAM6_ATOM_BONNELL_MID=$(( 0x26 )) # /* Silverthorne, Lincroft */ - INTEL_FAM6_ATOM_SALTWELL=$(( 0x36 )) # /* Cedarview */ - INTEL_FAM6_ATOM_SALTWELL_MID=$(( 0x27 )) # /* Penwell */ - INTEL_FAM6_ATOM_SALTWELL_TABLET=$(( 0x35 )) # /* Cloverview */ - INTEL_FAM6_ATOM_SILVERMONT=$(( 0x37 )) # /* Bay Trail, Valleyview */ - INTEL_FAM6_ATOM_SILVERMONT_D=$(( 0x4D )) # /* Avaton, Rangely */ - INTEL_FAM6_ATOM_SILVERMONT_MID=$(( 0x4A )) # /* Merriefield */ - INTEL_FAM6_ATOM_AIRMONT=$(( 0x4C )) # /* Cherry Trail, Braswell */ - INTEL_FAM6_ATOM_AIRMONT_MID=$(( 0x5A )) # /* Moorefield */ - INTEL_FAM6_ATOM_AIRMONT_NP=$(( 0x75 )) # /* Lightning Mountain */ - INTEL_FAM6_ATOM_GOLDMONT=$(( 0x5C )) # /* Apollo Lake */ - INTEL_FAM6_ATOM_GOLDMONT_D=$(( 0x5F )) # /* Denverton */ - INTEL_FAM6_ATOM_GOLDMONT_PLUS=$(( 0x7A )) # /* Gemini Lake */ - INTEL_FAM6_ATOM_TREMONT_D=$(( 0x86 )) # /* Jacobsville */ - INTEL_FAM6_ATOM_TREMONT=$(( 0x96 )) # /* Elkhart Lake */ - INTEL_FAM6_ATOM_TREMONT_L=$(( 0x9C )) # /* Jasper Lake */ - INTEL_FAM6_XEON_PHI_KNL=$(( 0x57 )) # /* Knights Landing */ - INTEL_FAM6_XEON_PHI_KNM=$(( 0x85 )) # /* Knights Mill */ - } - parse_cpu_details_done=1 -} -is_hygon() -{ - 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() +# 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() { - # 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 + 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 - # 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 + # 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 - _debug "is_ucode_blacklisted: no ($cpu_model/$cpu_stepping/$cpu_ucode)" - return 1 + # if we're here, all cores agree, return the result + return $ret } -is_skylake_cpu() +read_msr_one_core() { - # 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 -} + _core="$1" + _msr_dec=$(( $2 )) + _msr=$(printf "0x%x" "$_msr_dec") -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 -} + read_msr_value='' + read_msr_msg='unknown error' -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 -} + _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 -is_moksha_cpu() -{ - parse_cpu_details - is_hygon || return 1 - [ "$cpu_family" = 24 ] && return 0 - return 1 -} + _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 -# 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) )) -} + 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 -# 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 + 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 - return 1 + 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 } -# returns 0 (true) if yes, 1 otherwise -# returns 2 if not applicable -has_zenbleed_fixed_firmware() + +parse_cpu_details() { - # 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 -} + [ "${parse_cpu_details_done:-}" = 1 ] && return 0 -# Test if the current host is a Xen PV Dom0 / DomU -is_xen() { - if [ ! -d "$procfs/xen" ]; then - return 1 + 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 )) - # 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 + 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 - return 1 + 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 -} -is_xen_dom0() -{ - if ! is_xen; then - return 1 + # 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 + # FIXME add MOCK - if [ -e "$procfs/xen/capabilities" ] && grep -q "control_d" "$procfs/xen/capabilities"; then - return 0 + 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 - return 1 + mockme=$(printf "%b\n%b" "$mockme" "SMC_MOCK_CPU_FRIENDLY_NAME='$cpu_friendly_name'") fi -} - -is_xen_domU() -{ - if ! is_xen; then - return 1 + 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 - - # 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 + 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 ! is_xen_dom0; then - return 0 + 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 - return 1 + mockme=$(printf "%b\n%b" "$mockme" "SMC_MOCK_CPU_MODEL='$cpu_model'") 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" + 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 -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" + # get raw cpuid, it's always useful (referenced in the Intel doc for firmware updates for example) + if read_cpuid 0x1 0x0 $EAX 0 0xFFFFFFFF; then + cpu_cpuid="$read_cpuid_value" + else + # 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 - # 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 - ucode=$(( $(echo "$tuple" | cut -d, -f3) )) - ucode_date=$(echo "$tuple" | cut -d, -f4 | sed -r 's=(....)(..)(..)=\1/\2/\3=') - _debug "is_latest_known_ucode: with cpuid $cpu_cpuid has ucode $cpu_ucode, last known is $ucode from $ucode_date" - ucode_latest=$(printf "latest version is 0x%x dated $ucode_date according to $mcedb_info" "$ucode") - if [ "$cpu_ucode" -ge "$ucode" ]; then - return 0 - else - return 1 + # 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 - 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 + # 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 - _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'") + mockme=$(printf "%b\n%b" "$mockme" "SMC_MOCK_CPU_UCODE='$cpu_ucode'") 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 + 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") -# 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 + # 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 +} -# 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 +is_amd() +{ + parse_cpu_details + [ "$cpu_vendor" = AuthenticAMD ] && return 0 + return 1 +} -# 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 +is_intel() +{ + parse_cpu_details + [ "$cpu_vendor" = GenuineIntel ] && return 0 + return 1 +} -# 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='' +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 - 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" + # we can't tell + return 2 +} - # try to find the image of the current running kernel - if [ -n "$opt_kernel" ]; then - # specified by user on cmdline, with --live, don't override - : - # first, look for the BOOT_IMAGE hint in the kernel cmdline - elif echo "$kernel_cmdline" | grep -q 'BOOT_IMAGE='; then - opt_kernel=$(echo "$kernel_cmdline" | grep -Eo 'BOOT_IMAGE=[^ ]+' | cut -d= -f2) - _debug "found opt_kernel=$opt_kernel in $procfs/cmdline" - # if the boot partition is within a btrfs subvolume, strip the subvolume name - # if /boot is a separate subvolume, the remainder of the code in this section should handle it - if echo "$opt_kernel" | grep -q "^/@"; then opt_kernel=$(echo "$opt_kernel" | sed "s:/@[^/]*::"); fi - # if we have a dedicated /boot partition, our bootloader might have just called it / - # so try to prepend /boot and see if we find anything - [ -e "/boot/$opt_kernel" ] && opt_kernel="/boot/$opt_kernel" - # special case for CoreOS if we're inside the toolbox - [ -e "/media/root/boot/$opt_kernel" ] && opt_kernel="/media/root/boot/$opt_kernel" - _debug "opt_kernel is now $opt_kernel" - # else, the full path is already there (most probably /boot/something) - fi - # if we didn't find a kernel, default to guessing - if [ ! -e "$opt_kernel" ]; then - # Fedora: - [ -e "/lib/modules/$(uname -r)/vmlinuz" ] && opt_kernel="/lib/modules/$(uname -r)/vmlinuz" - # Slackware: - [ -e "/boot/vmlinuz" ] && opt_kernel="/boot/vmlinuz" - # Arch aarch64: - [ -e "/boot/Image" ] && opt_kernel="/boot/Image" - # Arch armv5/armv7: - [ -e "/boot/zImage" ] && opt_kernel="/boot/zImage" - # Arch arm7: - [ -e "/boot/kernel7.img" ] && opt_kernel="/boot/kernel7.img" - # Linux-Libre: - [ -e "/boot/vmlinuz-linux-libre" ] && opt_kernel="/boot/vmlinuz-linux-libre" - # pine64 - [ -e "/boot/pine64/Image" ] && opt_kernel="/boot/pine64/Image" - # generic: - [ -e "/boot/vmlinuz-$(uname -r)" ] && opt_kernel="/boot/vmlinuz-$(uname -r)" - [ -e "/boot/kernel-$( uname -r)" ] && opt_kernel="/boot/kernel-$( uname -r)" - [ -e "/boot/bzImage-$(uname -r)" ] && opt_kernel="/boot/bzImage-$(uname -r)" - # Gentoo: - [ -e "/boot/kernel-genkernel-$(uname -m)-$(uname -r)" ] && opt_kernel="/boot/kernel-genkernel-$(uname -m)-$(uname -r)" - # NixOS: - [ -e "/run/booted-system/kernel" ] && opt_kernel="/run/booted-system/kernel" - # Guix System: - [ -e "/run/booted-system/kernel/bzImage" ] && opt_kernel="/run/booted-system/kernel/bzImage" - # systemd kernel-install: - [ -e "/etc/machine-id" ] && [ -e "/boot/$(cat /etc/machine-id)/$(uname -r)/linux" ] && opt_kernel="/boot/$(cat /etc/machine-id)/$(uname -r)/linux" - # Clear Linux: - str_uname=$(uname -r) - clear_linux_kernel="/lib/kernel/org.clearlinux.${str_uname##*.}.${str_uname%.*}" - [ -e "$clear_linux_kernel" ] && opt_kernel=$clear_linux_kernel - # Custom Arch seems to have the kernel path in its cmdline in the form "\directory\kernelimage", - # with actual \'s instead of /'s: - custom_arch_kernel=$(echo "$kernel_cmdline" | grep -Eo "(^|\s)\\\\[\\\\a-zA-Z0-9_.-]+" | tr "\\\\" "/" | tr -d '[:space:]') - if [ -n "$custom_arch_kernel" ] && [ -e "$custom_arch_kernel" ]; then - opt_kernel="$custom_arch_kernel" +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 - # FreeBSD: - [ -e "/boot/kernel/kernel" ] && opt_kernel="/boot/kernel/kernel" - fi + done + _debug "is_ucode_blacklisted: no ($cpu_model/$cpu_stepping/$cpu_ucode)" + return 1 +} - # system.map - if [ -n "$opt_map" ]; then - # specified by user on cmdline, with --live, don't override - : - elif [ -e "$procfs/kallsyms" ] ; then - opt_map="$procfs/kallsyms" - elif [ -e "/lib/modules/$(uname -r)/System.map" ] ; then - opt_map="/lib/modules/$(uname -r)/System.map" - elif [ -e "/boot/System.map-$(uname -r)" ] ; then - opt_map="/boot/System.map-$(uname -r)" - elif [ -e "/lib/kernel/System.map-$(uname -r)" ]; then - opt_map="/lib/kernel/System.map-$(uname -r)" +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 +} - # config - if [ -n "$opt_config" ]; then - # specified by user on cmdline, with --live, don't override - : - elif [ -e "$procfs/config.gz" ] ; then - dumped_config="$(mktemp -t smc-config-XXXXXX)" - gunzip -c "$procfs/config.gz" > "$dumped_config" - # dumped_config will be deleted at the end of the script - opt_config="$dumped_config" - elif [ -e "/lib/modules/$(uname -r)/config" ]; then - opt_config="/lib/modules/$(uname -r)/config" - elif [ -e "/boot/config-$(uname -r)" ]; then - opt_config="/boot/config-$(uname -r)" - elif [ -e "/etc/kernels/kernel-config-$(uname -m)-$(uname -r)" ]; then - opt_config="/etc/kernels/kernel-config-$(uname -m)-$(uname -r)" - elif [ -e "/lib/kernel/config-$(uname -r)" ]; then - opt_config="/lib/kernel/config-$(uname -r)" +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 -else - _info "Checking for vulnerabilities against specified kernel" - _info "CPU is \033[35m$cpu_friendly_name\033[0m" -fi - -if [ -n "$opt_kernel" ]; then - _verbose "Will use kernel image \033[35m$opt_kernel\033[0m" -else - _verbose "Will use no kernel image (accuracy might be reduced)" - bad_accuracy=1 -fi - -if [ "$os" = Linux ]; then - if [ -n "$opt_config" ] && ! grep -q '^CONFIG_' "$opt_config"; then - # given file is invalid! - _warn "The kernel config file seems invalid, was expecting a plain-text file, ignoring it!" - opt_config='' + if is_skylake_cpu || [ "$capabilities_rsba" = 1 ]; then + return 0 fi + return 1 +} - if [ -n "${dumped_config:-}" ] && [ -n "$opt_config" ]; then - _verbose "Will use kconfig \033[35m$procfs/config.gz (decompressed)\033[0m" - elif [ -n "$opt_config" ]; then - _verbose "Will use kconfig \033[35m$opt_config\033[0m" - else - _verbose "Will use no kconfig (accuracy might be reduced)" - bad_accuracy=1 - fi +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 +} - 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 +is_moksha_cpu() +{ + parse_cpu_details + is_hygon || return 1 + [ "$cpu_family" = 24 ] && return 0 + return 1 +} - if [ "${bad_accuracy:=0}" = 1 ]; then - _warn "We're missing some kernel info (see -v), accuracy might be reduced" +# 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 -fi + return 1 +} -if [ -e "$opt_kernel" ]; then - if ! command -v "${opt_arch_prefix}readelf" >/dev/null 2>&1; then - _debug "readelf not found" - kernel_err="missing '${opt_arch_prefix}readelf' tool, please install it, usually it's in the 'binutils' package" - elif [ "$opt_sysfs_only" = 1 ] || [ "$opt_hw_only" = 1 ]; then - kernel_err='kernel image decompression skipped' - else - extract_kernel "$opt_kernel" +# 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 -else - _debug "no opt_kernel defined" - kernel_err="couldn't find your kernel image in /boot, if you used netboot, this is normal" -fi -if [ -z "$kernel" ] || [ ! -r "$kernel" ]; then - [ -z "$kernel_err" ] && kernel_err="couldn't extract your kernel from $opt_kernel" -else - # vanilla kernels have with ^Linux version - # also try harder with some kernels (such as Red Hat) that don't have ^Linux version before their version string - # and check for FreeBSD - kernel_version=$("${opt_arch_prefix}strings" "$kernel" 2>/dev/null | grep -E \ - -e '^Linux version ' \ - -e '^[[:alnum:]][^[:space:]]+ \([^[:space:]]+\) #[0-9]+ .+ (19|20)[0-9][0-9]$' \ - -e '^FreeBSD [0-9]' | grep -v 'ABI compat' | head -1) - if [ -z "$kernel_version" ]; then - # try even harder with some kernels (such as ARM) that split the release (uname -r) and version (uname -v) in 2 adjacent strings - kernel_version=$("${opt_arch_prefix}strings" "$kernel" 2>/dev/null | grep -E -B1 '^#[0-9]+ .+ (19|20)[0-9][0-9]$' | tr "\n" " ") - fi - if [ -n "$kernel_version" ]; then - # in live mode, check if the img we found is the correct one - if [ "$opt_live" = 1 ]; then - _verbose "Kernel image is \033[35m$kernel_version" - if ! echo "$kernel_version" | grep -qF "$(uname -r)"; then - _warn "Possible discrepancy between your running kernel '$(uname -r)' and the image '$kernel_version' we found ($opt_kernel), results might be incorrect" + # 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 - else - _info "Kernel image is \033[35m$kernel_version" fi - else - _verbose "Kernel image version is unknown" - fi -fi - -_info + done + unset _tuples + return $zenbleed_fw +} -# end of header stuff +# Test if the current host is a Xen PV Dom0 / DomU +is_xen() { + if [ ! -d "$procfs/xen" ]; then + return 1 + fi -# now we define some util functions and the check_*() funcs, as -# the user can choose to execute only some of those + # 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 +} -sys_interface_check() +is_xen_dom0() { - file="$1" - regex="${2:-}" - mode="${3:-}" - msg='' - fullmsg='' + if ! is_xen; then + return 1 + fi - if [ "$opt_live" = 1 ] && [ "$opt_no_sysfs" = 0 ] && [ -r "$file" ]; then - : + if [ -e "$procfs/xen/capabilities" ] && grep -q "control_d" "$procfs/xen/capabilities"; then + return 0 else - mockme=$(printf "%b\n%b" "$mockme" "SMC_MOCK_SYSFS_$(basename "$file")_RET=1") return 1 fi +} - _mockvarname="SMC_MOCK_SYSFS_$(basename "$file")_RET" - # shellcheck disable=SC2086,SC1083 - if [ -n "$(eval echo \${$_mockvarname:-})" ]; then - _debug "sysfs: MOCKING enabled for $file func returns $(eval echo \$$_mockvarname)" - mocked=1 - return "$(eval echo \$$_mockvarname)" +is_xen_domU() +{ + if ! is_xen; then + return 1 fi - [ -n "$regex" ] || regex='.*' - _mockvarname="SMC_MOCK_SYSFS_$(basename "$file")" - # shellcheck disable=SC2086,SC1083 - if [ -n "$(eval echo \${$_mockvarname:-})" ]; then - fullmsg="$(eval echo \$$_mockvarname)" - msg=$(echo "$fullmsg" | grep -Eo "$regex") - _debug "sysfs: MOCKING enabled for $file, will return $fullmsg" - mocked=1 - else - fullmsg=$(cat "$file") - msg=$(grep -Eo "$regex" "$file") - mockme=$(printf "%b\n%b" "$mockme" "SMC_MOCK_SYSFS_$(basename "$file")='$fullmsg'") + # 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 [ "$mode" = silent ]; then - return 0 - elif [ "$mode" = quiet ]; then - _info "* Information from the /sys interface: $fullmsg" + + if ! is_xen_dom0; then return 0 - fi - _info_nol "* Mitigated according to the /sys interface: " - if echo "$msg" | grep -qi '^not affected'; then - # Not affected - status=OK - pstatus green YES "$fullmsg" - elif echo "$msg" | grep -qEi '^(kvm: )?mitigation'; then - # Mitigation: PTI - status=OK - pstatus green YES "$fullmsg" - elif echo "$msg" | grep -qi '^vulnerable'; then - # Vulnerable - status=VULN - pstatus yellow NO "$fullmsg" else - status=UNK - pstatus yellow UNKNOWN "$fullmsg" + return 1 fi - _debug "sys_interface_check: $file=$msg (re=$regex)" - return 0 } -# 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() +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() { - 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 $? + 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 +} - # 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 +is_latest_known_ucode() +{ + # 0: yes, 1: no, 2: unknown + parse_cpu_details + if [ "$cpu_cpuid" = 0 ]; then + ucode_latest="couldn't get your cpuid" + return 2 + fi + ucode_latest="latest microcode version for your CPU model is unknown" + if is_intel; then + cpu_brand_prefix=I + elif is_amd; then + cpu_brand_prefix=A + else + return 2 + fi + for tuple in $(read_mcedb | grep "$(printf "^$cpu_brand_prefix,0x%08X," "$cpu_cpuid")") + do + ucode=$(( $(echo "$tuple" | cut -d, -f3) )) + ucode_date=$(echo "$tuple" | cut -d, -f4 | sed -r 's=(....)(..)(..)=\1/\2/\3=') + _debug "is_latest_known_ucode: with cpuid $cpu_cpuid has ucode $cpu_ucode, last known is $ucode from $ucode_date" + ucode_latest=$(printf "latest version is 0x%x dated $ucode_date according to $mcedb_info" "$ucode") + if [ "$cpu_ucode" -ge "$ucode" ]; then + return 0 else - # 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 + return 1 fi done - # if we're here, all cores agree, return the result - return $ret + _debug "is_latest_known_ucode: this cpuid is not referenced ($cpu_cpuid)" + return 2 } -write_msr_one_core() +get_cmdline() { - _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}" + if [ -n "${kernel_cmdline:-}" ]; then + return + fi - _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)" + if [ -n "${SMC_MOCK_CMDLINE:-}" ]; then mocked=1 - [ "$(eval echo \$$_mockvarname)" = $WRITE_MSR_RET_LOCKDOWN ] && msr_locked_down=1 - return "$(eval echo \$$_mockvarname)" + _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 +} - 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 +# 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 - if [ ! -e /dev/cpu/0/msr ] && [ ! -e /dev/cpuctl0 ]; then - read_msr_msg="is msr kernel module available?" - return $WRITE_MSR_RET_ERR +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 - _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 +# 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 - _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 + procfs='' 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 + 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" ] && opt_kernel="/boot/zImage" + # Arch arm7: + [ -e "/boot/kernel7.img" ] && opt_kernel="/boot/kernel7.img" + # Linux-Libre: + [ -e "/boot/vmlinuz-linux-libre" ] && opt_kernel="/boot/vmlinuz-linux-libre" + # pine64 + [ -e "/boot/pine64/Image" ] && opt_kernel="/boot/pine64/Image" + # generic: + [ -e "/boot/vmlinuz-$(uname -r)" ] && opt_kernel="/boot/vmlinuz-$(uname -r)" + [ -e "/boot/kernel-$( uname -r)" ] && opt_kernel="/boot/kernel-$( uname -r)" + [ -e "/boot/bzImage-$(uname -r)" ] && opt_kernel="/boot/bzImage-$(uname -r)" + # Gentoo: + [ -e "/boot/kernel-genkernel-$(uname -m)-$(uname -r)" ] && opt_kernel="/boot/kernel-genkernel-$(uname -m)-$(uname -r)" + # NixOS: + [ -e "/run/booted-system/kernel" ] && opt_kernel="/run/booted-system/kernel" + # Guix System: + [ -e "/run/booted-system/kernel/bzImage" ] && opt_kernel="/run/booted-system/kernel/bzImage" + # systemd kernel-install: + [ -e "/etc/machine-id" ] && [ -e "/boot/$(cat /etc/machine-id)/$(uname -r)/linux" ] && opt_kernel="/boot/$(cat /etc/machine-id)/$(uname -r)/linux" + # Clear Linux: + str_uname=$(uname -r) + clear_linux_kernel="/lib/kernel/org.clearlinux.${str_uname##*.}.${str_uname%.*}" + [ -e "$clear_linux_kernel" ] && opt_kernel=$clear_linux_kernel + # Custom Arch seems to have the kernel path in its cmdline in the form "\directory\kernelimage", + # with actual \'s instead of /'s: + custom_arch_kernel=$(echo "$kernel_cmdline" | grep -Eo "(^|\s)\\\\[\\\\a-zA-Z0-9_.-]+" | tr "\\\\" "/" | tr -d '[:space:]') + if [ -n "$custom_arch_kernel" ] && [ -e "$custom_arch_kernel" ]; then + opt_kernel="$custom_arch_kernel" fi + # FreeBSD: + [ -e "/boot/kernel/kernel" ] && opt_kernel="/boot/kernel/kernel" + fi + + # system.map + if [ -n "$opt_map" ]; then + # specified by user on cmdline, with --live, don't override + : + elif [ -e "$procfs/kallsyms" ] ; then + opt_map="$procfs/kallsyms" + elif [ -e "/lib/modules/$(uname -r)/System.map" ] ; then + opt_map="/lib/modules/$(uname -r)/System.map" + elif [ -e "/boot/System.map-$(uname -r)" ] ; then + opt_map="/boot/System.map-$(uname -r)" + elif [ -e "/lib/kernel/System.map-$(uname -r)" ]; then + opt_map="/lib/kernel/System.map-$(uname -r)" + fi + + # config + if [ -n "$opt_config" ]; then + # specified by user on cmdline, with --live, don't override + : + elif [ -e "$procfs/config.gz" ] ; then + dumped_config="$(mktemp -t smc-config-XXXXXX)" + gunzip -c "$procfs/config.gz" > "$dumped_config" + # dumped_config will be deleted at the end of the script + opt_config="$dumped_config" + elif [ -e "/lib/modules/$(uname -r)/config" ]; then + opt_config="/lib/modules/$(uname -r)/config" + elif [ -e "/boot/config-$(uname -r)" ]; then + opt_config="/boot/config-$(uname -r)" + elif [ -e "/etc/kernels/kernel-config-$(uname -m)-$(uname -r)" ]; then + opt_config="/etc/kernels/kernel-config-$(uname -m)-$(uname -r)" + elif [ -e "/lib/kernel/config-$(uname -r)" ]; then + opt_config="/lib/kernel/config-$(uname -r)" fi +else + _info "Checking for vulnerabilities against specified kernel" + _info "CPU is \033[35m$cpu_friendly_name\033[0m" +fi - # normalize ret - if [ "$ret" = 0 ]; then - ret=$WRITE_MSR_RET_OK +if [ -n "$opt_kernel" ]; then + _verbose "Will use kernel image \033[35m$opt_kernel\033[0m" +else + _verbose "Will use no kernel image (accuracy might be reduced)" + bad_accuracy=1 +fi + +if [ "$os" = Linux ]; then + if [ -n "$opt_config" ] && ! grep -q '^CONFIG_' "$opt_config"; then + # given file is invalid! + _warn "The kernel config file seems invalid, was expecting a plain-text file, ignoring it!" + opt_config='' + fi + + if [ -n "${dumped_config:-}" ] && [ -n "$opt_config" ]; then + _verbose "Will use kconfig \033[35m$procfs/config.gz (decompressed)\033[0m" + elif [ -n "$opt_config" ]; then + _verbose "Will use kconfig \033[35m$opt_config\033[0m" else - ret=$WRITE_MSR_RET_KO + _verbose "Will use no kconfig (accuracy might be reduced)" + bad_accuracy=1 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 $? + 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 - # 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 + if [ "${bad_accuracy:=0}" = 1 ]; then + _warn "We're missing some kernel info (see -v), accuracy might be reduced" + fi +fi + +if [ -e "$opt_kernel" ]; then + if ! command -v "${opt_arch_prefix}readelf" >/dev/null 2>&1; then + _debug "readelf not found" + kernel_err="missing '${opt_arch_prefix}readelf' tool, please install it, usually it's in the 'binutils' package" + elif [ "$opt_sysfs_only" = 1 ] || [ "$opt_hw_only" = 1 ]; then + kernel_err='kernel image decompression skipped' + else + extract_kernel "$opt_kernel" + fi +else + _debug "no opt_kernel defined" + kernel_err="couldn't find your kernel image in /boot, if you used netboot, this is normal" +fi +if [ -z "$kernel" ] || [ ! -r "$kernel" ]; then + [ -z "$kernel_err" ] && kernel_err="couldn't extract your kernel from $opt_kernel" +else + # vanilla kernels have with ^Linux version + # also try harder with some kernels (such as Red Hat) that don't have ^Linux version before their version string + # and check for FreeBSD + kernel_version=$("${opt_arch_prefix}strings" "$kernel" 2>/dev/null | grep -E \ + -e '^Linux version ' \ + -e '^[[:alnum:]][^[:space:]]+ \([^[:space:]]+\) #[0-9]+ .+ (19|20)[0-9][0-9]$' \ + -e '^FreeBSD [0-9]' | grep -v 'ABI compat' | head -1) + if [ -z "$kernel_version" ]; then + # try even harder with some kernels (such as ARM) that split the release (uname -r) and version (uname -v) in 2 adjacent strings + kernel_version=$("${opt_arch_prefix}strings" "$kernel" 2>/dev/null | grep -E -B1 '^#[0-9]+ .+ (19|20)[0-9][0-9]$' | tr "\n" " ") + fi + if [ -n "$kernel_version" ]; then + # in live mode, check if the img we found is the correct one + if [ "$opt_live" = 1 ]; then + _verbose "Kernel image is \033[35m$kernel_version" + if ! echo "$kernel_version" | grep -qF "$(uname -r)"; then + _warn "Possible discrepancy between your running kernel '$(uname -r)' and the image '$kernel_version' we found ($opt_kernel), results might be incorrect" fi + else + _info "Kernel image is \033[35m$kernel_version" fi - done - # if we're here, all cores agree, return the result - return $ret -} + else + _verbose "Kernel image version is unknown" + fi +fi -read_msr_one_core() +_info + +# end of header stuff + +# now we define some util functions and the check_*() funcs, as +# the user can choose to execute only some of those + +sys_interface_check() { - _core="$1" - _msr_dec=$(( $2 )) - _msr=$(printf "0x%x" "$_msr_dec") + file="$1" + regex="${2:-}" + mode="${3:-}" + msg='' + fullmsg='' - read_msr_value='' - read_msr_msg='unknown error' + if [ "$opt_live" = 1 ] && [ "$opt_no_sysfs" = 0 ] && [ -r "$file" ]; then + : + else + mockme=$(printf "%b\n%b" "$mockme" "SMC_MOCK_SYSFS_$(basename "$file")_RET=1") + return 1 + fi - _mockvarname="SMC_MOCK_RDMSR_${_msr}" + _mockvarname="SMC_MOCK_SYSFS_$(basename "$file")_RET" # 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" + _debug "sysfs: MOCKING enabled for $file func returns $(eval echo \$$_mockvarname)" mocked=1 - return $READ_MSR_RET_OK + return "$(eval echo \$$_mockvarname)" fi - _mockvarname="SMC_MOCK_RDMSR_${_msr}_RET" + [ -n "$regex" ] || regex='.*' + _mockvarname="SMC_MOCK_SYSFS_$(basename "$file")" # 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)" + if [ -n "$(eval echo \${$_mockvarname:-})" ]; then + fullmsg="$(eval echo \$$_mockvarname)" + msg=$(echo "$fullmsg" | grep -Eo "$regex") + _debug "sysfs: MOCKING enabled for $file, will return $fullmsg" mocked=1 - 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 + else + fullmsg=$(cat "$file") + msg=$(grep -Eo "$regex" "$file") + mockme=$(printf "%b\n%b" "$mockme" "SMC_MOCK_SYSFS_$(basename "$file")='$fullmsg'") fi - if [ ! -e /dev/cpu/0/msr ] && [ ! -e /dev/cpuctl0 ]; then - read_msr_msg="is msr kernel module available?" - return $READ_MSR_RET_ERR + if [ "$mode" = silent ]; then + return 0 + elif [ "$mode" = quiet ]; then + _info "* Information from the /sys interface: $fullmsg" + return 0 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 )) + _info_nol "* Mitigated according to the /sys interface: " + if echo "$msg" | grep -qi '^not affected'; then + # Not affected + status=OK + pstatus green YES "$fullmsg" + elif echo "$msg" | grep -qEi '^(kvm: )?mitigation'; then + # Mitigation: PTI + status=OK + pstatus green YES "$fullmsg" + elif echo "$msg" | grep -qi '^vulnerable'; then + # Vulnerable + status=VULN + pstatus yellow NO "$fullmsg" else - # 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 )) + status=UNK + pstatus yellow UNKNOWN "$fullmsg" 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 + _debug "sys_interface_check: $file=$msg (re=$regex)" + return 0 } check_cpu() @@ -6489,6 +6581,42 @@ check_CVE_2023_20569_linux() { fi } +####################### +# Reptar section + +check_CVE_2023_23583() { + cve='CVE-2023-23583' + _info "\033[1;34m$cve aka '$(cve2name "$cve")'\033[0m" + if [ "$os" = Linux ] + then + check_CVE_2023_23583_linux + else + _warn "Unsupported OS ($os)." + fi +} + +check_CVE_2023_23583_linux() { + status=UNK + sys_interface_available=0 + msg='' + + # there is no sysfs file for this vuln, and no kernel patch, + # the mitigation is only ucode-based and there's no flag exposed, + # so most of the work has already been done by is_cpu_affected() + if ! is_cpu_affected "$cve" ; then + pvulnstatus "$cve" OK "your CPU vendor reported your CPU model as not affected" + else + _info_nol "* Reptar is mitigated by microcode: " + if [ "$cpu_ucode" -lt "$reptar_fixed_ucode_version" ]; then + pstatus yellow NO "You have ucode $(printf "0x%x" $cpu_ucode) and version $(printf "0x%x" $reptar_fixed_ucode_version) minimum is required" + pvulnstatus $cve VULN "Your microcode is too old to mitigate the vulnerability" + else + pstatus green YES "You have ucode $(printf "0x%x" $cpu_ucode) which is recent enough (>= $(printf "0x%x" $reptar_fixed_ucode_version))" + pvulnstatus $cve OK "Your microcode mitigates the vulnerability" + fi + fi +} + ####################### # END OF VULNS SECTIONS @@ -6711,7 +6839,7 @@ exit 0 # ok # with X being either I for Intel, or A for AMD # When the date is unknown it defaults to 20000101 -# %%% MCEDB v273+i20230808+b6bd +# %%% MCEDB v282+i20231114+826c # I,0x00000611,0x00000B27,19961218 # I,0x00000612,0x000000C6,19961210 # I,0x00000616,0x000000C6,19961210 @@ -6978,11 +7106,12 @@ exit 0 # ok # I,0x000606A0,0x80000031,20200308 # I,0x000606A4,0x0B000280,20200817 # I,0x000606A5,0x0C0002F0,20210308 -# I,0x000606A6,0x0D0003A5,20230330 +# I,0x000606A6,0x0D0003B9,20230901 # I,0x000606C0,0xFD000220,20210629 -# I,0x000606C1,0x01000230,20230127 +# I,0x000606C1,0x01000268,20230908 # I,0x000606E0,0x0000000B,20161104 # I,0x000606E1,0x00000108,20190423 +# I,0x000606E4,0x0000000C,20190124 # I,0x000706A0,0x00000026,20170712 # I,0x000706A1,0x0000003E,20220916 # I,0x000706A8,0x00000022,20220920 @@ -6991,40 +7120,42 @@ exit 0 # ok # I,0x000706E2,0x00000042,20190420 # I,0x000706E3,0x81000008,20181002 # I,0x000706E4,0x00000046,20190905 -# I,0x000706E5,0x000000BC,20230226 +# I,0x000706E5,0x000000C2,20230903 # I,0x00080650,0x00000018,20180108 -# I,0x00080664,0x4C000021,20220815 -# I,0x00080665,0x4C000021,20220815 -# I,0x00080667,0x4C000021,20220815 +# I,0x00080664,0x4C000023,20230222 +# I,0x00080665,0x4C000023,20230222 +# I,0x00080667,0x4C000023,20230222 # I,0x000806A0,0x00000010,20190507 # I,0x000806A1,0x00000033,20230113 # I,0x000806C0,0x00000068,20200402 -# I,0x000806C1,0x000000AC,20230227 -# I,0x000806C2,0x0000002C,20230227 -# I,0x000806D0,0x00000050,20201217 -# I,0x000806D1,0x00000046,20230227 +# I,0x000806C1,0x000000B4,20230907 +# I,0x000806C2,0x00000034,20230907 +# I,0x000806D0,0x00000054,20210507 +# I,0x000806D1,0x0000004E,20230907 # I,0x000806E9,0x000000F4,20230223 # I,0x000806EA,0x000000F4,20230223 # I,0x000806EB,0x000000F4,20230223 # I,0x000806EC,0x000000F8,20230226 +# I,0x000806F1,0x800003C0,20220327 +# I,0x000806F2,0x8C0004E0,20211112 # I,0x000806F3,0x8D000520,20220812 -# I,0x000806F4,0x2C000271,20230515 -# I,0x000806F5,0x2C000271,20230515 -# I,0x000806F6,0x2C000271,20230515 -# I,0x000806F7,0x2B0004B1,20230509 -# I,0x000806F8,0x2C000271,20230515 +# I,0x000806F4,0x2C000290,20230626 +# I,0x000806F5,0x2C000290,20230626 +# I,0x000806F6,0x2C000290,20230626 +# I,0x000806F7,0x2B0004D0,20230616 +# I,0x000806F8,0x2C000290,20230626 # I,0x00090660,0x00000009,20200617 # I,0x00090661,0x00000017,20220715 # I,0x00090670,0x00000019,20201111 # I,0x00090671,0x0000001C,20210614 -# I,0x00090672,0x0000002E,20230418 +# I,0x00090672,0x00000032,20230607 # I,0x00090674,0x00000219,20210425 -# I,0x00090675,0x0000002E,20230418 +# I,0x00090675,0x00000032,20230607 # I,0x000906A0,0x0000001C,20210614 # I,0x000906A1,0x0000011F,20211104 # I,0x000906A2,0x00000315,20220102 -# I,0x000906A3,0x0000042C,20230418 -# I,0x000906A4,0x0000042C,20230418 +# I,0x000906A3,0x00000430,20230607 +# I,0x000906A4,0x00000430,20230607 # I,0x000906C0,0x24000024,20220902 # I,0x000906E9,0x000000F4,20230223 # I,0x000906EA,0x000000F4,20230223 @@ -7040,17 +7171,20 @@ exit 0 # ok # I,0x000A0660,0x000000F8,20230223 # I,0x000A0661,0x000000F8,20230223 # I,0x000A0670,0x0000002C,20201124 -# I,0x000A0671,0x00000059,20230226 +# I,0x000A0671,0x0000005D,20230903 # I,0x000A0680,0x80000002,20200121 +# I,0x000A06A1,0x00000017,20230518 +# I,0x000A06A2,0x00000011,20230627 +# I,0x000A06A4,0x00000017,20231026 # I,0x000B0670,0x0000000E,20220220 -# I,0x000B0671,0x00000119,20230606 -# I,0x000B06A2,0x00004119,20230606 -# I,0x000B06A3,0x00004119,20230606 -# I,0x000B06E0,0x00000011,20230412 -# I,0x000B06F2,0x0000002E,20230418 -# I,0x000B06F5,0x0000002E,20230418 -# I,0x000C06F1,0x21000030,20230410 -# I,0x000C06F2,0x21000030,20230410 +# I,0x000B0671,0x0000011D,20230829 +# I,0x000B06A2,0x0000411C,20230830 +# I,0x000B06A3,0x0000411C,20230830 +# I,0x000B06E0,0x00000012,20230626 +# I,0x000B06F2,0x00000032,20230607 +# I,0x000B06F5,0x00000032,20230607 +# I,0x000C06F1,0x21000200,20231120 +# I,0x000C06F2,0x21000200,20231120 # A,0x00000F00,0x02000008,20070614 # A,0x00000F01,0x0000001C,20021031 # A,0x00000F10,0x00000003,20020325 @@ -7091,7 +7225,7 @@ exit 0 # ok # A,0x00200F31,0x02000057,20080502 # A,0x00200F32,0x02000034,20080307 # A,0x00300F01,0x0300000E,20101004 -# A,0x00300F10,0x03000027,20111309 +# A,0x00300F10,0x03000027,20111209 # A,0x00500F00,0x0500000B,20100601 # A,0x00500F01,0x0500001A,20100908 # A,0x00500F10,0x05000029,20130121 @@ -7110,7 +7244,7 @@ exit 0 # ok # A,0x00610F01,0x0600111F,20180305 # A,0x00630F00,0x0600301C,20130817 # A,0x00630F01,0x06003109,20180227 -# A,0x00660F00,0x06006012,20141014 +# A,0x00660F00,0x06006108,20150302 # A,0x00660F01,0x0600611A,20180126 # A,0x00670F00,0x06006705,20180220 # A,0x00680F00,0x06000017,20101029 @@ -7134,13 +7268,17 @@ exit 0 # ok # A,0x00820F00,0x08200002,20180214 # A,0x00820F01,0x08200103,20190417 # A,0x00830F00,0x08300027,20190401 -# A,0x00830F10,0x0830107A,20230517 +# A,0x00830F10,0x0830107B,20230816 # A,0x00850F00,0x08500004,20180212 # A,0x00860F00,0x0860000E,20200127 # A,0x00860F01,0x08600109,20220328 # A,0x00860F81,0x08608104,20220328 # A,0x00870F00,0x08700004,20181206 # A,0x00870F10,0x08701030,20220328 +# A,0x00890F00,0x08900007,20200921 +# A,0x00890F01,0x08900103,20201105 +# A,0x00890F02,0x08900201,20210114 +# A,0x00890F10,0x08901001,20220309 # A,0x008A0F00,0x08A00008,20230615 # A,0x00A00F00,0x0A000033,20200413 # A,0x00A00F10,0x0A001079,20230609 @@ -7152,18 +7290,26 @@ exit 0 # ok # A,0x00A10F01,0x0A100104,20220207 # A,0x00A10F0B,0x0A100B07,20220610 # A,0x00A10F10,0x0A101020,20220913 -# A,0x00A10F11,0x0A10113E,20230620 -# A,0x00A10F12,0x0A10123E,20230620 +# A,0x00A10F11,0x0A101144,20230906 +# A,0x00A10F12,0x0A101244,20230911 +# A,0x00A10F80,0x0A108005,20230613 +# A,0x00A10F81,0x0A108105,20230711 # A,0x00A20F00,0x0A200025,20200121 -# A,0x00A20F10,0x0A201025,20211014 -# A,0x00A20F12,0x0A20120A,20211014 +# A,0x00A20F10,0x0A20102B,20230707 +# A,0x00A20F12,0x0A20120E,20230707 # A,0x00A40F00,0x0A400016,20210330 # A,0x00A40F40,0x0A404002,20210408 # A,0x00A40F41,0x0A404102,20211018 -# A,0x00A50F00,0x0A50000D,20211014 +# A,0x00A50F00,0x0A50000F,20230707 # A,0x00A60F00,0x0A600005,20211220 -# A,0x00A60F11,0x0A601114,20220712 -# A,0x00A60F12,0x0A601203,20220715 +# A,0x00A60F11,0x0A601119,20230613 +# A,0x00A60F12,0x0A601206,20230613 +# A,0x00A70F00,0x0A700003,20220517 +# A,0x00A70F40,0x0A704001,20220721 +# A,0x00A70F41,0x0A704104,20230713 +# A,0x00A70F42,0x0A704202,20230713 +# A,0x00A70F52,0x0A705203,20230713 +# A,0x00A70F80,0x0A708004,20230713 # A,0x00AA0F00,0x0AA00009,20221006 # A,0x00AA0F01,0x0AA00116,20230619 -# A,0x00AA0F02,0x0AA00212,20230619 +# A,0x00AA0F02,0x0AA00213,20230911