# vim: set ts=4 sw=4 sts=4 et:
###############################
# CVE-2017-5715, Spectre V2, Branch Target Injection

# Sets: vulnstatus
check_CVE_2017_5715() {
    check_cve 'CVE-2017-5715'
}

# Sets: g_ibrs_can_tell, g_ibrs_supported, g_ibrs_enabled, g_ibrs_fw_enabled,
#   g_ibpb_can_tell, g_ibpb_supported, g_ibpb_enabled, g_specex_knob_dir
check_CVE_2017_5715_linux() {
    local status sys_interface_available msg dir bp_harden_can_tell bp_harden retpoline retpoline_compiler retpoline_compiler_reason retp_enabled rsb_filling
    local v2_base_mode v2_stibp_status v2_pbrsb_status v2_bhi_status v2_ibpb_mode v2_vuln_module v2_is_autoibrs smt_enabled
    status=UNK
    sys_interface_available=0
    msg=''
    if sys_interface_check "$VULN_SYSFS_BASE/spectre_v2"; then
        sys_interface_available=1
        status=$ret_sys_interface_check_status
        #
        # Complete sysfs message inventory for spectre_v2, traced via git blame
        # on mainline (~/linux) and stable (~/linux-stable):
        #
        # all versions:
        #   "Not affected"                          (cpu_show_common, 61dc0f555b5c)
        #   "Vulnerable"                            (cpu_show_common fallthrough / spectre_v2_strings[NONE], 61dc0f555b5c)
        #
        # The output is a composite string: <base>[<ibpb>][<ibrs_fw>][<stibp>][<rsb>][<pbrsb>][<bhi>][<module>]
        # where <base> comes from spectre_v2_strings[] (or an early-return override),
        # and the remaining fields are appended by helper functions.
        # Before v6.9-rc4 (0cd01ac5dcb1), fields were separated by ", ".
        # From v6.9-rc4 onward, fields are separated by "; ".
        #
        # --- base string (spectre_v2_strings[]) ---
        #
        # da285121560e (v4.15, initial spectre_v2 sysfs):
        #   "Vulnerable"
        #   "Mitigation: None"                           (documented in spectre.rst 5ad3eb113245,
        #     not found in code but listed as a possible value; treat as vulnerable / sysfs override)
        #   "Vulnerable: Minimal generic ASM retpoline"
        #   "Vulnerable: Minimal AMD ASM retpoline"
        #   "Mitigation: Full generic retpoline"
        #   "Mitigation: Full AMD retpoline"
        # 706d51681d63 (v4.19, added Enhanced IBRS):
        #   + "Mitigation: Enhanced IBRS"
        # ef014aae8f1c (v4.20-rc5, removed minimal retpoline):
        #   - "Vulnerable: Minimal generic ASM retpoline"
        #   - "Vulnerable: Minimal AMD ASM retpoline"
        # d45476d98324 (v5.17-rc8, renamed retpoline variants):
        #   "Mitigation: Full generic retpoline" -> "Mitigation: Retpolines"
        #   "Mitigation: Full AMD retpoline" -> "Mitigation: LFENCE" (in array)
        #   NOTE: "Mitigation: LFENCE" was the actual sysfs output for a brief window
        #   (between d45476d98324 and eafd987d4a82, both in v5.17-rc8) before the
        #   show_state override reclassified it as "Vulnerable: LFENCE". LFENCE alone
        #   is now considered an insufficient mitigation for Spectre v2. Any kernel
        #   reporting "Mitigation: LFENCE" is from this narrow window and should be
        #   treated as vulnerable.
        # 1e19da8522c8 (v5.17-rc8, split Enhanced IBRS into 3 modes):
        #   "Mitigation: Enhanced IBRS" -> "Mitigation: Enhanced IBRS" (EIBRS alone)
        #   + "Mitigation: Enhanced IBRS + LFENCE"
        #   + "Mitigation: Enhanced IBRS + Retpolines"
        # 7c693f54c873 (v5.19-rc7, added kernel IBRS):
        #   + "Mitigation: IBRS"
        # e7862eda309e (v6.3-rc1, AMD Automatic IBRS):
        #   "Mitigation: Enhanced IBRS" -> "Mitigation: Enhanced / Automatic IBRS"
        #   "Mitigation: Enhanced IBRS + LFENCE" -> "Mitigation: Enhanced / Automatic IBRS + LFENCE"
        #   "Mitigation: Enhanced IBRS + Retpolines" -> "Mitigation: Enhanced / Automatic IBRS + Retpolines"
        # d1cc1baef67a (v6.18-rc1, fixed LFENCE in array):
        #   "Mitigation: LFENCE" -> "Vulnerable: LFENCE" (in array, matching the
        #   show_state override that had been correcting the sysfs output since v5.17)
        #
        # --- early-return overrides in spectre_v2_show_state() ---
        #
        # These bypass the base string + suffix format entirely.
        #
        # eafd987d4a82 (v5.17-rc8, LFENCE override):
        #   "Vulnerable: LFENCE"
        #   (overrode the array's "Mitigation: LFENCE"; removed in v6.18-rc1 when the array
        #   was fixed to say "Vulnerable: LFENCE" directly)
        # 44a3918c8245 (v5.17-rc8, created spectre_v2_show_state, eIBRS+eBPF):
        #   "Vulnerable: Unprivileged eBPF enabled"
        #   (immediately renamed below)
        # 0de05d056afd (v5.17-rc8, renamed + added eIBRS+LFENCE case):
        #   "Vulnerable: Unprivileged eBPF enabled" -> "Vulnerable: eIBRS with unprivileged eBPF"
        #   + "Vulnerable: eIBRS+LFENCE with unprivileged eBPF and SMT"
        #
        # --- <ibpb> suffix (ibpb_state()) ---
        #
        # 20ffa1caecca (v4.16-rc1, initial IBPB):
        #   ", IBPB" (present/absent)
        # a8f76ae41cd6 (v4.20-rc5, extracted ibpb_state()):
        #   ", IBPB: disabled" | ", IBPB: always-on"
        # 7cc765a67d8e (v4.20-rc5, conditional IBPB):
        #   + ", IBPB: conditional"
        # 0cd01ac5dcb1 (v6.9-rc4, separator change):
        #   ", IBPB: ..." -> "; IBPB: ..."
        #
        # --- <ibrs_fw> suffix ---
        #
        # dd84441a7971 (v4.16-rc4):
        #   ", IBRS_FW" (present/absent)
        # 0cd01ac5dcb1 (v6.9-rc4, separator change):
        #   ", IBRS_FW" -> "; IBRS_FW"
        #
        # --- <stibp> suffix (stibp_state()) ---
        #
        # 53c613fe6349 (v4.20-rc1, initial STIBP):
        #   ", STIBP" (present/absent)
        # a8f76ae41cd6 (v4.20-rc5, extracted stibp_state()):
        #   ", STIBP: disabled" | ", STIBP: forced"
        # 9137bb27e60e (v4.20-rc5, added prctl):
        #   + ", STIBP: conditional" (when prctl/seccomp + switch_to_cond_stibp)
        # 20c3a2c33e9f (v5.0-rc1, added always-on preferred):
        #   + ", STIBP: always-on"
        # 34bce7c9690b (v4.20-rc5, eIBRS suppresses STIBP):
        #   returns "" when eIBRS is in use
        # fd470a8beed8 (v6.5-rc4, AutoIBRS exception):
        #   returns "" only when eIBRS AND not AutoIBRS (AutoIBRS shows STIBP)
        # 0cd01ac5dcb1 (v6.9-rc4, separator change):
        #   ", STIBP: ..." -> "; STIBP: ..."
        #
        # --- <rsb> suffix ---
        #
        # bb4b3b776273 (v4.20-rc1):
        #   ", RSB filling" (present/absent)
        # 53c613fe6349 (v4.20-rc1, temporarily removed, re-added in a8f76ae41cd6)
        # 0cd01ac5dcb1 (v6.9-rc4, separator change):
        #   ", RSB filling" -> "; RSB filling"
        #
        # --- <pbrsb> suffix (pbrsb_eibrs_state()) ---
        #
        # 2b1299322016 (v6.0-rc1):
        #   ", PBRSB-eIBRS: Not affected" | ", PBRSB-eIBRS: SW sequence" | ", PBRSB-eIBRS: Vulnerable"
        # 0cd01ac5dcb1 (v6.9-rc4, separator change):
        #   ", PBRSB-eIBRS: ..." -> "; PBRSB-eIBRS: ..."
        #
        # --- <bhi> suffix (spectre_bhi_state()) ---
        #
        # ec9404e40e8f (v6.9-rc4):
        #   "; BHI: Not affected" | "; BHI: BHI_DIS_S" | "; BHI: SW loop, KVM: SW loop"
        #   | "; BHI: Retpoline" | "; BHI: Vulnerable"
        # 95a6ccbdc719 (v6.9-rc4, KVM default):
        #   + "; BHI: Vulnerable, KVM: SW loop"
        # 5f882f3b0a8b (v6.9-rc4, clarified syscall hardening):
        #   removed "; BHI: Vulnerable (Syscall hardening enabled)"
        #   removed "; BHI: Syscall hardening, KVM: SW loop"
        #   (both replaced by "; BHI: Vulnerable" / "; BHI: Vulnerable, KVM: SW loop")
        #
        # --- <module> suffix (spectre_v2_module_string()) ---
        #
        # caf7501a1b4e (v4.16-rc1):
        #   " - vulnerable module loaded" (present/absent)
        #
        # --- stable backports ---
        #
        # 3.2.y: old-style base strings ("Full generic/AMD retpoline", "Minimal generic/AMD
        #   ASM retpoline"). Suffixes: ", IBPB" only (no STIBP/IBRS_FW/RSB). Format: %s%s\n.
        #   Has "Mitigation: Enhanced IBRS" (no "/ Automatic") in later releases.
        # 3.16.y, 4.4.y: old-style base strings. ibpb_state()/stibp_state()/IBRS_FW/RSB filling.
        #   3.16.y lacks STIBP "always-on". Comma separators.
        #   Both have "Mitigation: Enhanced IBRS" (no "/ Automatic"). No LFENCE/EIBRS modes.
        # 4.9.y: has spectre_v2_show_state() with LFENCE override ("Vulnerable: LFENCE"),
        #   eIBRS+eBPF overrides. "Mitigation: Enhanced IBRS" (no "/ Automatic").
        #   No SPECTRE_V2_IBRS. No pbrsb or BHI. Comma separators.
        # 4.14.y: like 4.9.y but also has SPECTRE_V2_IBRS and pbrsb_eibrs_state().
        #   "Mitigation: Enhanced IBRS" (no "/ Automatic"). No BHI. Comma separators.
        # 4.19.y: like 4.14.y but has "Enhanced / Automatic IBRS". No BHI. Comma separators.
        # 5.4.y, 5.10.y: like 4.19.y. No BHI. Comma separators.
        # 5.15.y, 6.1.y, 6.6.y, 6.12.y: match mainline (semicolons, BHI, all fields).
        #
        # --- Red Hat / CentOS / Rocky kernels ---
        #
        # Red Hat kernels carry their own spectre_v2 mitigation implementation that differs
        # significantly from mainline. The following strings are unique to Red Hat kernels:
        #
        # centos6 (RHEL 6, kernel 2.6.32): base strings are a superset of mainline v4.15:
        #   "Vulnerable: Minimal ASM retpoline"         (no "generic" qualifier)
        #   "Vulnerable: Minimal AMD ASM retpoline"
        #   "Vulnerable: Retpoline without IBPB"
        #   "Vulnerable: Retpoline on Skylake+"          (removed in later centos6 releases)
        #   "Vulnerable: Retpoline with unsafe module(s)"
        #   "Mitigation: Full AMD retpoline"
        #   "Mitigation: Full retpoline"                 (no "generic" qualifier)
        #   "Mitigation: Full retpoline and IBRS (user space)"
        #   "Mitigation: IBRS (kernel)"
        #   "Mitigation: IBRS (kernel and user space)"
        #   "Mitigation: IBP disabled"
        #   Suffixes: ", IBPB" only. Format: %s%s\n.
        #
        # centos7 (RHEL 7, kernel 3.10): early releases have all centos6 strings plus
        #   "Vulnerable: Retpoline on Skylake+". Later releases removed Skylake+ and
        #   Minimal AMD, and changed AMD retpoline to:
        #   "Vulnerable: AMD retpoline (LFENCE/JMP)"
        #   Added "Mitigation: Enhanced IBRS". Suffixes: ibpb_state() + stibp_state()
        #   with simple ", IBPB" / ", STIBP" strings. No IBRS_FW/RSB/pbrsb/BHI.
        #
        # centos8 (RHEL 8, kernel 4.18): uses mainline v5.17+ style enum names
        #   (RETPOLINE/LFENCE/EIBRS) but retains RHEL-specific entries:
        #   "Mitigation: IBRS (kernel)"                  (SPECTRE_V2_IBRS)
        #   "Mitigation: Full retpoline and IBRS (user space)" (SPECTRE_V2_RETPOLINE_IBRS_USER)
        #   "Mitigation: IBRS (kernel and user space)"   (SPECTRE_V2_IBRS_ALWAYS)
        #   "Mitigation: Enhanced IBRS" (no "/ Automatic"). spectre_v2_show_state() with
        #   LFENCE/eIBRS+eBPF overrides. Comma separators. No pbrsb/BHI.
        #
        # rocky9 (RHEL 9, kernel 5.14): matches mainline. Semicolons, BHI, all fields.
        # rocky10 (RHEL 10, kernel 6.12): matches mainline.
        #
        #
        # --- Kconfig symbols ---
        # 76b043848fd2 (v4.15-rc8): CONFIG_RETPOLINE
        # f43b9876e857 (v5.19-rc7): CONFIG_CPU_IBRS_ENTRY (kernel IBRS on entry)
        # aefb2f2e619b (v6.9-rc1): renamed CONFIG_RETPOLINE => CONFIG_MITIGATION_RETPOLINE
        # 1da8d2172ce5 (v6.9-rc1): renamed CONFIG_CPU_IBRS_ENTRY => CONFIG_MITIGATION_IBRS_ENTRY
        # ec9404e40e8f (v6.9-rc4): CONFIG_SPECTRE_BHI_ON / CONFIG_SPECTRE_BHI_OFF
        # 4f511739c54b (v6.9-rc4): replaced by CONFIG_MITIGATION_SPECTRE_BHI
        # 72c70f480a70 (v6.12-rc1): CONFIG_MITIGATION_SPECTRE_V2 (top-level on/off)
        # 8754e67ad4ac (v6.15-rc7): CONFIG_MITIGATION_ITS (indirect target selection)
        # stable 5.4.y-6.6.y: CONFIG_RETPOLINE (pre-rename)
        # stable 6.12.y: CONFIG_MITIGATION_RETPOLINE, CONFIG_MITIGATION_SPECTRE_V2
        #
        # --- kernel functions (for $opt_map / System.map) ---
        # da285121560e (v4.15-rc8): spectre_v2_select_mitigation(),
        #   spectre_v2_parse_cmdline(), nospectre_v2_parse_cmdline()
        # 20ffa1caecca (v4.16-rc1): spectre_v2_module_string(), retpoline_module_ok()
        # a8f76ae41cd6 (v4.20-rc5): spectre_v2_user_select_mitigation(),
        #   spectre_v2_user_parse_cmdline()
        # 7c693f54c873 (v5.19-rc7): spectre_v2_in_ibrs_mode(), spectre_v2_in_eibrs_mode()
        # 44a3918c8245 (v5.17-rc8): spectre_v2_show_state()
        # 480e803dacf8 (v6.16-rc1): split into spectre_v2_select_mitigation() +
        #   spectre_v2_apply_mitigation() + spectre_v2_update_mitigation() +
        #   spectre_v2_user_apply_mitigation() + spectre_v2_user_update_mitigation()
        #
        # --- CPU affection logic (for is_cpu_affected) ---
        # X86_BUG_SPECTRE_V2 is set for ALL x86 CPUs except:
        #   - CPUs matching NO_SPECULATION: family 4 (all vendors), Centaur/Intel/NSC/Vortex
        #     family 5, Intel Atom Bonnell/Saltwell
        #   - CPUs matching NO_SPECTRE_V2: Centaur family 7, Zhaoxin family 7
        # 99c6fa2511d8 (v4.15-rc8): unconditional for all x86 CPUs
        # 1e41a766c98b (v5.6-rc1): added NO_SPECTRE_V2 exemption for Centaur/Zhaoxin
        # 98c7a713db91 (v6.15-rc1): added X86_BUG_SPECTRE_V2_USER as separate bit
        # No MSR/CPUID immunity bits — purely whitelist-based.
        # vendor scope: all x86 vendors affected (Intel, AMD, Hygon, etc.)
        #   except Centaur family 7 and Zhaoxin family 7.
        #
        # all messages start with either "Not affected", "Mitigation", or "Vulnerable"
    fi
    if [ "$opt_sysfs_only" != 1 ]; then
        check_has_vmm

        v2_base_mode=''
        v2_stibp_status=''
        v2_pbrsb_status=''
        v2_bhi_status=''
        v2_ibpb_mode=''
        v2_vuln_module=''
        v2_is_autoibrs=0

        # Mitigation 1 (IBRS/IBPB) and Mitigation 3 (sub-mitigations) are x86-only.
        # On ARM64, only Mitigation 2 (branch predictor hardening) is relevant.
        if is_x86_kernel; then

            pr_info "* Mitigation 1"

            g_ibrs_can_tell=0
            g_ibrs_supported=''
            g_ibrs_enabled=''
            g_ibpb_can_tell=0
            g_ibpb_supported=''
            g_ibpb_enabled=''

            if [ "$g_mode" = live ]; then
                # in live mode, we can check for the ibrs_enabled file in debugfs
                # all versions of the patches have it (NOT the case of IBPB or KPTI)
                g_ibrs_can_tell=1
                mount_debugfs
                for dir in \
                    $DEBUGFS_BASE \
                    $DEBUGFS_BASE/x86 \
                    "$g_procfs/sys/kernel"; do
                    if [ -e "$dir/ibrs_enabled" ]; then
                        # if the file is there, we have IBRS compiled-in
                        # $DEBUGFS_BASE/ibrs_enabled: vanilla
                        # $DEBUGFS_BASE/x86/ibrs_enabled: Red Hat (see https://access.redhat.com/articles/3311301)
                        # /proc/sys/kernel/ibrs_enabled: OpenSUSE tumbleweed
                        g_specex_knob_dir=$dir
                        g_ibrs_supported="$dir/ibrs_enabled exists"
                        g_ibrs_enabled=$(cat "$dir/ibrs_enabled" 2>/dev/null)
                        pr_debug "ibrs: found $dir/ibrs_enabled=$g_ibrs_enabled"
                        # if ibrs_enabled is there, ibpb_enabled will be in the same dir
                        if [ -e "$dir/ibpb_enabled" ]; then
                            # if the file is there, we have IBPB compiled-in (see note above for IBRS)
                            g_ibpb_supported="$dir/ibpb_enabled exists"
                            g_ibpb_enabled=$(cat "$dir/ibpb_enabled" 2>/dev/null)
                            pr_debug "ibpb: found $dir/ibpb_enabled=$g_ibpb_enabled"
                        else
                            pr_debug "ibpb: $dir/ibpb_enabled file doesn't exist"
                        fi
                        break
                    else
                        pr_debug "ibrs: $dir/ibrs_enabled file doesn't exist"
                    fi
                done
                # on some newer kernels, the spec_ctrl_ibrs flag in "$g_procfs/cpuinfo"
                # is set when ibrs has been administratively enabled (usually from cmdline)
                # which in that case means ibrs is supported *and* enabled for kernel & user
                # as per the ibrs patch series v3
                if [ -z "$g_ibrs_supported" ]; then
                    if cpuinfo_has_flag spec_ctrl_ibrs; then
                        pr_debug "ibrs: found spec_ctrl_ibrs flag in $g_procfs/cpuinfo"
                        g_ibrs_supported="spec_ctrl_ibrs flag in $g_procfs/cpuinfo"
                        # enabled=2 -> kernel & user
                        g_ibrs_enabled=2
                        # XXX and what about ibpb ?
                    fi
                fi
                if [ -n "$ret_sys_interface_check_fullmsg" ]; then
                    # when IBPB is enabled on 4.15+, we can see it in sysfs
                    if echo "$ret_sys_interface_check_fullmsg" | grep -q 'IBPB'; then
                        pr_debug "ibpb: found enabled in sysfs"
                        [ -z "$g_ibpb_supported" ] && g_ibpb_supported='IBPB found enabled in sysfs'
                        [ -z "$g_ibpb_enabled" ] && g_ibpb_enabled=1
                    fi
                    # when IBRS_FW is enabled on 4.15+, we can see it in sysfs
                    if echo "$ret_sys_interface_check_fullmsg" | grep -q '[,;] IBRS_FW'; then
                        pr_debug "ibrs: found IBRS_FW in sysfs"
                        [ -z "$g_ibrs_supported" ] && g_ibrs_supported='found IBRS_FW in sysfs'
                        g_ibrs_fw_enabled=1
                    fi
                    # when IBRS is enabled on 4.15+, we can see it in sysfs
                    # on a more recent kernel, classic "IBRS" is not even longer an option, because of the performance impact.
                    # only "Enhanced IBRS" is available (on CPUs with the IBRS_ALL flag)
                    if echo "$ret_sys_interface_check_fullmsg" | grep -q -e '\<IBRS\>' -e 'Indirect Branch Restricted Speculation'; then
                        pr_debug "ibrs: found IBRS in sysfs"
                        [ -z "$g_ibrs_supported" ] && g_ibrs_supported='found IBRS in sysfs'
                        [ -z "$g_ibrs_enabled" ] && g_ibrs_enabled=3
                    fi
                    # checking for 'Enhanced IBRS' in sysfs, enabled on CPUs with IBRS_ALL
                    if echo "$ret_sys_interface_check_fullmsg" | grep -q -e 'Enhanced IBRS'; then
                        [ -z "$g_ibrs_supported" ] && g_ibrs_supported='found Enhanced IBRS in sysfs'
                        # 4 isn't actually a valid value of the now extinct "g_ibrs_enabled" flag file,
                        # that only went from 0 to 3, so we use 4 as "enhanced ibrs is enabled"
                        g_ibrs_enabled=4
                    fi
                fi
                # in live mode, if ibrs or ibpb is supported and we didn't find these are enabled, then they are not
                [ -n "$g_ibrs_supported" ] && [ -z "$g_ibrs_enabled" ] && g_ibrs_enabled=0
                [ -n "$g_ibpb_supported" ] && [ -z "$g_ibpb_enabled" ] && g_ibpb_enabled=0
            fi
            if [ -z "$g_ibrs_supported" ]; then
                check_redhat_canonical_spectre
                if [ "$g_redhat_canonical_spectre" = 1 ]; then
                    g_ibrs_supported="Red Hat/Ubuntu variant"
                    g_ibpb_supported="Red Hat/Ubuntu variant"
                fi
            fi
            if [ -z "$g_ibrs_supported" ] && [ -n "$g_kernel" ]; then
                if ! command -v "${opt_arch_prefix}strings" >/dev/null 2>&1; then
                    :
                else
                    g_ibrs_can_tell=1
                    g_ibrs_supported=$("${opt_arch_prefix}strings" "$g_kernel" | grep -Fw -e '[,;] IBRS_FW' | head -n1)
                    if [ -n "$g_ibrs_supported" ]; then
                        pr_debug "ibrs: found ibrs evidence in kernel image ($g_ibrs_supported)"
                        g_ibrs_supported="found '$g_ibrs_supported' in kernel image"
                    fi
                fi
            fi
            if [ -z "$g_ibrs_supported" ] && [ -n "$opt_map" ]; then
                g_ibrs_can_tell=1
                if grep -q spec_ctrl "$opt_map"; then
                    g_ibrs_supported="found spec_ctrl in symbols file"
                    pr_debug "ibrs: found '*spec_ctrl*' symbol in $opt_map"
                elif grep -q -e spectre_v2_select_mitigation -e spectre_v2_apply_mitigation "$opt_map"; then
                    # spectre_v2_select_mitigation exists since v4.15; split into
                    # spectre_v2_select_mitigation + spectre_v2_apply_mitigation in v6.16
                    g_ibrs_supported="found spectre_v2 mitigation function in symbols file"
                    pr_debug "ibrs: found spectre_v2_*_mitigation symbol in $opt_map"
                fi
            fi
            # CONFIG_CPU_IBRS_ENTRY (v5.19) / CONFIG_MITIGATION_IBRS_ENTRY (v6.9): kernel IBRS on entry
            if [ -z "$g_ibrs_supported" ] && [ -n "$opt_config" ] && [ -r "$opt_config" ]; then
                g_ibrs_can_tell=1
                if grep -q '^CONFIG_\(CPU_\|MITIGATION_\)IBRS_ENTRY=y' "$opt_config"; then
                    g_ibrs_supported="CONFIG_CPU_IBRS_ENTRY/CONFIG_MITIGATION_IBRS_ENTRY found in kernel config"
                    pr_debug "ibrs: found IBRS entry config option in $opt_config"
                fi
            fi
            # recent (4.15) vanilla kernels have IBPB but not IBRS, and without the debugfs tunables of Red Hat
            # we can detect it directly in the image
            if [ -z "$g_ibpb_supported" ] && [ -n "$g_kernel" ]; then
                if ! command -v "${opt_arch_prefix}strings" >/dev/null 2>&1; then
                    :
                else
                    g_ibpb_can_tell=1
                    g_ibpb_supported=$("${opt_arch_prefix}strings" "$g_kernel" | grep -Fw -e 'ibpb' -e ', IBPB' | head -n1)
                    if [ -n "$g_ibpb_supported" ]; then
                        pr_debug "ibpb: found ibpb evidence in kernel image ($g_ibpb_supported)"
                        g_ibpb_supported="found '$g_ibpb_supported' in kernel image"
                    fi
                fi
            fi

            pr_info_nol "  * Kernel is compiled with IBRS support: "
            if [ -z "$g_ibrs_supported" ]; then
                if [ "$g_ibrs_can_tell" = 1 ]; then
                    pstatus yellow NO
                else
                    # problem obtaining/inspecting kernel or strings not installed, but if the later is true,
                    # then readelf is not installed either (both in binutils) which makes the former true, so
                    # either way g_kernel_err should be set
                    pstatus yellow UNKNOWN "couldn't check ($g_kernel_err)"
                fi
            else
                if [ "$opt_verbose" -ge 2 ]; then
                    pstatus green YES "$g_ibrs_supported"
                else
                    pstatus green YES
                fi
            fi

            pr_info_nol "    * IBRS enabled and active: "
            if [ "$g_mode" = live ]; then
                if [ "$g_ibpb_enabled" = 2 ]; then
                    # if ibpb=2, ibrs is forcefully=0
                    pstatus blue NO "IBPB used instead of IBRS in all kernel entrypoints"
                else
                    # 0 means disabled
                    # 1 is enabled only for kernel space
                    # 2 is enabled for kernel and user space
                    # 3 is enabled
                    # 4 is enhanced ibrs enabled
                    case "$g_ibrs_enabled" in
                        0)
                            if [ "$g_ibrs_fw_enabled" = 1 ]; then
                                pstatus blue YES "for firmware code only"
                            else
                                pstatus yellow NO
                            fi
                            ;;
                        1) if [ "$g_ibrs_fw_enabled" = 1 ]; then pstatus green YES "for kernel space and firmware code"; else pstatus green YES "for kernel space"; fi ;;
                        2) if [ "$g_ibrs_fw_enabled" = 1 ]; then pstatus green YES "for kernel, user space, and firmware code"; else pstatus green YES "for both kernel and user space"; fi ;;
                        3) if [ "$g_ibrs_fw_enabled" = 1 ]; then pstatus green YES "for kernel and firmware code"; else pstatus green YES; fi ;;
                        4) pstatus green YES "Enhanced flavor, performance impact will be greatly reduced" ;;
                        *) if [ "$cap_ibrs" != 'SPEC_CTRL' ] && [ "$cap_ibrs" != 'IBRS_SUPPORT' ] && [ "$cap_spec_ctrl" != -1 ]; then
                            pstatus yellow NO
                            pr_debug "ibrs: known cpu not supporting SPEC-CTRL or IBRS"
                        else
                            pstatus yellow UNKNOWN
                        fi ;;
                    esac
                fi
            else
                pstatus blue N/A "not testable in no-runtime mode"
            fi

            pr_info_nol "  * Kernel is compiled with IBPB support: "
            if [ -z "$g_ibpb_supported" ]; then
                if [ "$g_ibpb_can_tell" = 1 ]; then
                    pstatus yellow NO
                else
                    # if we're in no-runtime mode without System.map, we can't really know
                    pstatus yellow UNKNOWN "in no-runtime mode, we need the kernel image to be able to tell"
                fi
            else
                if [ "$opt_verbose" -ge 2 ]; then
                    pstatus green YES "$g_ibpb_supported"
                else
                    pstatus green YES
                fi
            fi

            pr_info_nol "    * IBPB enabled and active: "
            if [ "$g_mode" = live ]; then
                case "$g_ibpb_enabled" in
                    "")
                        if [ "$g_ibrs_supported" = 1 ]; then
                            pstatus yellow UNKNOWN
                        else
                            pstatus yellow NO
                        fi
                        ;;
                    0)
                        pstatus yellow NO
                        ;;
                    1) pstatus green YES ;;
                    2) pstatus green YES "IBPB used instead of IBRS in all kernel entrypoints" ;;
                    *) pstatus yellow UNKNOWN ;;
                esac
            else
                pstatus blue N/A "not testable in no-runtime mode"
            fi

        fi # is_x86_kernel (Mitigation 1)

        bp_harden_can_tell=0
        bp_harden=''
        if is_arm_kernel; then
            pr_info "* Mitigation 2"
            pr_info_nol "  * Kernel has branch predictor hardening (arm): "
            if [ -r "$opt_config" ]; then
                bp_harden_can_tell=1
                bp_harden=$(grep -w 'CONFIG_HARDEN_BRANCH_PREDICTOR=y' "$opt_config")
                if [ -n "$bp_harden" ]; then
                    pstatus green YES
                    pr_debug "bp_harden: found '$bp_harden' in $opt_config"
                fi
            fi
            if [ -z "$bp_harden" ] && [ -n "$opt_map" ]; then
                bp_harden_can_tell=1
                bp_harden=$(grep -w bp_hardening_data "$opt_map")
                if [ -n "$bp_harden" ]; then
                    pstatus green YES
                    pr_debug "bp_harden: found '$bp_harden' in $opt_map"
                fi
            fi
            if [ -z "$bp_harden" ]; then
                if [ "$bp_harden_can_tell" = 1 ]; then
                    pstatus yellow NO
                else
                    pstatus yellow UNKNOWN
                fi
            fi
        fi

        if is_x86_kernel; then

            pr_info_nol "  * Kernel compiled with retpoline option: "
            # We check the RETPOLINE kernel options
            retpoline=0
            if [ -r "$opt_config" ]; then
                if grep -q '^CONFIG_\(MITIGATION_\)\?RETPOLINE=y' "$opt_config"; then
                    pstatus green YES
                    retpoline=1
                    # shellcheck disable=SC2046
                    pr_debug 'retpoline: found '$(grep '^CONFIG_\(MITIGATION_\)\?RETPOLINE' "$opt_config")" in $opt_config"
                else
                    pstatus yellow NO
                fi
            else
                pstatus yellow UNKNOWN "couldn't read your kernel configuration"
            fi

            if [ "$retpoline" = 1 ]; then
                # Now check if the compiler used to compile the kernel knows how to insert retpolines in generated asm
                # For gcc, this is -mindirect-branch=thunk-extern (detected by the kernel makefiles)
                # See gcc commit https://github.com/hjl-tools/gcc/commit/23b517d4a67c02d3ef80b6109218f2aadad7bd79
                # In latest retpoline LKML patches, the noretpoline_setup symbol exists only if CONFIG_MITIGATION_RETPOLINE is set
                # *AND* if the compiler is retpoline-compliant, so look for that symbol. The name of this kernel config
                # option before version 6.9-rc1 is CONFIG_RETPOLINE.
                #
                # if there is "retpoline" in the file and NOT "minimal", then it's full retpoline
                # (works for vanilla and Red Hat variants)
                #
                # since 5.15.28, this is now "Retpolines" as the implementation was switched to a generic one,
                # so we look for both "retpoline" and "retpolines"
                if [ "$g_mode" = live ] && [ -n "$ret_sys_interface_check_fullmsg" ]; then
                    if echo "$ret_sys_interface_check_fullmsg" | grep -qwi -e retpoline -e retpolines; then
                        if echo "$ret_sys_interface_check_fullmsg" | grep -qwi minimal; then
                            retpoline_compiler=0
                            retpoline_compiler_reason="kernel reports minimal retpoline compilation"
                        else
                            retpoline_compiler=1
                            retpoline_compiler_reason="kernel reports full retpoline compilation"
                        fi
                    fi
                elif [ -n "$opt_map" ]; then
                    # look for the symbol
                    if grep -qw noretpoline_setup "$opt_map"; then
                        retpoline_compiler=1
                        retpoline_compiler_reason="noretpoline_setup symbol found in System.map"
                    fi
                elif [ -n "$g_kernel" ]; then
                    # look for the symbol
                    if command -v "${opt_arch_prefix}nm" >/dev/null 2>&1; then
                        # the proper way: use nm and look for the symbol
                        if "${opt_arch_prefix}nm" "$g_kernel" 2>/dev/null | grep -qw 'noretpoline_setup'; then
                            retpoline_compiler=1
                            retpoline_compiler_reason="noretpoline_setup found in kernel symbols"
                        fi
                    elif grep -q noretpoline_setup "$g_kernel"; then
                        # if we don't have nm, nevermind, the symbol name is long enough to not have
                        # any false positive using good old grep directly on the binary
                        retpoline_compiler=1
                        retpoline_compiler_reason="noretpoline_setup found in kernel"
                    fi
                fi
                if [ -n "$retpoline_compiler" ]; then
                    pr_info_nol "    * Kernel compiled with a retpoline-aware compiler: "
                    if [ "$retpoline_compiler" = 1 ]; then
                        if [ -n "$retpoline_compiler_reason" ]; then
                            pstatus green YES "$retpoline_compiler_reason"
                        else
                            pstatus green YES
                        fi
                    else
                        if [ -n "$retpoline_compiler_reason" ]; then
                            pstatus red NO "$retpoline_compiler_reason"
                        else
                            pstatus red NO
                        fi
                    fi
                fi
            fi

            # only Red Hat has a tunable to disable it on runtime
            retp_enabled=-1
            if [ "$g_mode" = live ]; then
                if [ -e "$g_specex_knob_dir/retp_enabled" ]; then
                    retp_enabled=$(cat "$g_specex_knob_dir/retp_enabled" 2>/dev/null)
                    pr_debug "retpoline: found $g_specex_knob_dir/retp_enabled=$retp_enabled"
                    pr_info_nol "    * Retpoline is enabled: "
                    if [ "$retp_enabled" = 1 ]; then
                        pstatus green YES
                    else
                        pstatus yellow NO
                    fi
                fi
            fi

            # only for information, in verbose mode
            if [ "$opt_verbose" -ge 2 ]; then
                pr_info_nol "    * Local gcc is retpoline-aware: "
                if command -v gcc >/dev/null 2>&1; then
                    if [ -n "$(gcc -mindirect-branch=thunk-extern --version 2>&1 >/dev/null)" ]; then
                        pstatus blue NO
                    else
                        pstatus green YES
                    fi
                else
                    pstatus blue NO "gcc is not installed"
                fi
            fi

            if is_vulnerable_to_empty_rsb || [ "$opt_verbose" -ge 2 ]; then
                pr_info_nol "  * Kernel supports RSB filling: "
                rsb_filling=0
                if [ "$g_mode" = live ] && [ "$opt_no_sysfs" != 1 ]; then
                    # if we're live and we aren't denied looking into /sys, let's do it
                    if echo "$ret_sys_interface_check_fullmsg" | grep -qw RSB; then
                        rsb_filling=1
                        pstatus green YES
                    fi
                fi
                if [ "$rsb_filling" = 0 ]; then
                    # Red Hat kernels (RHEL 6/7/8) stuff RSB on context switch as part of
                    # their retpoline implementation when retp_enabled=1, but don't use the
                    # upstream X86_FEATURE_RSB_CTXSW flag or "Filling RSB on context switch"
                    # string. Detect this via the RHEL-specific debugfs knob.
                    # See https://bugzilla.redhat.com/show_bug.cgi?id=1616245#c8
                    if [ "$retp_enabled" = 1 ]; then
                        rsb_filling=1
                        pstatus green YES "Red Hat kernel with retpoline enabled includes RSB filling"
                    elif [ -n "$g_kernel_err" ]; then
                        pstatus yellow UNKNOWN "couldn't check ($g_kernel_err)"
                    else
                        if grep -qw -e 'Filling RSB on context switch' "$g_kernel"; then
                            rsb_filling=1
                            pstatus green YES
                        else
                            rsb_filling=0
                            pstatus yellow NO
                        fi
                    fi
                fi
            fi

            # Mitigation 3: derive structured mitigation variables for the verdict.
            # These are set from sysfs fields (when available) with hardware fallbacks.
            pr_info "* Mitigation 3 (sub-mitigations)"

            # --- v2_base_mode: which base Spectre v2 mitigation is active ---
            pr_info_nol "  * Base Spectre v2 mitigation mode: "
            if [ -n "$ret_sys_interface_check_fullmsg" ]; then
                # Parse from sysfs (handle all mainline, stable, and RHEL variants)
                case "$ret_sys_interface_check_fullmsg" in
                    *"Enhanced / Automatic IBRS + LFENCE"* | *"Enhanced IBRS + LFENCE"*) v2_base_mode=eibrs_lfence ;;
                    *"Enhanced / Automatic IBRS + Retpolines"* | *"Enhanced IBRS + Retpolines"*) v2_base_mode=eibrs_retpoline ;;
                    *"Enhanced / Automatic IBRS"* | *"Enhanced IBRS"*) v2_base_mode=eibrs ;;
                    *"Mitigation: IBRS (kernel and user space)"*) v2_base_mode=ibrs ;;
                    *"Mitigation: IBRS (kernel)"*) v2_base_mode=ibrs ;;
                    *"Mitigation: IBRS"*) v2_base_mode=ibrs ;;
                    *"Mitigation: Retpolines"* | *"Full generic retpoline"* | *"Full retpoline"* | *"Full AMD retpoline"*) v2_base_mode=retpoline ;;
                    *"Vulnerable: LFENCE"* | *"Mitigation: LFENCE"*) v2_base_mode=lfence ;;
                    *"Vulnerable"*) v2_base_mode=none ;;
                    *) v2_base_mode=unknown ;;
                esac
            fi
            # Fallback to existing variables if sysfs didn't provide a base mode
            if [ -z "$v2_base_mode" ] || [ "$v2_base_mode" = "unknown" ]; then
                if [ "$g_ibrs_enabled" = 4 ]; then
                    v2_base_mode=eibrs
                elif [ -n "$g_ibrs_enabled" ] && [ "$g_ibrs_enabled" -ge 1 ] 2>/dev/null; then
                    v2_base_mode=ibrs
                elif [ "$retpoline" = 1 ] && [ "$retpoline_compiler" = 1 ]; then
                    v2_base_mode=retpoline
                elif [ "$retpoline" = 1 ]; then
                    v2_base_mode=retpoline
                fi
            fi
            case "$v2_base_mode" in
                eibrs) pstatus green "Enhanced / Automatic IBRS" ;;
                eibrs_lfence) pstatus green "Enhanced / Automatic IBRS + LFENCE" ;;
                eibrs_retpoline) pstatus green "Enhanced / Automatic IBRS + Retpolines" ;;
                ibrs) pstatus green "IBRS" ;;
                retpoline) pstatus green "Retpolines" ;;
                lfence) pstatus red "LFENCE (insufficient)" ;;
                none) pstatus yellow "None" ;;
                *) pstatus yellow UNKNOWN ;;
            esac

            # --- v2_is_autoibrs: AMD AutoIBRS vs Intel eIBRS ---
            case "$v2_base_mode" in
                eibrs | eibrs_lfence | eibrs_retpoline)
                    if [ "$cap_autoibrs" = 1 ] || { (is_amd || is_hygon) && [ "$cap_ibrs_all" != 1 ]; }; then
                        v2_is_autoibrs=1
                    fi
                    ;;
            esac

            # --- v2_ibpb_mode ---
            pr_info_nol "  * IBPB mode: "
            if [ -n "$ret_sys_interface_check_fullmsg" ]; then
                case "$ret_sys_interface_check_fullmsg" in
                    *"IBPB: always-on"*) v2_ibpb_mode=always-on ;;
                    *"IBPB: conditional"*) v2_ibpb_mode=conditional ;;
                    *"IBPB: disabled"*) v2_ibpb_mode=disabled ;;
                    *", IBPB"* | *"; IBPB"*) v2_ibpb_mode=conditional ;;
                    *) v2_ibpb_mode=disabled ;;
                esac
            elif [ "$g_mode" = live ]; then
                case "$g_ibpb_enabled" in
                    2) v2_ibpb_mode=always-on ;;
                    1) v2_ibpb_mode=conditional ;;
                    0) v2_ibpb_mode=disabled ;;
                    *) v2_ibpb_mode=unknown ;;
                esac
            else
                v2_ibpb_mode=unknown
            fi
            case "$v2_ibpb_mode" in
                always-on) pstatus green YES "always-on" ;;
                conditional) pstatus green YES "conditional" ;;
                disabled) pstatus yellow NO "disabled" ;;
                *) pstatus yellow UNKNOWN ;;
            esac

            # --- SMT state (used in STIBP inference and verdict) ---
            is_cpu_smt_enabled
            smt_enabled=$?
            # smt_enabled: 0=enabled, 1=disabled, 2=unknown

            # --- v2_stibp_status ---
            pr_info_nol "  * STIBP status: "
            if [ -n "$ret_sys_interface_check_fullmsg" ]; then
                case "$ret_sys_interface_check_fullmsg" in
                    *"STIBP: always-on"*) v2_stibp_status=always-on ;;
                    *"STIBP: forced"*) v2_stibp_status=forced ;;
                    *"STIBP: conditional"*) v2_stibp_status=conditional ;;
                    *"STIBP: disabled"*) v2_stibp_status=disabled ;;
                    *", STIBP"* | *"; STIBP"*) v2_stibp_status=forced ;;
                    *)
                        # No STIBP field: Intel eIBRS suppresses it (implicit cross-thread protection)
                        case "$v2_base_mode" in
                            eibrs | eibrs_lfence | eibrs_retpoline)
                                if [ "$v2_is_autoibrs" != 1 ]; then
                                    v2_stibp_status=eibrs-implicit
                                else
                                    v2_stibp_status=unknown
                                fi
                                ;;
                            *) v2_stibp_status=unknown ;;
                        esac
                        ;;
                esac
            else
                # No sysfs: use hardware capability + context to infer STIBP status
                if [ "$smt_enabled" != 0 ]; then
                    # SMT disabled or unknown: STIBP is not needed
                    v2_stibp_status=not-needed
                else
                    case "$v2_base_mode" in
                        eibrs | eibrs_lfence | eibrs_retpoline)
                            if [ "$v2_is_autoibrs" != 1 ]; then
                                # Intel eIBRS provides implicit cross-thread protection
                                v2_stibp_status=eibrs-implicit
                            elif [ -n "$cap_stibp" ]; then
                                # AMD AutoIBRS: CPU supports STIBP but can't confirm runtime state
                                v2_stibp_status=unknown
                            else
                                # No STIBP support on this CPU
                                v2_stibp_status=unavailable
                            fi
                            ;;
                        *)
                            if [ -n "$cap_stibp" ]; then
                                # CPU supports STIBP but can't confirm runtime state without sysfs
                                v2_stibp_status=unknown
                            else
                                # CPU does not support STIBP at all
                                v2_stibp_status=unavailable
                            fi
                            ;;
                    esac
                fi
            fi
            case "$v2_stibp_status" in
                always-on) pstatus green YES "always-on" ;;
                forced) pstatus green YES "forced" ;;
                conditional) pstatus green YES "conditional" ;;
                eibrs-implicit) pstatus green YES "implicit via eIBRS" ;;
                not-needed) pstatus green YES "not needed (SMT disabled)" ;;
                unavailable) pstatus red NO "CPU does not support STIBP" ;;
                disabled) pstatus yellow NO "disabled" ;;
                *) pstatus yellow UNKNOWN ;;
            esac

            # --- v2_pbrsb_status (only relevant for eIBRS) ---
            case "$v2_base_mode" in
                eibrs | eibrs_lfence | eibrs_retpoline)
                    pr_info_nol "  * PBRSB-eIBRS mitigation: "
                    if [ -n "$ret_sys_interface_check_fullmsg" ]; then
                        case "$ret_sys_interface_check_fullmsg" in
                            *"PBRSB-eIBRS: Not affected"*) v2_pbrsb_status=not-affected ;;
                            *"PBRSB-eIBRS: SW sequence"*) v2_pbrsb_status=sw-sequence ;;
                            *"PBRSB-eIBRS: Vulnerable"*) v2_pbrsb_status=vulnerable ;;
                            *) v2_pbrsb_status=unknown ;;
                        esac
                    elif [ "$g_mode" != live ] && [ -n "$g_kernel" ]; then
                        if grep -q 'PBRSB-eIBRS' "$g_kernel" 2>/dev/null; then
                            v2_pbrsb_status=sw-sequence
                        else
                            v2_pbrsb_status=unknown
                        fi
                    else
                        v2_pbrsb_status=unknown
                    fi
                    case "$v2_pbrsb_status" in
                        not-affected) pstatus green "Not affected" ;;
                        sw-sequence) pstatus green "SW sequence" ;;
                        vulnerable) pstatus red "Vulnerable" ;;
                        *) pstatus yellow UNKNOWN ;;
                    esac
                    ;;
                *) v2_pbrsb_status=n/a ;;
            esac

            # --- v2_bhi_status ---
            pr_info_nol "  * BHI mitigation: "
            if [ -n "$ret_sys_interface_check_fullmsg" ]; then
                case "$ret_sys_interface_check_fullmsg" in
                    *"BHI: Not affected"*) v2_bhi_status=not-affected ;;
                    *"BHI: BHI_DIS_S"*) v2_bhi_status=bhi_dis_s ;;
                    *"BHI: SW loop"*) v2_bhi_status=sw-loop ;;
                    *"BHI: Retpoline"*) v2_bhi_status=retpoline ;;
                    *"BHI: Vulnerable, KVM: SW loop"*) v2_bhi_status=vuln-kvm-loop ;;
                    *"BHI: Vulnerable"*) v2_bhi_status=vulnerable ;;
                    *) v2_bhi_status=unknown ;;
                esac
            elif [ "$g_mode" != live ] && [ -n "$opt_config" ] && [ -r "$opt_config" ]; then
                if grep -q '^CONFIG_\(MITIGATION_\)\?SPECTRE_BHI' "$opt_config"; then
                    if [ "$cap_bhi" = 1 ]; then
                        v2_bhi_status=bhi_dis_s
                    else
                        v2_bhi_status=sw-loop
                    fi
                else
                    v2_bhi_status=unknown
                fi
            else
                v2_bhi_status=unknown
            fi
            case "$v2_bhi_status" in
                not-affected) pstatus green "Not affected" ;;
                bhi_dis_s) pstatus green "BHI_DIS_S (hardware)" ;;
                sw-loop) pstatus green "SW loop" ;;
                retpoline) pstatus green "Retpoline" ;;
                vuln-kvm-loop) pstatus yellow "Vulnerable (KVM: SW loop)" ;;
                vulnerable) pstatus red "Vulnerable" ;;
                *) pstatus yellow UNKNOWN ;;
            esac

            # --- v2_vuln_module ---
            if [ "$g_mode" = live ] && [ -n "$ret_sys_interface_check_fullmsg" ]; then
                pr_info_nol "  * Non-retpoline module loaded: "
                if echo "$ret_sys_interface_check_fullmsg" | grep -q 'vulnerable module loaded'; then
                    v2_vuln_module=1
                    pstatus red YES
                else
                    v2_vuln_module=0
                    pstatus green NO
                fi
            fi

        fi # is_x86_kernel (retpoline + Mitigation 3)

    elif [ "$sys_interface_available" = 0 ]; then
        # we have no sysfs but were asked to use it only!
        msg="/sys vulnerability interface use forced, but it's not available!"
        status=UNK
    fi

    if ! is_cpu_affected "$cve"; then
        # override status & msg in case CPU is not vulnerable after all
        pvulnstatus "$cve" OK "your CPU vendor reported your CPU model as not affected"
    elif [ -z "$msg" ]; then
        if [ "$opt_sysfs_only" != 1 ]; then
            # --- own logic using Phase 2 variables ---
            # Helper: collect caveats for the verdict message
            _v2_caveats=''
            # Append a caveat string to the _v2_caveats list
            # Callers: check_CVE_2017_5715_linux (eIBRS, IBRS, retpoline verdict paths)
            _v2_add_caveat() { _v2_caveats="${_v2_caveats:+$_v2_caveats; }$1"; }

            # ARM branch predictor hardening (unchanged)
            if [ -n "$bp_harden" ]; then
                pvulnstatus "$cve" OK "Branch predictor hardening mitigates the vulnerability"
            elif [ -z "$bp_harden" ] && is_arm_kernel; then
                pvulnstatus "$cve" VULN "Branch predictor hardening is needed to mitigate the vulnerability"
                explain "Your kernel does not have branch predictor hardening. On kernels v5.10+, this code is compiled unconditionally so you may need a newer kernel. On older kernels (v4.16 to v5.9), recompile with the CONFIG_HARDEN_BRANCH_PREDICTOR option enabled."

            # LFENCE-only is always VULN (reclassified in v5.17)
            elif [ "$v2_base_mode" = "lfence" ]; then
                pvulnstatus "$cve" VULN "LFENCE alone is not a sufficient Spectre v2 mitigation"
                explain "LFENCE-based indirect branch mitigation was reclassified as vulnerable starting with Linux v5.17. Use retpoline (spectre_v2=retpoline) or IBRS-based mitigations (spectre_v2=eibrs or spectre_v2=ibrs) instead. If your CPU supports Enhanced IBRS, that is the preferred option."

            # eIBRS paths (eibrs / eibrs_lfence / eibrs_retpoline)
            elif [ "$v2_base_mode" = "eibrs" ] || [ "$v2_base_mode" = "eibrs_lfence" ] || [ "$v2_base_mode" = "eibrs_retpoline" ]; then
                _v2_caveats=''
                _v2_ok=1

                # BHI check: eIBRS alone doesn't protect against BHI
                if [ "$v2_bhi_status" = "vulnerable" ]; then
                    _v2_ok=0
                    _v2_add_caveat "BHI vulnerable"
                elif [ "$v2_bhi_status" = "unknown" ] && is_intel; then
                    if [ "$cap_bhi" = 0 ]; then
                        _v2_ok=0
                        _v2_add_caveat "BHI vulnerable (no BHI_DIS_S hardware support, no kernel mitigation detected)"
                    elif [ "$cap_rrsba" != 0 ]; then
                        _v2_add_caveat "BHI status unknown (kernel may lack BHI mitigation)"
                    fi
                fi

                # PBRSB check (only matters for VMM hosts)
                if [ "$v2_pbrsb_status" = "vulnerable" ]; then
                    if [ "$g_has_vmm" != 0 ] || [ "$opt_paranoid" = 1 ]; then
                        _v2_ok=0
                        _v2_add_caveat "PBRSB-eIBRS vulnerable"
                    fi
                fi

                # AutoIBRS: needs explicit STIBP (does NOT provide implicit cross-thread protection)
                if [ "$v2_is_autoibrs" = 1 ] && [ "$smt_enabled" = 0 ]; then
                    if [ "$v2_stibp_status" = "disabled" ] || [ "$v2_stibp_status" = "unavailable" ]; then
                        _v2_ok=0
                        _v2_add_caveat "STIBP not active with SMT on AMD AutoIBRS"
                    fi
                fi

                # Vulnerable module check
                if [ "$v2_vuln_module" = 1 ]; then
                    _v2_add_caveat "non-retpoline module loaded"
                fi

                # Paranoid mode
                if [ "$opt_paranoid" = 1 ]; then
                    if [ "$v2_ibpb_mode" != "always-on" ]; then
                        _v2_ok=0
                        _v2_add_caveat "IBPB not always-on"
                    fi
                    if [ "$smt_enabled" = 0 ]; then
                        _v2_ok=0
                        _v2_add_caveat "SMT enabled"
                    fi
                fi

                # eBPF caveat: eIBRS without retpoline is insufficient when unprivileged eBPF is enabled
                _ebpf_disabled=''
                if [ "$v2_base_mode" = "eibrs" ] || [ "$v2_base_mode" = "eibrs_lfence" ]; then
                    # shellcheck disable=SC2154
                    if [ -n "${SMC_MOCK_UNPRIVILEGED_BPF_DISABLED:-}" ]; then
                        _ebpf_disabled="$SMC_MOCK_UNPRIVILEGED_BPF_DISABLED"
                        g_mocked=1
                    elif [ "$g_mode" = live ] && [ -r "$g_procfs/sys/kernel/unprivileged_bpf_disabled" ]; then
                        _ebpf_disabled=$(cat "$g_procfs/sys/kernel/unprivileged_bpf_disabled" 2>/dev/null)
                        g_mockme=$(printf "%b\n%b" "$g_mockme" "SMC_MOCK_UNPRIVILEGED_BPF_DISABLED='$_ebpf_disabled'")
                    fi
                    # In paranoid mode, enabled unprivileged eBPF makes eIBRS insufficient
                    if [ "$_v2_ok" = 1 ] && [ "$_ebpf_disabled" = 0 ] && [ "$opt_paranoid" = 1 ]; then
                        _v2_ok=0
                        _v2_add_caveat "unprivileged eBPF enabled (eIBRS insufficient)"
                    fi
                fi

                # Build the base description
                case "$v2_base_mode" in
                    eibrs) _v2_desc="Enhanced / Automatic IBRS" ;;
                    eibrs_lfence) _v2_desc="Enhanced / Automatic IBRS + LFENCE" ;;
                    eibrs_retpoline) _v2_desc="Enhanced / Automatic IBRS + Retpolines" ;;
                esac

                if [ "$_v2_ok" = 1 ]; then
                    if [ -n "$_v2_caveats" ]; then
                        pvulnstatus "$cve" OK "$_v2_desc mitigates the vulnerability ($_v2_caveats)"
                    else
                        pvulnstatus "$cve" OK "$_v2_desc mitigates the vulnerability"
                    fi
                    if [ "$v2_base_mode" = "eibrs" ] || [ "$v2_base_mode" = "eibrs_lfence" ]; then
                        pr_info "    NOTE: eIBRS is considered vulnerable by the kernel when unprivileged eBPF is enabled."
                        if [ "$_ebpf_disabled" = 0 ]; then
                            pr_info "    Unprivileged eBPF is currently ENABLED (kernel.unprivileged_bpf_disabled=0): this system may be vulnerable!"
                        elif [ "$_ebpf_disabled" = 1 ] || [ "$_ebpf_disabled" = 2 ]; then
                            pr_info "    Unprivileged eBPF is currently disabled (kernel.unprivileged_bpf_disabled=$_ebpf_disabled): eIBRS is sufficient."
                        else
                            pr_info "    Could not read kernel.unprivileged_bpf_disabled, check it manually with \`sysctl kernel.unprivileged_bpf_disabled\`."
                        fi
                    fi
                else
                    pvulnstatus "$cve" VULN "$_v2_desc active but insufficient: $_v2_caveats"
                    explain "Your system uses $_v2_desc but has gaps in sub-mitigations: $_v2_caveats. Update your kernel and microcode to the latest versions. If BHI is vulnerable, a kernel with CONFIG_MITIGATION_SPECTRE_BHI or BHI_DIS_S microcode support is needed. If PBRSB-eIBRS is vulnerable, update the kernel for RSB VM exit mitigation. If STIBP is disabled on AMD AutoIBRS with SMT, add \`spectre_v2_user=on\` or disable SMT with \`nosmt\`. If unprivileged eBPF is enabled, disable it with \`sysctl -w kernel.unprivileged_bpf_disabled=1\`. In paranoid mode, disable SMT with \`nosmt\` and set \`spectre_v2_user=on\` for IBPB always-on."
                fi

            # Kernel IBRS path
            elif [ "$v2_base_mode" = "ibrs" ]; then
                _v2_caveats=''
                _v2_ok=1

                # IBRS needs IBPB for cross-process protection
                if [ "$v2_ibpb_mode" = "disabled" ]; then
                    _v2_ok=0
                    _v2_add_caveat "IBPB disabled"
                fi

                # IBRS needs STIBP or SMT-off for cross-thread protection
                if [ "$smt_enabled" = 0 ] && { [ "$v2_stibp_status" = "disabled" ] || [ "$v2_stibp_status" = "unavailable" ]; }; then
                    _v2_ok=0
                    _v2_add_caveat "STIBP not active with SMT enabled"
                fi

                # RSB filling on Skylake+
                if is_vulnerable_to_empty_rsb && [ "$rsb_filling" != 1 ]; then
                    _v2_ok=0
                    _v2_add_caveat "RSB filling missing on Skylake+"
                fi

                # BHI check
                if [ "$v2_bhi_status" = "vulnerable" ]; then
                    _v2_ok=0
                    _v2_add_caveat "BHI vulnerable"
                elif [ "$v2_bhi_status" = "unknown" ] && is_intel && [ "$cap_bhi" = 0 ]; then
                    _v2_ok=0
                    _v2_add_caveat "BHI vulnerable (no BHI_DIS_S hardware support, no kernel mitigation detected)"
                fi

                # Vulnerable module check
                if [ "$v2_vuln_module" = 1 ]; then
                    _v2_add_caveat "non-retpoline module loaded"
                fi

                # Paranoid mode
                if [ "$opt_paranoid" = 1 ]; then
                    if [ "$v2_ibpb_mode" != "always-on" ]; then
                        _v2_ok=0
                        _v2_add_caveat "IBPB not always-on"
                    fi
                    if [ "$smt_enabled" = 0 ]; then
                        _v2_ok=0
                        _v2_add_caveat "SMT enabled"
                    fi
                fi

                if [ "$_v2_ok" = 1 ]; then
                    pvulnstatus "$cve" OK "IBRS mitigates the vulnerability"
                else
                    pvulnstatus "$cve" VULN "IBRS active but insufficient: $_v2_caveats"
                    explain "Your system uses kernel IBRS but has gaps: $_v2_caveats. Ensure IBPB is enabled (spectre_v2_user=on or spectre_v2_user=prctl,ibpb). If STIBP is disabled with SMT, add spectre_v2_user=on or disable SMT with \`nosmt\`. If RSB filling is missing, update the kernel. If BHI is vulnerable, update kernel/microcode for BHI mitigation."
                fi

            # Retpoline path
            elif [ "$v2_base_mode" = "retpoline" ]; then
                _v2_caveats=''
                _v2_ok=1

                # Retpoline compiler check
                if [ "$retpoline_compiler" = 0 ]; then
                    _v2_ok=0
                    _v2_add_caveat "not compiled with retpoline-aware compiler"
                fi

                # Red Hat runtime disable check
                if [ "$retp_enabled" = 0 ]; then
                    _v2_ok=0
                    _v2_add_caveat "retpoline disabled at runtime"
                fi

                # RSB filling on Skylake+ (empty RSB falls back to BTB)
                if is_vulnerable_to_empty_rsb && [ "$rsb_filling" != 1 ]; then
                    _v2_ok=0
                    _v2_add_caveat "RSB filling missing on Skylake+"
                fi

                # BHI: retpoline only mitigates BHI if RRSBA is disabled
                if [ "$v2_bhi_status" = "vulnerable" ]; then
                    _v2_ok=0
                    _v2_add_caveat "BHI vulnerable"
                elif [ "$v2_bhi_status" = "unknown" ] && is_intel; then
                    if [ "$cap_bhi" = 0 ] && [ "$cap_rrsba" = 1 ]; then
                        _v2_ok=0
                        _v2_add_caveat "BHI vulnerable (no BHI_DIS_S hardware support, RRSBA bypasses retpoline)"
                    elif [ "$cap_rrsba" = 1 ]; then
                        _v2_add_caveat "BHI status unknown with RRSBA"
                    fi
                fi

                # Vulnerable module
                if [ "$v2_vuln_module" = 1 ]; then
                    _v2_ok=0
                    _v2_add_caveat "non-retpoline module loaded"
                fi

                # IBPB check: retpoline without IBPB is weaker
                if [ "$v2_ibpb_mode" = "disabled" ] || { [ -z "$g_ibpb_enabled" ] || [ "$g_ibpb_enabled" = 0 ]; }; then
                    if [ "$opt_paranoid" = 1 ]; then
                        _v2_ok=0
                        _v2_add_caveat "IBPB disabled"
                    else
                        _v2_add_caveat "IBPB disabled (recommended)"
                    fi
                fi

                # Paranoid mode: require SMT off, IBPB always-on
                if [ "$opt_paranoid" = 1 ]; then
                    if [ "$v2_ibpb_mode" != "always-on" ] && [ "$v2_ibpb_mode" != "disabled" ]; then
                        _v2_ok=0
                        _v2_add_caveat "IBPB not always-on"
                    fi
                    if [ "$smt_enabled" = 0 ]; then
                        _v2_ok=0
                        _v2_add_caveat "SMT enabled"
                    fi
                fi

                if [ "$_v2_ok" = 1 ]; then
                    if [ -n "$_v2_caveats" ]; then
                        pvulnstatus "$cve" OK "Retpolines mitigate the vulnerability ($_v2_caveats)"
                        if echo "$_v2_caveats" | grep -q 'IBPB'; then
                            if [ -n "$cap_ibpb" ]; then
                                pr_warn "You should enable IBPB to complete retpoline as a Variant 2 mitigation"
                            else
                                pr_warn "IBPB is considered as a good addition to retpoline for Variant 2 mitigation, but your CPU microcode doesn't support it"
                            fi
                        fi
                    else
                        pvulnstatus "$cve" OK "Retpolines + IBPB mitigate the vulnerability"
                    fi
                else
                    pvulnstatus "$cve" VULN "Retpoline active but insufficient: $_v2_caveats"
                    explain "Your system uses retpoline but has gaps: $_v2_caveats. Ensure the kernel was compiled with a retpoline-aware compiler. Enable IBPB (spectre_v2_user=on). If RSB filling is missing on Skylake+, update the kernel. If BHI is vulnerable, update kernel/microcode. In paranoid mode, disable SMT with \`nosmt\` and set \`spectre_v2_user=on\`."
                fi

            # Legacy fallback: IBRS+IBPB from debugfs on old systems without sysfs
            elif [ -n "$g_ibrs_enabled" ] && [ "$g_ibrs_enabled" -ge 1 ] 2>/dev/null && [ -n "$g_ibpb_enabled" ] && [ "$g_ibpb_enabled" -ge 1 ] 2>/dev/null; then
                if [ "$g_ibrs_enabled" = 4 ]; then
                    pvulnstatus "$cve" OK "Enhanced IBRS + IBPB are mitigating the vulnerability"
                else
                    pvulnstatus "$cve" OK "IBRS + IBPB are mitigating the vulnerability"
                fi
            elif [ "$g_ibpb_enabled" = 2 ] && [ "$smt_enabled" != 0 ]; then
                pvulnstatus "$cve" OK "Full IBPB is mitigating the vulnerability"

            # No-runtime mode fallback
            elif [ "$g_mode" != live ]; then
                if [ "$retpoline" = 1 ] && [ -n "$g_ibpb_supported" ]; then
                    pvulnstatus "$cve" OK "no-runtime mode: kernel supports retpoline + IBPB to mitigate the vulnerability"
                elif [ -n "$g_ibrs_supported" ] && [ -n "$g_ibpb_supported" ]; then
                    pvulnstatus "$cve" OK "no-runtime mode: kernel supports IBRS + IBPB to mitigate the vulnerability"
                elif [ "$cap_ibrs_all" = 1 ] || [ "$cap_autoibrs" = 1 ]; then
                    pvulnstatus "$cve" OK "no-runtime mode: CPU supports Enhanced / Automatic IBRS"
                # CONFIG_MITIGATION_SPECTRE_V2 (v6.12+): top-level on/off for all Spectre V2 mitigations
                elif [ -n "$opt_config" ] && [ -r "$opt_config" ] && grep -q '^CONFIG_MITIGATION_SPECTRE_V2=y' "$opt_config"; then
                    pvulnstatus "$cve" OK "no-runtime mode: kernel has Spectre V2 mitigation framework enabled (CONFIG_MITIGATION_SPECTRE_V2)"
                elif [ "$g_ibrs_can_tell" != 1 ]; then
                    pvulnstatus "$cve" UNK "no-runtime mode: not enough information"
                    explain "Re-run this script with root privileges, and give it the kernel image (--kernel), the kernel configuration (--config) and the System.map file (--map) corresponding to the kernel you would like to inspect."
                fi
            fi

            # Catch-all: if no verdict was reached above, it's VULN
            if [ "$g_pvulnstatus_last_cve" != "$cve" ]; then
                if is_intel || is_amd || is_hygon; then
                    pvulnstatus "$cve" VULN "Your CPU is affected and no sufficient mitigation was detected"
                    explain "To mitigate this vulnerability, you need one of: (1) Enhanced IBRS / Automatic IBRS (eIBRS) -- requires CPU microcode support; preferred for modern CPUs. (2) Kernel IBRS (spectre_v2=ibrs) + IBPB -- requires IBRS-capable microcode. (3) Retpoline (spectre_v2=retpoline) + IBPB -- requires a retpoline-aware compiler and IBPB-capable microcode. For Skylake+ CPUs, options 1 or 2 are preferred as retpoline needs RSB filling. Update your kernel and CPU microcode to the latest versions."
                else
                    if [ "$sys_interface_available" = 1 ]; then
                        pvulnstatus "$cve" "$status" "$ret_sys_interface_check_fullmsg"
                    else
                        pvulnstatus "$cve" VULN "no known mitigation exists for your CPU vendor ($cpu_vendor)"
                    fi
                fi
            fi
        else
            # --sysfs-only: Phase 2 variables are unset, fall back to the
            # raw sysfs result (status + fullmsg were set in Phase 1).
            pvulnstatus "$cve" "$status" "$ret_sys_interface_check_fullmsg"
        fi
    else
        # msg was explicitly set by the "sysfs not available" elif above.
        pvulnstatus "$cve" "$status" "$msg"
    fi
}

