# vim: set ts=4 sw=4 sts=4 et:
# ENTRYPOINT

# we can't do anything useful under WSL
if uname -a | grep -qE -- '-Microsoft #[0-9]+-Microsoft '; then
    pr_warn "This script doesn't work under Windows Subsystem for Linux"
    pr_warn "You should use the official Microsoft tool instead."
    pr_warn "It can be found under https://aka.ms/SpeculationControlPS"
    exit 1
fi

# or other UNIX-ish OSes non-Linux non-supported-BSDs
if [ "$g_os" = Darwin ] || [ "$g_os" = VMkernel ]; then
    pr_warn "You're running under the $g_os OS, but this script"
    pr_warn "only works under Linux and some BSD systems, sorry."
    pr_warn "Please read the README and FAQ for more information."
    exit 1
fi

# check for mode selection inconsistency
if [ "$g_mode" = hw-only ]; then
    if [ "$opt_cve_all" = 0 ]; then
        show_usage
        echo "$0: error: incompatible modes specified, --hw-only vs --variant" >&2
        exit 255
    else
        opt_cve_all=0
        opt_cve_list=''
    fi
fi

# coreos mode
if [ "$opt_coreos" = 1 ]; then
    if ! is_coreos; then
        pr_warn "CoreOS mode asked, but we're not under CoreOS!"
        exit 255
    fi
    pr_warn "CoreOS mode, starting an ephemeral toolbox to launch the script"
    load_msr
    load_cpuid
    mount_debugfs
    toolbox --ephemeral --bind-ro "$CPU_DEV_BASE:$CPU_DEV_BASE" -- sh -c "dnf install -y binutils which && /media/root$PWD/$0 $* --coreos-within-toolbox"
    g_exitcode=$?
    exit $g_exitcode
else
    if is_coreos; then
        pr_warn "You seem to be running CoreOS, you might want to use the --coreos option for better results"
        pr_warn
    fi
fi

# if we're under a BSD, try to mount linprocfs for "$g_procfs/cpuinfo"
g_procfs=/proc
if echo "$g_os" | grep -q BSD; then
    pr_debug "We're under BSD, check if we have g_procfs"
    g_procfs=$(mount | awk '/^linprocfs/ { print $3; exit; }')
    if [ -z "$g_procfs" ]; then
        pr_debug "we don't, try to mount it"
        g_procfs=/proc
        [ -d /compat/linux/proc ] && g_procfs=/compat/linux/proc
        test -d $g_procfs || mkdir $g_procfs
        if mount -t linprocfs linprocfs $g_procfs 2>/dev/null; then
            g_mounted_procfs=1
            pr_debug "g_procfs just mounted at $g_procfs"
        else
            g_procfs=''
        fi
    else
        pr_debug "We do: $g_procfs"
    fi
fi

# define a few vars we might reference later without these being inited
g_mockme=''
g_mocked=0
g_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 "$g_max_core_id" ]; then
    echo "$0: error: --cpu can't be higher than $g_max_core_id, got $opt_cpu" >&2
    exit 255
fi

if has_runtime; then
    pr_info "Checking for vulnerabilities on current system"

    # 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 "$g_kernel_cmdline" | grep -q 'BOOT_IMAGE='; then
        opt_kernel=$(echo "$g_kernel_cmdline" | grep -Eo 'BOOT_IMAGE=[^ ]+' | cut -d= -f2)
        pr_debug "found opt_kernel=$opt_kernel in $g_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"
        pr_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:
        g_str_uname=$(uname -r)
        g_clear_linux_kernel="/lib/kernel/org.clearlinux.${g_str_uname##*.}.${g_str_uname%.*}"
        [ -e "$g_clear_linux_kernel" ] && opt_kernel=$g_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:
        g_custom_arch_kernel=$(echo "$g_kernel_cmdline" | grep -Eo "(^|\s)\\\\[\\\\a-zA-Z0-9_.-]+" | tr "\\\\" "/" | tr -d '[:space:]')
        if [ -n "$g_custom_arch_kernel" ] && [ -e "$g_custom_arch_kernel" ]; then
            opt_kernel="$g_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 "$g_procfs/kallsyms" ]; then
        opt_map="$g_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 "$g_procfs/config.gz" ]; then
        g_dumped_config="$(mktemp -t smc-config-XXXXXX)"
        gunzip -c "$g_procfs/config.gz" >"$g_dumped_config"
        # g_dumped_config will be deleted at the end of the script
        opt_config="$g_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
    pr_info "Checking for vulnerabilities against specified kernel"
fi

if [ -n "$opt_kernel" ]; then
    pr_verbose "Will use kernel image \033[35m$opt_kernel\033[0m"
else
    pr_verbose "Will use no kernel image (accuracy might be reduced)"
    g_bad_accuracy=1
fi

if [ "$g_os" = Linux ]; then
    if [ -n "$opt_config" ] && ! grep -q '^CONFIG_' "$opt_config"; then
        # given file is invalid!
        pr_warn "The kernel config file seems invalid, was expecting a plain-text file, ignoring it!"
        opt_config=''
    fi

    if [ -n "${g_dumped_config:-}" ] && [ -n "$opt_config" ]; then
        pr_verbose "Will use kconfig \033[35m$g_procfs/config.gz (decompressed)\033[0m"
    elif [ -n "$opt_config" ]; then
        pr_verbose "Will use kconfig \033[35m$opt_config\033[0m"
    else
        pr_verbose "Will use no kconfig (accuracy might be reduced)"
        g_bad_accuracy=1
    fi

    if [ -n "$opt_map" ]; then
        pr_verbose "Will use System.map file \033[35m$opt_map\033[0m"
    else
        pr_verbose "Will use no System.map file (accuracy might be reduced)"
        g_bad_accuracy=1
    fi

    : "${g_bad_accuracy:=0}"
fi

if [ -e "$opt_kernel" ]; then
    if ! command -v "${opt_arch_prefix}readelf" >/dev/null 2>&1; then
        pr_debug "readelf not found"
        g_kernel_err="missing '${opt_arch_prefix}readelf' tool, please install it, usually it's in the 'binutils' package"
    elif [ "$opt_sysfs_only" = 1 ] || [ "$g_mode" = hw-only ]; then
        g_kernel_err='kernel image decompression skipped'
    else
        extract_kernel "$opt_kernel"
    fi
else
    pr_debug "no opt_kernel defined"
    g_kernel_err="couldn't find your kernel image in /boot, if you used netboot, this is normal"