# Sets: vulnstatus
check_CVE_2017_5715_bsd() {
    local ibrs_disabled ibrs_active retpoline nb_thunks
    pr_info "* Mitigation 1"
    pr_info_nol "  * Kernel supports IBRS: "
    ibrs_disabled=$(sysctl -n hw.ibrs_disable 2>/dev/null)
    if [ -z "$ibrs_disabled" ]; then
        pstatus yellow NO
    else
        pstatus green YES
    fi

    pr_info_nol "  * IBRS enabled and active: "
    ibrs_active=$(sysctl -n hw.ibrs_active 2>/dev/null)
    if [ "$ibrs_active" = 1 ]; then
        pstatus green YES
    else
        pstatus yellow NO
    fi

    pr_info "* Mitigation 2"
    pr_info_nol "  * Kernel compiled with RETPOLINE: "
    retpoline=0
    if [ -n "$g_kernel_err" ]; then
        pstatus yellow UNKNOWN "couldn't check ($g_kernel_err)"
    else
        if ! command -v "${opt_arch_prefix}readelf" >/dev/null 2>&1; then
            pstatus yellow UNKNOWN "missing '${opt_arch_prefix}readelf' tool, please install it, usually it's in the binutils package"
        else
            nb_thunks=$("${opt_arch_prefix}readelf" -s "$g_kernel" | grep -c -e __llvm_retpoline_ -e __llvm_external_retpoline_ -e __x86_indirect_thunk_)
            if [ "$nb_thunks" -gt 0 ]; then
                retpoline=1
                pstatus green YES "found $nb_thunks thunk(s)"
            else
                pstatus yellow NO
            fi
        fi
    fi

    if ! is_cpu_affected "$cve"; then
        # override status & msg in case CPU is not vulnerable after all
        pvulnstatus "$cve" OK "your CPU vendor reported your CPU model as not affected"
    elif [ "$retpoline" = 1 ]; then
        pvulnstatus "$cve" OK "Retpoline mitigates the vulnerability"
    elif [ "$ibrs_active" = 1 ]; then
        pvulnstatus "$cve" OK "IBRS mitigates the vulnerability"
    elif [ "$ibrs_disabled" = 0 ]; then
        pvulnstatus "$cve" VULN "IBRS is supported by your kernel but your CPU microcode lacks support"
        explain "The microcode of your CPU needs to be upgraded to be able to use IBRS. Availability of a microcode update for you CPU model depends on your CPU vendor. You can usually find out online if a microcode update is available for your CPU by searching for your CPUID (indicated in the Hardware Check section). To do a microcode update, you can search the ports for the \`cpupdate\` tool. Microcode updates done this way are not reboot-proof, so be sure to do it every time the system boots up."
    elif [ "$ibrs_disabled" = 1 ]; then
        pvulnstatus "$cve" VULN "IBRS is supported but administratively disabled on your system"
        explain "To enable IBRS, use \`sysctl hw.ibrs_disable=0\`"
    else
        pvulnstatus "$cve" VULN "IBRS is needed to mitigate the vulnerability but your kernel is missing support"
        explain "You need to either upgrade your kernel or recompile yourself a more recent version having IBRS support"
    fi
}