fi
if [ -z "$g_kernel" ] || [ ! -r "$g_kernel" ]; then
    [ -z "$g_kernel_err" ] && g_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
    g_kernel_version=$("${opt_arch_prefix}strings" "$g_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 -n1)
    if [ -z "$g_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
        g_kernel_version=$("${opt_arch_prefix}strings" "$g_kernel" 2>/dev/null | grep -E -B1 '^#[0-9]+ .+ (19|20)[0-9][0-9]$' | tr "\n" " ")
    fi
    if [ -n "$g_kernel_version" ]; then
        # in live mode, check if the img we found is the correct one
        if has_runtime; then
            pr_verbose "Kernel image is \033[35m$g_kernel_version"
            if ! echo "$g_kernel_version" | grep -qF "$(uname -r)"; then
                pr_warn "Possible discrepancy between your running kernel '$(uname -r)' and the image '$g_kernel_version' we found ($opt_kernel), results might be incorrect"
            fi
        else
            pr_verbose "Kernel image is \033[35m$g_kernel_version"
        fi
    else
        pr_verbose "Kernel image version is unknown"
    fi
fi

pr_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

# Check a sysfs/procfs file for a vulnerability mitigation status
# Args: $1=file_path $2=regex(optional) $3=mode(optional)
# Sets: ret_sys_interface_check_fullmsg
# Returns: 0 if file matched, 1 otherwise
sys_interface_check() {
    local file regex mode mockvarname
    file="$1"
    regex="${2:-}"
    mode="${3:-}"
    msg=''
    ret_sys_interface_check_fullmsg=''

    if has_runtime && [ "$opt_no_sysfs" = 0 ] && [ -r "$file" ]; then
        :
    else
        g_mockme=$(printf "%b\n%b" "$g_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
        pr_debug "sysfs: MOCKING enabled for $file func returns $(eval echo \$$mockvarname)"
        g_mocked=1
        return "$(eval echo \$$mockvarname)"
    fi

    [ -n "$regex" ] || regex='.*'
    mockvarname="SMC_MOCK_SYSFS_$(basename "$file")"
    # shellcheck disable=SC2086,SC1083
    if [ -n "$(eval echo \${$mockvarname:-})" ]; then
        ret_sys_interface_check_fullmsg="$(eval echo \$$mockvarname)"
        msg=$(echo "$ret_sys_interface_check_fullmsg" | grep -Eo "$regex")
        pr_debug "sysfs: MOCKING enabled for $file, will return $ret_sys_interface_check_fullmsg"
        g_mocked=1
    else
        ret_sys_interface_check_fullmsg=$(cat "$file")
        msg=$(grep -Eo "$regex" "$file")
        g_mockme=$(printf "%b\n%b" "$g_mockme" "SMC_MOCK_SYSFS_$(basename "$file")='$ret_sys_interface_check_fullmsg'")
    fi
    if [ "$mode" = silent ]; then
        # capture sysfs message for JSON even in silent mode
        # shellcheck disable=SC2034
        g_json_cve_sysfs_msg="$ret_sys_interface_check_fullmsg"
        return 0
    elif [ "$mode" = quiet ]; then
        pr_info "* Information from the /sys interface: $ret_sys_interface_check_fullmsg"
        # shellcheck disable=SC2034
        g_json_cve_sysfs_msg="$ret_sys_interface_check_fullmsg"
        return 0
    fi
    pr_info_nol "* Mitigated according to the /sys interface: "
    if echo "$msg" | grep -qi '^not affected'; then
        # Not affected
        ret_sys_interface_check_status=OK
        pstatus green YES "$ret_sys_interface_check_fullmsg"
    elif echo "$msg" | grep -qEi '^(kvm: )?mitigation'; then
        # Mitigation: PTI
        ret_sys_interface_check_status=OK
        pstatus green YES "$ret_sys_interface_check_fullmsg"
    elif echo "$msg" | grep -qi '^vulnerable'; then
        # Vulnerable
        ret_sys_interface_check_status=VULN
        pstatus yellow NO "$ret_sys_interface_check_fullmsg"
    else
        ret_sys_interface_check_status=UNK
        pstatus yellow UNKNOWN "$ret_sys_interface_check_fullmsg"
    fi
    # capture for JSON full output (read by _emit_json_full via pvulnstatus)
    # shellcheck disable=SC2034
    g_json_cve_sysfs_status="$ret_sys_interface_check_status"
    # shellcheck disable=SC2034
    g_json_cve_sysfs_msg="$ret_sys_interface_check_fullmsg"
    pr_debug "sys_interface_check: $file=$msg (re=$regex)"
    return 0
}

# Display kernel image, config, and System.map availability
check_kernel_info() {
    local config_display
    pr_info "\033[1;34mKernel information\033[0m"
    if has_runtime; then
        pr_info "* Kernel is \033[35m$g_os $(uname -r) $(uname -v) $(uname -m)\033[0m"
    elif [ -n "$g_kernel_version" ]; then
        pr_info "* Kernel is \033[35m$g_kernel_version\033[0m"
    else
        pr_info "* Kernel is \033[35munknown\033[0m"
    fi
    if [ -n "$opt_kernel" ] && [ -e "$opt_kernel" ]; then
        pr_info "* Kernel image found at \033[35m$opt_kernel\033[0m"
    else
        pr_info "* Kernel image NOT found"
    fi
    if [ -n "$opt_config" ]; then
        if [ -n "${g_dumped_config:-}" ]; then
            config_display="$g_procfs/config.gz"
        else
            config_display="$opt_config"
        fi
        pr_info "* Kernel config found at \033[35m$config_display\033[0m"
    else
        pr_info "* Kernel config NOT found"
    fi
    if [ -n "$opt_map" ]; then
        pr_info "* Kernel System.map found at \033[35m$opt_map\033[0m"
    else
        pr_info "* Kernel System.map NOT found"
    fi
    if [ "${g_bad_accuracy:-0}" = 1 ]; then
        pr_warn "We're missing some kernel info, accuracy might be reduced"
    fi
}

# Collapse a whitespace-separated list to its unique values, preserving first-seen order.
# Used to prettify the per-core ARM lists for display (e.g. "0x41 0x41 0x41 0x41" -> "0x41").
_uniq_list() {
    echo "$1" | awk '{ for (i = 1; i <= NF; i++) if (!seen[$i]++) printf "%s%s", (n++ ? " " : ""), $i }'
}

# Display hardware-level CPU mitigation support (microcode features, ARCH_CAPABILITIES, etc.)
check_cpu() {
    local capabilities ret spec_ctrl_msr codename ucode_str

    if is_arm_cpu; then
        pr_info "* CPU details"
        pr_info "  * Vendor: $cpu_vendor"
        pr_info "  * Model name: $cpu_friendly_name"
        if [ -n "${cpu_impl_list:-}" ]; then
            pr_info "  * Implementer(s): $(_uniq_list "$cpu_impl_list")"
        fi
        if [ -n "${cpu_part_list:-}" ]; then
            pr_info "  * Part(s): $(_uniq_list "$cpu_part_list")"
        fi
        if [ -n "${cpu_arch_list:-}" ]; then
            pr_info "  * Architecture(s): $(_uniq_list "$cpu_arch_list")"
        fi
        if has_runtime; then
            pr_info_nol "  * Running as VM guest: "
            if is_running_as_guest; then
                pstatus yellow YES "$g_is_guest_vm_reason"
            else
                pstatus green NO
            fi
        fi
        # ARM exposes no userspace-readable CPUID/MSR to query SSBD support directly.
        # The ARMv8.5 SSBS ("Speculative Store Bypass Safe") hardware bit, when present,
        # surfaces as the 'ssbs' hwcap in /proc/cpuinfo. We use it *only* as a positive
        # confirmation of SSB mitigation capability (Variant 4 / CVE-2018-3639): its
        # absence proves nothing, because the kernel deliberately hides the hwcap on some
        # cores (e.g. the erratum-3194386 SSBS self-sync workaround), so we must never
        # infer immunity from a missing 'ssbs'.
        if has_runtime; then
            pr_info_nol "  * CPU indicates SSBS (Speculative Store Bypass Safe) capability: "
            if grep '^Features' "$g_procfs/cpuinfo" | grep -qw ssbs; then
                cap_ssbd='ARM SSBS (cpuinfo)'
                pstatus green YES "$cap_ssbd"
            else
                pstatus blue UNKNOWN "not exposed (the kernel may hide it; cannot conclude)"
            fi
        fi
        return
    fi

    if ! uname -m | grep -qwE 'x86_64|i[3-6]86|amd64'; then
        return
    fi

    pr_info "* CPU details"
    pr_info "  * Vendor: $cpu_vendor"
    pr_info "  * Model name: $cpu_friendly_name"
    pr_info "  * Family: $(printf '0x%02x' "$cpu_family")  Model: $(printf '0x%02x' "$cpu_model")  Stepping: $(printf '0x%02x' "$cpu_stepping")"
    if [ -n "$cpu_ucode" ]; then
        ucode_str=$(printf '0x%x' "$cpu_ucode")
    else
        ucode_str="N/A"
    fi
    pr_info "  * Microcode: $ucode_str"
    pr_info "  * CPUID: $(printf '0x%08x' "$cpu_cpuid")"
    if is_intel; then
        pr_info "  * Platform ID: $(printf '0x%02x' "$cpu_platformid")"
        if [ "$cpu_hybrid" = 1 ]; then
            pr_info "  * Hybrid CPU: YES"
        else
            pr_info "  * Hybrid CPU: NO"
        fi
        codename=$(get_intel_codename)
        if [ -n "$codename" ]; then
            pr_info "  * Codename: $codename"
        fi
    fi

    if has_runtime; then
        pr_info_nol "  * Running as VM guest: "
        if is_running_as_guest; then
            pstatus yellow YES "$g_is_guest_vm_reason"
        else
            pstatus green NO
        fi
    fi

    pr_info "* Hardware support (CPU microcode) for mitigation techniques"
    pr_info "  * Indirect Branch Restricted Speculation (IBRS)"
    pr_info_nol "    * SPEC_CTRL MSR is available: "
    read_msr $MSR_IA32_SPEC_CTRL
    ret=$?
    if [ $ret = $READ_MSR_RET_OK ]; then
        spec_ctrl_msr=1
        pstatus green YES
    elif [ $ret = $READ_MSR_RET_KO ]; then
        spec_ctrl_msr=0
        pstatus yellow NO
    else
        spec_ctrl_msr=-1
        pstatus yellow UNKNOWN "$ret_read_msr_msg"
    fi

    pr_info_nol "    * CPU indicates IBRS capability: "
    # from kernel src: { X86_FEATURE_SPEC_CTRL,        CPUID_EDX,26, 0x00000007, 0 },
    # amd: https://developer.amd.com/wp-content/resources/Architecture_Guidelines_Update_Indirect_Branch_Control.pdf
    # amd: 8000_0008 EBX[14]=1
    cap_ibrs=''
    if is_intel; then
        read_cpuid 0x7 0x0 $EDX 26 1 1
        ret=$?
        if [ $ret = $READ_CPUID_RET_OK ]; then
            pstatus green YES "SPEC_CTRL feature bit"
            cap_spec_ctrl=1
            cap_ibrs='SPEC_CTRL'
        fi
    elif is_amd || is_hygon; then
        read_cpuid 0x80000008 0x0 $EBX 14 1 1
        ret=$?
        if [ $ret = $READ_CPUID_RET_OK ]; then
            pstatus green YES "IBRS_SUPPORT feature bit"
            cap_ibrs='IBRS_SUPPORT'
        fi
    else
        ret=invalid
        pstatus yellow NO "unknown CPU"
    fi
    if [ -z "$cap_ibrs" ] && [ $ret = $READ_CPUID_RET_ERR ] && has_runtime; then
        # CPUID device unavailable (e.g. in a VM): fall back to /proc/cpuinfo
        if cpuinfo_has_flag ibrs; then
            cap_ibrs='IBRS (cpuinfo)'
            cap_spec_ctrl=1
            pstatus green YES "ibrs flag in $g_procfs/cpuinfo"
            ret=$READ_CPUID_RET_OK
        fi
    fi
    if [ $ret = $READ_CPUID_RET_KO ]; then
        pstatus yellow NO
    elif [ $ret = $READ_CPUID_RET_ERR ]; then
        pstatus yellow UNKNOWN "$ret_read_cpuid_msg"
        cap_spec_ctrl=-1
    fi

    if is_amd || is_hygon; then
        pr_info_nol "    * CPU indicates preferring IBRS always-on: "
        # amd or hygon
        read_cpuid 0x80000008 0x0 $EBX 16 1 1
        ret=$?
        if [ $ret = $READ_CPUID_RET_OK ]; then
            pstatus green YES
        elif [ $ret = $READ_CPUID_RET_KO ]; then
            pstatus yellow NO
        else
            pstatus yellow UNKNOWN "$ret_read_cpuid_msg"
        fi

        pr_info_nol "    * CPU indicates preferring IBRS over retpoline: "
        # amd or hygon
        read_cpuid 0x80000008 0x0 $EBX 18 1 1
        ret=$?
        if [ $ret = $READ_CPUID_RET_OK ]; then
            pstatus green YES
        elif [ $ret = $READ_CPUID_RET_KO ]; then
            pstatus yellow NO
        else
            pstatus yellow UNKNOWN "$ret_read_cpuid_msg"
        fi
    fi

    # IBPB
    pr_info "  * Indirect Branch Prediction Barrier (IBPB)"

    if [ "$opt_allow_msr_write" = 1 ]; then
        pr_info_nol "    * PRED_CMD MSR is available: "
        # the new MSR 'PRED_CTRL' is at offset 0x49, write-only
        write_msr 0x49
        ret=$?
        if [ $ret = $WRITE_MSR_RET_OK ]; then
            pstatus green YES
        elif [ $ret = $WRITE_MSR_RET_KO ]; then
            pstatus yellow NO
        else
            pstatus yellow UNKNOWN "$ret_write_msr_msg"
        fi
    fi

    pr_info_nol "    * CPU indicates IBPB capability: "
    # CPUID EAX=0x80000008, ECX=0x00 return EBX[12] indicates support for just IBPB.
    if [ "$cap_spec_ctrl" = 1 ]; then
        # spec_ctrl implies ibpb
        cap_ibpb='SPEC_CTRL'
        pstatus green YES "SPEC_CTRL feature bit"
    elif is_intel; then
        if [ "$cap_spec_ctrl" = -1 ]; then
            pstatus yellow UNKNOWN "is cpuid kernel module available?"
        else
            pstatus yellow NO
        fi
    elif is_amd || is_hygon; then
        read_cpuid 0x80000008 0x0 $EBX 12 1 1
        ret=$?
        if [ $ret = $READ_CPUID_RET_OK ]; then
            cap_ibpb='IBPB_SUPPORT'
            pstatus green YES "IBPB_SUPPORT feature bit"
        elif [ $ret = $READ_CPUID_RET_ERR ] && has_runtime && cpuinfo_has_flag ibpb; then
            # CPUID device unavailable (e.g. in a VM): fall back to /proc/cpuinfo
            cap_ibpb='IBPB (cpuinfo)'
            pstatus green YES "ibpb flag in $g_procfs/cpuinfo"
        elif [ $ret = $READ_CPUID_RET_KO ]; then
            pstatus yellow NO
        else
            pstatus yellow UNKNOWN "$ret_read_cpuid_msg"
        fi
    fi

    # IBPB_RET: CPUID EAX=0x80000008, ECX=0x00 return EBX[30] indicates IBPB also flushes
    # return predictions (Zen4+). Without this bit, IBPB alone does not clear the return
    # predictor, requiring an additional RSB fill (kernel X86_BUG_IBPB_NO_RET fix).
    cap_ibpb_ret=''
    if is_amd || is_hygon; then
        pr_info_nol "    * CPU indicates IBPB flushes return predictions: "
        read_cpuid 0x80000008 0x0 $EBX 30 1 1
        ret=$?
        if [ $ret = $READ_CPUID_RET_OK ]; then
            cap_ibpb_ret=1
            pstatus green YES "IBPB_RET feature bit"
        elif [ $ret = $READ_CPUID_RET_KO ]; then
            cap_ibpb_ret=0
            pstatus yellow NO
        else
            cap_ibpb_ret=-1
            pstatus yellow UNKNOWN "$ret_read_cpuid_msg"
        fi
    fi

    # STIBP
    pr_info "  * Single Thread Indirect Branch Predictors (STIBP)"
    pr_info_nol "    * SPEC_CTRL MSR is available: "
    if [ "$spec_ctrl_msr" = 1 ]; then
        pstatus green YES
    elif [ "$spec_ctrl_msr" = 0 ]; then
        pstatus yellow NO
    else
        pstatus yellow UNKNOWN "$ret_read_msr_msg"
    fi

    pr_info_nol "    * CPU indicates STIBP capability: "
    # intel: A processor supports STIBP if it enumerates CPUID (EAX=7H,ECX=0):EDX[27] as 1
    # amd: 8000_0008 EBX[15]=1
    cap_stibp=''
    if is_intel; then
        read_cpuid 0x7 0x0 $EDX 27 1 1
        ret=$?
        if [ $ret = $READ_CPUID_RET_OK ]; then
            pstatus green YES "Intel STIBP feature bit"
            cap_stibp='Intel STIBP'
        fi
    elif is_amd; then
        read_cpuid 0x80000008 0x0 $EBX 15 1 1
        ret=$?
        if [ $ret = $READ_CPUID_RET_OK ]; then
            pstatus green YES "AMD STIBP feature bit"
            cap_stibp='AMD STIBP'
        fi
    elif is_hygon; then
        read_cpuid 0x80000008 0x0 $EBX 15 1 1
        ret=$?
        if [ $ret = $READ_CPUID_RET_OK ]; then
            pstatus green YES "HYGON STIBP feature bit"
            cap_stibp='HYGON STIBP'
        fi
    else
        ret=invalid
        pstatus yellow UNKNOWN "unknown CPU"
    fi
    if [ -z "$cap_stibp" ] && [ $ret = $READ_CPUID_RET_ERR ] && has_runtime; then
        # CPUID device unavailable (e.g. in a VM): fall back to /proc/cpuinfo
        if cpuinfo_has_flag stibp; then
            cap_stibp='STIBP (cpuinfo)'
            pstatus green YES "stibp flag in $g_procfs/cpuinfo"
            ret=$READ_CPUID_RET_OK
        fi
    fi
    if [ $ret = $READ_CPUID_RET_KO ]; then
        pstatus yellow NO
    elif [ $ret = $READ_CPUID_RET_ERR ]; then
        pstatus yellow UNKNOWN "$ret_read_cpuid_msg"
    fi

    if is_amd || is_hygon; then
        pr_info_nol "    * CPU indicates preferring STIBP always-on: "
        read_cpuid 0x80000008 0x0 $EBX 17 1 1
        ret=$?
        if [ $ret = $READ_CPUID_RET_OK ]; then
            pstatus green YES
        elif [ $ret = $READ_CPUID_RET_KO ]; then
            pstatus yellow NO
        else
            pstatus yellow UNKNOWN "$ret_read_cpuid_msg"
        fi
    fi

    # variant 4
    if is_intel; then
        pr_info "  * Speculative Store Bypass Disable (SSBD)"
        pr_info_nol "    * CPU indicates SSBD capability: "
        read_cpuid 0x7 0x0 $EDX 31 1 1
        ret24=$?
        ret25=$ret24
        if [ $ret24 = $READ_CPUID_RET_OK ]; then
            cap_ssbd='Intel SSBD'
        fi
    elif is_amd; then
        pr_info "  * Speculative Store Bypass Disable (SSBD)"
        pr_info_nol "    * CPU indicates SSBD capability: "
        read_cpuid 0x80000008 0x0 $EBX 24 1 1
        ret24=$?
        read_cpuid 0x80000008 0x0 $EBX 25 1 1
        ret25=$?
        if [ $ret24 = $READ_CPUID_RET_OK ]; then
            cap_ssbd='AMD SSBD in SPEC_CTRL'
            #cpuid_ssbd_spec_ctrl=1
        elif [ $ret25 = $READ_CPUID_RET_OK ]; then
            cap_ssbd='AMD SSBD in VIRT_SPEC_CTRL'
            #cpuid_ssbd_virt_spec_ctrl=1
        elif [ "$cpu_family" -ge 21 ] && [ "$cpu_family" -le 23 ]; then
            cap_ssbd='AMD non-architectural MSR'
        fi
    elif is_hygon; then
        pr_info "  * Speculative Store Bypass Disable (SSBD)"
        pr_info_nol "    * CPU indicates SSBD capability: "
        read_cpuid 0x80000008 0x0 $EBX 24 1 1
        ret24=$?
        read_cpuid 0x80000008 0x0 $EBX 25 1 1
        ret25=$?

        if [ $ret24 = $READ_CPUID_RET_OK ]; then
            cap_ssbd='HYGON SSBD in SPEC_CTRL'
            #hygon cpuid_ssbd_spec_ctrl=1
        elif [ $ret25 = $READ_CPUID_RET_OK ]; then
            cap_ssbd='HYGON SSBD in VIRT_SPEC_CTRL'
            #hygon cpuid_ssbd_virt_spec_ctrl=1
        elif [ "$cpu_family" -ge 24 ]; then
            cap_ssbd='HYGON non-architectural MSR'
        fi
    fi

    if [ -z "$cap_ssbd" ] && [ "$ret24" = $READ_CPUID_RET_ERR ] && [ "$ret25" = $READ_CPUID_RET_ERR ] && has_runtime; then
        # CPUID device unavailable (e.g. in a VM): fall back to /proc/cpuinfo
        if cpuinfo_has_flag ssbd; then
            cap_ssbd='SSBD (cpuinfo)'
        elif cpuinfo_has_flag virt_ssbd; then
            cap_ssbd='SSBD in VIRT_SPEC_CTRL (cpuinfo)'
        fi
    fi

    if [ -n "${cap_ssbd:=}" ]; then
        pstatus green YES "$cap_ssbd"
    elif [ "$ret24" = $READ_CPUID_RET_ERR ] && [ "$ret25" = $READ_CPUID_RET_ERR ]; then
        pstatus yellow UNKNOWN "$ret_read_cpuid_msg"
    else
        pstatus yellow NO
    fi

    cap_amd_ssb_no=0
    cap_hygon_ssb_no=0
    if is_amd; then
        # similar to SSB_NO for intel
        read_cpuid 0x80000008 0x0 $EBX 26 1 1
        ret=$?
        if [ $ret = $READ_CPUID_RET_OK ]; then
            cap_amd_ssb_no=1
        elif [ $ret = $READ_CPUID_RET_ERR ]; then
            cap_amd_ssb_no=-1
        fi
    elif is_hygon; then
        # indicate when speculative store bypass disable is no longer needed to prevent speculative loads bypassing older stores
        read_cpuid 0x80000008 0x0 $EBX 26 1 1
        ret=$?
        if [ $ret = $READ_CPUID_RET_OK ]; then
            cap_hygon_ssb_no=1
        elif [ $ret = $READ_CPUID_RET_ERR ]; then
            cap_hygon_ssb_no=-1
        fi
    fi

    pr_info "  * L1 data cache invalidation"

    if [ "$opt_allow_msr_write" = 1 ]; then
        pr_info_nol "    * FLUSH_CMD MSR is available: "
        # the new MSR 'FLUSH_CMD' is at offset 0x10b, write-only
        # this is probed for informational purposes only, the CPUID L1D flush bit
        # (cap_l1df) is the authoritative indicator per Intel guidance
        write_msr 0x10b
        ret=$?
        if [ $ret = $WRITE_MSR_RET_OK ]; then
            pstatus green YES
        elif [ $ret = $WRITE_MSR_RET_KO ]; then
            pstatus yellow NO
        else
            pstatus yellow UNKNOWN "$ret_write_msr_msg"
        fi
    fi

    # CPUID of L1D
    pr_info_nol "    * CPU indicates L1D flush capability: "
    read_cpuid 0x7 0x0 $EDX 28 1 1
    ret=$?
    if [ $ret = $READ_CPUID_RET_OK ]; then
        pstatus green YES "L1D flush feature bit"
        cap_l1df=1
    elif [ $ret = $READ_CPUID_RET_ERR ] && has_runtime && cpuinfo_has_flag flush_l1d; then
        # CPUID device unavailable (e.g. in a VM): fall back to /proc/cpuinfo
        pstatus green YES "flush_l1d flag in $g_procfs/cpuinfo"
        cap_l1df=1
    elif [ $ret = $READ_CPUID_RET_KO ]; then
        pstatus yellow NO
        cap_l1df=0
    else
        pstatus yellow UNKNOWN "$ret_read_cpuid_msg"
        cap_l1df=-1
    fi

    if is_intel; then
        pr_info "  * Microarchitectural Data Sampling"
        pr_info_nol "    * VERW instruction is available: "
        read_cpuid 0x7 0x0 $EDX 10 1 1
        ret=$?
        if [ $ret = $READ_CPUID_RET_OK ]; then
            cap_md_clear=1
            pstatus green YES "MD_CLEAR feature bit"
        elif [ $ret = $READ_CPUID_RET_ERR ] && has_runtime && cpuinfo_has_flag md_clear; then
            # CPUID device unavailable (e.g. in a VM): fall back to /proc/cpuinfo
            cap_md_clear=1
            pstatus green YES "md_clear flag in $g_procfs/cpuinfo"
        elif [ $ret = $READ_CPUID_RET_KO ]; then
            cap_md_clear=0
            pstatus yellow NO
        else
            cap_md_clear=-1
            pstatus yellow UNKNOWN "$ret_read_cpuid_msg"
        fi
    fi

    if is_intel; then
        pr_info "  * Indirect Branch Predictor Controls"
        pr_info_nol "    * Indirect Predictor Disable feature is available: "
        read_cpuid 0x7 0x2 $EDX 1 1 1
        ret=$?
        # cap_ipred is not yet used in verdict logic (no kernel sysfs/config to cross-reference)
        # shellcheck disable=SC2034
        if [ $ret = $READ_CPUID_RET_OK ]; then
            cap_ipred=1
            pstatus green YES "IPRED_CTRL feature bit"
        elif [ $ret = $READ_CPUID_RET_KO ]; then
            cap_ipred=0
            pstatus yellow NO
        else
            cap_ipred=-1
            pstatus yellow UNKNOWN "$ret_read_cpuid_msg"
        fi

        pr_info_nol "    * Bottomless RSB Disable feature is available: "
        read_cpuid 0x7 0x2 $EDX 2 1 1
        ret=$?
        if [ $ret = $READ_CPUID_RET_OK ]; then
            cap_rrsba=1
            pstatus green YES "RRSBA_CTRL feature bit"
        elif [ $ret = $READ_CPUID_RET_KO ]; then
            cap_rrsba=0
            pstatus yellow NO
        else
            cap_rrsba=-1
            pstatus yellow UNKNOWN "$ret_read_cpuid_msg"
        fi

        pr_info_nol "    * BHB-Focused Indirect Predictor Disable feature is available: "
        read_cpuid 0x7 0x2 $EDX 2 1 1
        ret=$?
        if [ $ret = $READ_CPUID_RET_OK ]; then
            cap_bhi=1
            pstatus green YES "BHI_CTRL feature bit"
        elif [ $ret = $READ_CPUID_RET_KO ]; then
            cap_bhi=0
            pstatus yellow NO
        else
            cap_bhi=-1
            pstatus yellow UNKNOWN "$ret_read_cpuid_msg"
        fi
    fi

    if is_intel; then
        pr_info "  * Enhanced IBRS (IBRS_ALL)"
        pr_info_nol "    * CPU indicates ARCH_CAPABILITIES MSR availability: "
        cap_arch_capabilities=-1
        # A processor supports the ARCH_CAPABILITIES MSR if it enumerates CPUID (EAX=7H,ECX=0):EDX[29] as 1
        read_cpuid 0x7 0x0 $EDX 29 1 1
        ret=$?
        if [ $ret = $READ_CPUID_RET_OK ]; then
            pstatus green YES
            cap_arch_capabilities=1
        elif [ $ret = $READ_CPUID_RET_ERR ] && has_runtime && cpuinfo_has_flag arch_capabilities; then
            # CPUID device unavailable (e.g. in a VM): fall back to /proc/cpuinfo
            pstatus green YES "arch_capabilities flag in $g_procfs/cpuinfo"
            cap_arch_capabilities=1
        elif [ $ret = $READ_CPUID_RET_KO ]; then
            pstatus yellow NO
            cap_arch_capabilities=0
        else
            pstatus yellow UNKNOWN "$ret_read_cpuid_msg"
        fi

        pr_info_nol "    * ARCH_CAPABILITIES MSR advertises IBRS_ALL capability: "
        cap_taa_no=-1
        cap_mds_no=-1
        cap_rdcl_no=-1
        cap_ibrs_all=-1
        cap_rsba=-1
        cap_l1dflush_no=-1
        cap_ssb_no=-1
        cap_pschange_msc_no=-1
        cap_tsx_ctrl_msr=-1
        cap_gds_ctrl=-1
        cap_gds_no=-1
        cap_rfds_no=-1
        cap_rfds_clear=-1
        cap_its_no=-1
        cap_sbdr_ssdp_no=-1
        cap_fbsdp_no=-1
        cap_psdp_no=-1
        cap_fb_clear=-1
        if [ "$cap_arch_capabilities" = -1 ]; then
            pstatus yellow UNKNOWN
        elif [ "$cap_arch_capabilities" != 1 ]; then
            cap_rdcl_no=0
            cap_taa_no=0
            cap_mds_no=0
            cap_ibrs_all=0
            cap_rsba=0
            cap_l1dflush_no=0
            cap_ssb_no=0
            cap_pschange_msc_no=0
            cap_tsx_ctrl_msr=0
            cap_gds_ctrl=0
            cap_gds_no=0
            cap_rfds_no=0
            cap_rfds_clear=0
            cap_its_no=0
            cap_sbdr_ssdp_no=0
            cap_fbsdp_no=0
            cap_psdp_no=0
            cap_fb_clear=0
            pstatus yellow NO
        else
            read_msr $MSR_IA32_ARCH_CAPABILITIES
            ret=$?
            cap_rdcl_no=0
            cap_taa_no=0
            cap_mds_no=0
            cap_ibrs_all=0
            cap_rsba=0
            cap_l1dflush_no=0
            cap_ssb_no=0
            cap_pschange_msc_no=0
            cap_tsx_ctrl_msr=0
            cap_gds_ctrl=0
            cap_gds_no=0
            cap_rfds_no=0
            cap_rfds_clear=0
            cap_its_no=0
            cap_sbdr_ssdp_no=0
            cap_fbsdp_no=0
            cap_psdp_no=0
            cap_fb_clear=0
            if [ $ret = $READ_MSR_RET_OK ]; then
                capabilities=$ret_read_msr_value
                # https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/arch/x86/include/asm/msr-index.h#n82
                pr_debug "capabilities MSR is $capabilities (hex)"
                [ $((ret_read_msr_value_lo >> 0 & 1)) -eq 1 ] && cap_rdcl_no=1
                [ $((ret_read_msr_value_lo >> 1 & 1)) -eq 1 ] && cap_ibrs_all=1
                [ $((ret_read_msr_value_lo >> 2 & 1)) -eq 1 ] && cap_rsba=1
                [ $((ret_read_msr_value_lo >> 3 & 1)) -eq 1 ] && cap_l1dflush_no=1
                [ $((ret_read_msr_value_lo >> 4 & 1)) -eq 1 ] && cap_ssb_no=1
                [ $((ret_read_msr_value_lo >> 5 & 1)) -eq 1 ] && cap_mds_no=1
                [ $((ret_read_msr_value_lo >> 6 & 1)) -eq 1 ] && cap_pschange_msc_no=1
                [ $((ret_read_msr_value_lo >> 7 & 1)) -eq 1 ] && cap_tsx_ctrl_msr=1
                [ $((ret_read_msr_value_lo >> 8 & 1)) -eq 1 ] && cap_taa_no=1
                [ $((ret_read_msr_value_lo >> 13 & 1)) -eq 1 ] && cap_sbdr_ssdp_no=1
                [ $((ret_read_msr_value_lo >> 14 & 1)) -eq 1 ] && cap_fbsdp_no=1
                [ $((ret_read_msr_value_lo >> 15 & 1)) -eq 1 ] && cap_psdp_no=1
                [ $((ret_read_msr_value_lo >> 17 & 1)) -eq 1 ] && cap_fb_clear=1
                [ $((ret_read_msr_value_lo >> 25 & 1)) -eq 1 ] && cap_gds_ctrl=1
                [ $((ret_read_msr_value_lo >> 26 & 1)) -eq 1 ] && cap_gds_no=1
                [ $((ret_read_msr_value_lo >> 27 & 1)) -eq 1 ] && cap_rfds_no=1
                [ $((ret_read_msr_value_lo >> 28 & 1)) -eq 1 ] && cap_rfds_clear=1
                [ $((ret_read_msr_value_hi >> 30 & 1)) -eq 1 ] && cap_its_no=1
                pr_debug "capabilities says rdcl_no=$cap_rdcl_no ibrs_all=$cap_ibrs_all rsba=$cap_rsba l1dflush_no=$cap_l1dflush_no ssb_no=$cap_ssb_no mds_no=$cap_mds_no taa_no=$cap_taa_no pschange_msc_no=$cap_pschange_msc_no rfds_no=$cap_rfds_no rfds_clear=$cap_rfds_clear its_no=$cap_its_no sbdr_ssdp_no=$cap_sbdr_ssdp_no fbsdp_no=$cap_fbsdp_no psdp_no=$cap_psdp_no fb_clear=$cap_fb_clear"
                if [ "$cap_ibrs_all" = 1 ]; then
                    pstatus green YES
                else
                    pstatus yellow NO
                fi
            elif [ $ret = $READ_MSR_RET_KO ]; then
                # the MSR access faulted: the register is genuinely absent, so the
                # pre-seeded 0 ("not advertised") values are correct.
                pstatus yellow NO
            else
                # RET_ERR (no msr module) or RET_LOCKDOWN (MSR reads restricted):
                # CPUID told us the MSR exists but we couldn't read it, so its bits
                # are undetermined, not 0. Leaving them at 0 would falsely claim the
                # CPU "explicitly indicates not immune".
                # Reset every arch-cap-derived value to -1 (UNKNOWN) instead.
                cap_rdcl_no=-1
                cap_taa_no=-1
                cap_mds_no=-1
                cap_ibrs_all=-1
                cap_rsba=-1
                cap_l1dflush_no=-1
                cap_ssb_no=-1
                cap_pschange_msc_no=-1
                cap_tsx_ctrl_msr=-1
                cap_gds_ctrl=-1
                cap_gds_no=-1
                cap_rfds_no=-1
                cap_rfds_clear=-1
                cap_its_no=-1
                cap_sbdr_ssdp_no=-1
                cap_fbsdp_no=-1
                cap_psdp_no=-1
                cap_fb_clear=-1
                pstatus yellow UNKNOWN "$ret_read_msr_msg"
            fi
        fi

        pr_info_nol "  * CPU explicitly indicates not being affected by Meltdown/L1TF (RDCL_NO): "
        if [ "$cap_rdcl_no" = -1 ]; then
            pstatus yellow UNKNOWN
        elif [ "$cap_rdcl_no" = 1 ]; then
            pstatus green YES
        else
            pstatus yellow NO
        fi

        pr_info_nol "  * CPU explicitly indicates not being affected by Variant 4 (SSB_NO): "
        if [ "$cap_ssb_no" = -1 ]; then
            pstatus yellow UNKNOWN
        elif [ "$cap_ssb_no" = 1 ] || [ "$cap_amd_ssb_no" = 1 ] || [ "$cap_hygon_ssb_no" = 1 ]; then
            pstatus green YES
        else
            pstatus yellow NO
        fi

        pr_info_nol "  * CPU/Hypervisor indicates L1D flushing is not necessary on this system: "
        if [ "$cap_l1dflush_no" = -1 ]; then
            pstatus yellow UNKNOWN
        elif [ "$cap_l1dflush_no" = 1 ]; then
            pstatus green YES
        else
            pstatus yellow NO
        fi

        pr_info_nol "  * Hypervisor indicates host CPU might be affected by RSB underflow (RSBA): "
        if [ "$cap_rsba" = -1 ]; then
            pstatus yellow UNKNOWN
        elif [ "$cap_rsba" = 1 ]; then
            pstatus yellow YES
        else
            pstatus blue NO
        fi

        pr_info_nol "  * CPU explicitly indicates not being affected by Microarchitectural Data Sampling (MDS_NO): "
        if [ "$cap_mds_no" = -1 ]; then
            pstatus yellow UNKNOWN
        elif [ "$cap_mds_no" = 1 ]; then
            pstatus green YES
        else
            pstatus yellow NO
        fi

        pr_info_nol "  * CPU explicitly indicates not being affected by TSX Asynchronous Abort (TAA_NO): "
        if [ "$cap_taa_no" = -1 ]; then
            pstatus yellow UNKNOWN
        elif [ "$cap_taa_no" = 1 ]; then
            pstatus green YES
        else
            pstatus yellow NO
        fi

        pr_info_nol "  * CPU explicitly indicates not being affected by iTLB Multihit (PSCHANGE_MSC_NO): "
        if [ "$cap_pschange_msc_no" = -1 ]; then
            pstatus yellow UNKNOWN
        elif [ "$cap_pschange_msc_no" = 1 ]; then
            pstatus green YES
        else
            pstatus yellow NO
        fi

        pr_info_nol "  * CPU explicitly indicates having MSR for TSX control (TSX_CTRL_MSR): "
        if [ "$cap_tsx_ctrl_msr" = -1 ]; then
            pstatus yellow UNKNOWN
        elif [ "$cap_tsx_ctrl_msr" = 1 ]; then
            pstatus green YES
        else
            pstatus yellow NO
        fi

        # IA32_TSX_CTRL (MSR 0x122): architectural way to disable TSX, available on
        # Cascade Lake and newer, and some Coffee Lake steppings via microcode update
        if [ "$cap_tsx_ctrl_msr" = 1 ]; then
            read_msr $MSR_IA32_TSX_CTRL
            ret=$?
            if [ "$ret" = $READ_MSR_RET_OK ]; then
                cap_tsx_ctrl_rtm_disable=$((ret_read_msr_value_lo >> 0 & 1))
                cap_tsx_ctrl_cpuid_clear=$((ret_read_msr_value_lo >> 1 & 1))
            fi

            pr_info_nol "    * TSX_CTRL MSR indicates TSX RTM is disabled: "
            if [ "$cap_tsx_ctrl_rtm_disable" = 1 ]; then
                pstatus blue YES
            elif [ "$cap_tsx_ctrl_rtm_disable" = 0 ]; then
                pstatus blue NO
            else
                # shellcheck disable=SC2154
                pstatus yellow UNKNOWN "$ret_read_msr_0x122_msg"
            fi

            pr_info_nol "    * TSX_CTRL MSR indicates TSX CPUID bit is cleared: "
            if [ "$cap_tsx_ctrl_cpuid_clear" = 1 ]; then
                pstatus blue YES
            elif [ "$cap_tsx_ctrl_cpuid_clear" = 0 ]; then
                pstatus blue NO
            else
                # shellcheck disable=SC2154
                pstatus yellow UNKNOWN "$ret_read_msr_0x122_msg"
            fi
        fi

        pr_info_nol "  * CPU explicitly indicates being affected by GDS and having mitigation control (GDS_CTRL): "
        if [ "$cap_gds_ctrl" = -1 ]; then
            pstatus yellow UNKNOWN "couldn't read MSR"
        elif [ "$cap_gds_ctrl" = 1 ]; then
            pstatus green YES
        else
            pstatus blue NO
        fi

        cap_gds_mitg_dis=-1
        cap_gds_mitg_lock=-1
        if [ "$cap_gds_ctrl" = 1 ]; then
            read_msr $MSR_IA32_MCU_OPT_CTRL
            ret=$?
            if [ "$ret" = $READ_MSR_RET_OK ]; then
                cap_gds_mitg_dis=$((ret_read_msr_value_lo >> 4 & 1))
                cap_gds_mitg_lock=$((ret_read_msr_value_lo >> 5 & 1))
            fi

            pr_info_nol "    * GDS microcode mitigation is disabled (GDS_MITG_DIS): "
            if [ "$cap_gds_mitg_dis" = -1 ]; then
                # shellcheck disable=SC2154
                pstatus yellow UNKNOWN "$ret_read_msr_0x123_msg"
            elif [ "$cap_gds_mitg_dis" = 1 ]; then
                pstatus yellow YES
            else
                pstatus green NO
            fi

            pr_info_nol "    * GDS microcode mitigation is locked in enabled state (GDS_MITG_LOCK): "
            if [ "$cap_gds_mitg_lock" = -1 ]; then
                # shellcheck disable=SC2154
                pstatus yellow UNKNOWN "$ret_read_msr_0x123_msg"
            elif [ "$cap_gds_mitg_lock" = 1 ]; then
                pstatus blue YES
            else
                pstatus blue NO
            fi
        fi

        pr_info_nol "  * CPU explicitly indicates not being affected by GDS (GDS_NO): "
        if [ "$cap_gds_no" = -1 ]; then
            pstatus yellow UNKNOWN "couldn't read MSR"
        elif [ "$cap_gds_no" = 1 ]; then
            pstatus green YES
        else
            pstatus yellow NO
        fi

        pr_info_nol "  * CPU explicitly indicates not being affected by MMIO Stale Data (FBSDP_NO & PSDP_NO & SBDR_SSDP_NO): "
        if [ "$cap_sbdr_ssdp_no" = -1 ]; then
            pstatus yellow UNKNOWN "couldn't read MSR"
        elif is_arch_cap_mmio_immune; then
            pstatus green YES
        else
            pstatus yellow NO
        fi

        pr_info_nol "  * CPU microcode supports Fill Buffer clearing (FB_CLEAR): "
        if [ "$cap_fb_clear" = -1 ]; then
            pstatus yellow UNKNOWN "couldn't read MSR"
        elif [ "$cap_fb_clear" = 1 ]; then
            pstatus green YES
        else
            pstatus yellow NO
        fi

        pr_info_nol "  * CPU explicitly indicates not being affected by RFDS (RFDS_NO): "
        if [ "$cap_rfds_no" = -1 ]; then
            pstatus yellow UNKNOWN "couldn't read MSR"
        elif [ "$cap_rfds_no" = 1 ]; then
            pstatus green YES
        else
            pstatus yellow NO
        fi

        pr_info_nol "  * CPU microcode supports clearing register files (RFDS_CLEAR): "
        if [ "$cap_rfds_clear" = -1 ]; then
            pstatus yellow UNKNOWN "couldn't read MSR"
        elif [ "$cap_rfds_clear" = 1 ]; then
            pstatus green YES
        else
            pstatus yellow NO
        fi

    fi

    if is_amd || is_hygon; then
        pr_info "  * Selective Branch Predictor Barrier (SBPB)"
        pr_info_nol "    * PRED_CMD MSR supports SBPB bit write: "

        if [ "$opt_allow_msr_write" = 1 ]; then
            # the MSR PRED_SBPB is at offset 0x49, BIT(7), write-only
            write_msr 0x49 128
            ret=$?
            if [ $ret = $WRITE_MSR_RET_OK ]; then
                pstatus green YES
                cap_sbpb=1
            elif [ $ret = $WRITE_MSR_RET_KO ]; then
                pstatus yellow NO
                cap_sbpb=2
            else
                pstatus yellow UNKNOWN "$ret_write_msr_msg"
                cap_sbpb=3
            fi
        else
            pstatus yellow UNKNOWN "not allowed to write msr"
            cap_sbpb=3
        fi
    fi

    if is_amd || is_hygon; then
        pr_info "  * Transient Scheduler Attacks"
        pr_info_nol "    * CPU indicates TSA_SQ_NO: "
        cap_tsa_sq_no=''
        read_cpuid 0x80000021 0x0 $ECX 1 1 1
        ret=$?
        if [ $ret = $READ_CPUID_RET_OK ]; then
            pstatus green YES
            cap_tsa_sq_no=1
        elif [ $ret = $READ_CPUID_RET_KO ]; then
            pstatus yellow NO
            cap_tsa_sq_no=0
        else
            pstatus yellow UNKNOWN "$ret_read_cpuid_msg"
        fi

        pr_info_nol "    * CPU indicates TSA_L1_NO: "
        cap_tsa_l1_no=''
        read_cpuid 0x80000021 0x0 $ECX 2 1 1
        ret=$?
        if [ $ret = $READ_CPUID_RET_OK ]; then
            pstatus green YES
            cap_tsa_l1_no=1
        elif [ $ret = $READ_CPUID_RET_KO ]; then
            pstatus yellow NO
            cap_tsa_l1_no=0
        else
            pstatus yellow UNKNOWN "$ret_read_cpuid_msg"
        fi

        pr_info_nol "    * CPU indicates VERW clears CPU buffers: "
        cap_verw_clear=''
        read_cpuid 0x80000021 0x0 $EAX 5 1 1
        ret=$?
        if [ $ret = $READ_CPUID_RET_OK ]; then
            pstatus green YES
            cap_verw_clear=1
        elif [ $ret = $READ_CPUID_RET_KO ]; then
            pstatus yellow NO
            cap_verw_clear=0
        else
            pstatus yellow UNKNOWN "$ret_read_cpuid_msg"
        fi

        pr_info_nol "    * CPU indicates AutoIBRS capability: "
        cap_autoibrs=''
        read_cpuid 0x80000021 0x0 $EAX 8 1 1
        ret=$?
        if [ $ret = $READ_CPUID_RET_OK ]; then
            pstatus green YES
            cap_autoibrs=1
        elif [ $ret = $READ_CPUID_RET_KO ]; then
            pstatus yellow NO
            cap_autoibrs=0
        else
            pstatus yellow UNKNOWN "$ret_read_cpuid_msg"
        fi
    fi

    pr_info_nol "  * CPU supports Transactional Synchronization Extensions (TSX): "
    ret=$READ_CPUID_RET_KO
    cap_rtm=0
    if is_intel; then
        read_cpuid 0x7 0x0 $EBX 11 1 1
        ret=$?
    fi
    if [ $ret = $READ_CPUID_RET_OK ]; then
        cap_rtm=1
        pstatus green YES "RTM feature bit"
    elif [ $ret = $READ_CPUID_RET_KO ]; then
        pstatus yellow NO
    else
        cap_rtm=-1
        pstatus yellow UNKNOWN "$ret_read_cpuid_msg"
    fi

    pr_info_nol "  * CPU supports TSX Force Abort (TSX_FORCE_ABORT): "
    ret=$READ_CPUID_RET_KO
    cap_tsx_force_abort=0
    if is_intel; then
        read_cpuid 0x7 0x0 $EDX 13 1 1
        ret=$?
    fi
    if [ $ret = $READ_CPUID_RET_OK ]; then
        cap_tsx_force_abort=1
        pstatus blue YES
    elif [ $ret = $READ_CPUID_RET_KO ]; then
        pstatus yellow NO
    else
        cap_tsx_force_abort=-1
        pstatus yellow UNKNOWN "$ret_read_cpuid_msg"
    fi

    # IA32_TSX_FORCE_ABORT (MSR 0x10F): stopgap for older Skylake/Kaby Lake CPUs that
    # don't support IA32_TSX_CTRL, forces all RTM transactions to abort via microcode update
    if [ "$cap_tsx_force_abort" = 1 ]; then
        read_msr $MSR_IA32_TSX_FORCE_ABORT
        ret=$?
        if [ "$ret" = $READ_MSR_RET_OK ]; then
            cap_tsx_force_abort_rtm_disable=$((ret_read_msr_value_lo >> 0 & 1))
            cap_tsx_force_abort_cpuid_clear=$((ret_read_msr_value_lo >> 1 & 1))
        fi

        pr_info_nol "    * TSX_FORCE_ABORT MSR indicates all TSX transactions are aborted: "
        if [ "$cap_tsx_force_abort_rtm_disable" = 1 ]; then
            pstatus blue YES
        elif [ "$cap_tsx_force_abort_rtm_disable" = 0 ]; then
            pstatus blue NO
        else
            # shellcheck disable=SC2154
            pstatus yellow UNKNOWN "$ret_read_msr_0x10f_msg"
        fi

        pr_info_nol "    * TSX_FORCE_ABORT MSR indicates TSX CPUID bit is cleared: "
        if [ "$cap_tsx_force_abort_cpuid_clear" = 1 ]; then
            pstatus blue YES
        elif [ "$cap_tsx_force_abort_cpuid_clear" = 0 ]; then
            pstatus blue NO
        else
            # shellcheck disable=SC2154
            pstatus yellow UNKNOWN "$ret_read_msr_0x10f_msg"
        fi
    fi

    pr_info_nol "  * CPU supports Software Guard Extensions (SGX): "
    ret=$READ_CPUID_RET_KO
    cap_sgx=0
    if is_intel; then
        read_cpuid 0x7 0x0 $EBX 2 1 1
        ret=$?
    fi
    if [ $ret = $READ_CPUID_RET_OK ]; then
        pstatus blue YES
        cap_sgx=1
    elif [ $ret = $READ_CPUID_RET_KO ]; then
        pstatus green NO
    else
        cap_sgx=-1
        pstatus yellow UNKNOWN "$ret_read_cpuid_msg"
    fi

    pr_info_nol "  * CPU supports Special Register Buffer Data Sampling (SRBDS): "
    # A processor supports SRBDS if it enumerates CPUID (EAX=7H,ECX=0):EDX[9] as 1
    # That means the mitigation disabling SRBDS exists
    ret=$READ_CPUID_RET_KO
    cap_srbds=0
    cap_srbds_on=0
    if is_intel; then
        read_cpuid 0x7 0x0 $EDX 9 1 1
        ret=$?
    fi
    if [ $ret = $READ_CPUID_RET_OK ]; then
        pstatus blue YES
        cap_srbds=1
        read_msr $MSR_IA32_MCU_OPT_CTRL
        ret=$?
        if [ $ret = $READ_MSR_RET_OK ]; then
            if [ "$((ret_read_msr_value_lo >> 0 & 1))" = 0 ]; then
                #SRBDS mitigation control exists and is enabled via microcode (RNGDS_MITG_DIS bit is 0)
                cap_srbds_on=1
            else
                #SRBDS mitigation control exists but is disabled via microcode (RNGDS_MITG_DIS bit is 1)
                cap_srbds_on=0
            fi
        else
            cap_srbds_on=-1
        fi
    elif [ $ret = $READ_CPUID_RET_KO ]; then
        pstatus green NO
    else
        pstatus yellow UNKNOWN "$ret_read_cpuid_msg"
        cap_srbds=0
    fi

    if is_amd; then
        pr_info_nol "  * CPU microcode is known to fix Zenbleed: "
        has_zenbleed_fixed_firmware
        ret=$?
        if [ $ret -eq 0 ]; then
            # affected CPU, new fw
            pstatus green YES
        elif [ $ret -eq 1 ]; then
            # affected CPU, old fw
            pstatus red NO "required version: $g_zenbleed_fw_required"
        else
            # unaffected CPU
            pstatus yellow NO
        fi
    fi

    pr_info_nol "  * CPU microcode is known to cause stability problems: "
    if is_ucode_blacklisted; then
        pstatus red YES "$g_ucode_found"
        pr_warn
        pr_warn "The microcode your CPU is running on is known to cause instability problems,"
        pr_warn "such as intempestive reboots or random crashes."
        pr_warn "You are advised to either revert to a previous microcode version (that might not have"
        pr_warn "the mitigations for recent vulnerabilities), or upgrade to a newer one if available."
        pr_warn
    else
        pstatus blue NO "$g_ucode_found"
    fi

    pr_info_nol "  * CPU microcode is the latest known available version: "
    is_latest_known_ucode
    ret=$?
    if [ $ret -eq 0 ]; then
        pstatus green YES "$ret_is_latest_known_ucode_latest"
    elif [ $ret -eq 1 ]; then
        pstatus red NO "$ret_is_latest_known_ucode_latest"
    else
        pstatus blue UNKNOWN "$ret_is_latest_known_ucode_latest"
    fi
    if is_running_as_guest; then
        pr_warn
        pr_warn "Note: this system is running inside a VM ($g_is_guest_vm_reason)."
        pr_warn "The hypervisor may be faking the CPU model and microcode version;"
        pr_warn "verify the above microcode information on the hypervisor host for accuracy."
        pr_warn
    fi
}

# Display per-CVE CPU vulnerability status based on CPU model/family.
# Mirrors the main dispatch gate: under a default "all CVEs" run, skip CVEs
# whose arch tag doesn't match this system. Explicit selection via
# --cve/--variant/--errata bypasses the gate.
check_cpu_vulnerabilities() {
    local cve
    pr_info "* CPU vulnerability to the speculative execution attack variants"
    for cve in $g_supported_cve_list; do
        if [ "$opt_cve_all" = 1 ]; then
            _is_cve_relevant_arch "$cve" || continue
        elif ! echo "$opt_cve_list" | grep -qw "$cve"; then
            continue
        fi
        pr_info_nol "  * Affected by $cve ($(cve2name "$cve")): "
        if is_cpu_affected "$cve"; then
            pstatus yellow YES
        else
            pstatus green NO
        fi
    done
}

# Detect Red Hat/Canonical backported Spectre mitigations in the kernel binary
# Sets: g_redhat_canonical_spectre
check_redhat_canonical_spectre() {
    # if we were already called, don't do it again
    [ -n "${g_redhat_canonical_spectre:-}" ] && return

    if ! command -v "${opt_arch_prefix}strings" >/dev/null 2>&1; then
        g_redhat_canonical_spectre=-1
    elif [ -n "$g_kernel_err" ]; then
        g_redhat_canonical_spectre=-2
    else
        # Red Hat / Ubuntu specific affected_variant1 patch is difficult to detect,
        # let's use the two same tricks than the official Red Hat detection script uses:
        if "${opt_arch_prefix}strings" "$g_kernel" | grep -qw noibrs && "${opt_arch_prefix}strings" "$g_kernel" | grep -qw noibpb; then
            # 1) detect their specific affected_variant2 patch. If it's present, it means
            # that the affected_variant1 patch is also present (both were merged at the same time)
            pr_debug "found redhat/canonical version of the affected_variant2 patch (implies affected_variant1)"
            g_redhat_canonical_spectre=1
        elif "${opt_arch_prefix}strings" "$g_kernel" | grep -q 'x86/pti:'; then
            # 2) detect their specific affected_variant3 patch. If it's present, but the affected_variant2
            # is not, it means that only affected_variant1 is present in addition to affected_variant3
            pr_debug "found redhat/canonical version of the affected_variant3 patch (implies affected_variant1 but not affected_variant2)"
            g_redhat_canonical_spectre=2
        else
            g_redhat_canonical_spectre=0
        fi
    fi
}

# Detect whether this system is hosting virtual machines (hypervisor check).
# Detection runs only on the first call; subsequent calls reuse the cached
# result.  The status line is always printed so each CVE section shows the
# hypervisor context to the user.
# Sets: g_has_vmm, g_has_vmm_reason
check_has_vmm() {
    local binary pid
    pr_info_nol "* This system is a host running a hypervisor: "
    if [ "$g_has_vmm_cached" != 1 ]; then
        g_has_vmm=$opt_vmm
        if [ "$g_has_vmm" != -1 ]; then
            # --vmm was explicitly set on the command line
            g_has_vmm_reason="forced from command line"
        elif [ "$opt_paranoid" = 1 ]; then
            # In paranoid mode, if --vmm was not specified on the command-line,
            # we want to be secure before everything else, so assume we're running
            # a hypervisor, as this requires more mitigations
            g_has_vmm=1
            g_has_vmm_reason="paranoid mode"
        else
            # Here, we want to know if we are hosting a hypervisor, and running some VMs on it.
            # If we find no evidence that this is the case, assume we're not (to avoid scaring users),
            # this can always be overridden with --vmm in any case.
            g_has_vmm=0
            if command -v pgrep >/dev/null 2>&1; then
                # Exclude xenbus/xenwatch (present inside domU guests) and
                # libvirtd (also manages containers, not just VMs).
                # Use pgrep -x (exact match) for most binaries.  QEMU is
                # special: the binary is almost never just "qemu" — it is
                # "qemu-system-x86_64", "qemu-system-aarch64", etc.  We
                # keep "qemu" for the rare wrapper/symlink case and add
                # "qemu-system-" as a substring match via a separate pgrep
                # call (without -x) to catch all qemu-system-* variants.
                # Kernel threads (e.g. [kvm-irqfd-clean]) are filtered out
                # below via the /proc/$pid/exe symlink check.
                # Note: the kernel truncates process names to 15 chars
                # (TASK_COMM_LEN), so pgrep -x can't match longer names.
                # "cloud-hypervisor" (16 chars) is handled in the substring
                # block below alongside qemu-system-*.
                for binary in qemu kvm xenstored xenconsoled \
                    VBoxHeadless VBoxSVC vmware-vmx firecracker bhyve; do
                    for pid in $(pgrep -x "$binary"); do
                        # resolve the exe symlink, if it doesn't resolve with -m,
                        # which doesn't even need the dest to exist, it means the symlink
                        # is null, which is the case for kernel threads: ignore those to
                        # avoid false positives (such as [kvm-irqfd-clean] under at least RHEL 7.6/7.7)
                        if ! [ "$(readlink -m "/proc/$pid/exe")" = "/proc/$pid/exe" ]; then
                            pr_debug "g_has_vmm: found PID $pid ($binary)"
                            g_has_vmm=1
                            g_has_vmm_reason="$binary process found (PID $pid)"
                        fi
                    done
                done
                # substring matches for names that pgrep -x can't handle:
                # - qemu-system-*: variable suffix (x86_64, aarch64, ...)
                # - cloud-hypervisor: 16 chars, exceeds TASK_COMM_LEN (15)
                if [ "$g_has_vmm" = 0 ]; then
                    for binary in "qemu-system-" "cloud-hyperviso"; do
                        for pid in $(pgrep "$binary"); do
                            if ! [ "$(readlink -m "/proc/$pid/exe")" = "/proc/$pid/exe" ]; then
                                pr_debug "g_has_vmm: found PID $pid ($binary*)"
                                g_has_vmm=1
                                g_has_vmm_reason="$binary* process found (PID $pid)"
                            fi
                        done
                    done
                fi
                unset binary pid
            else
                # ignore SC2009 as `ps ax` is actually used as a fallback if `pgrep` isn't installed
                # shellcheck disable=SC2009
                if command -v ps >/dev/null && ps ax | grep -vw grep | grep -q \
                    -e '\<qemu' -e '/qemu' -e '\<kvm' -e '/kvm' \
                    -e '/xenstored' -e '/xenconsoled' \
                    -e '\<VBoxHeadless' -e '\<VBoxSVC' -e '\<vmware-vmx' \
                    -e '\<firecracker' -e '\<cloud-hypervisor' -e '\<bhyve'; then
                    g_has_vmm=1
                    g_has_vmm_reason="hypervisor process found"
                fi
            fi
        fi
        g_has_vmm_cached=1
    fi
    if [ "$g_has_vmm" = 0 ]; then
        pstatus green NO "$g_has_vmm_reason"
    else
        pstatus blue YES "$g_has_vmm_reason"
    fi
}
