# vim: set ts=4 sw=4 sts=4 et:
###############################
# CVE-2022-29900, Retbleed (AMD), Arbitrary Speculative Code Execution with Return Instructions

check_CVE_2022_29900() {
    check_cve 'CVE-2022-29900'
}

check_CVE_2022_29900_linux() {
    local status sys_interface_available msg kernel_retbleed kernel_retbleed_err kernel_unret kernel_ibpb_entry smt_enabled
    status=UNK
    sys_interface_available=0
    msg=''

    #
    # Kernel source inventory for retbleed (CVE-2022-29900 / CVE-2022-29901)
    #
    # --- sysfs messages ---
    # all versions:
    #   "Not affected"                                  (cpu_show_common, pre-existing)
    #
    # --- mainline ---
    # 6b80b59b3555 (v5.19-rc7, initial retbleed sysfs):
    #   "Vulnerable\n"                                  (hardcoded, no enum yet)
    # 7fbf47c7ce50 (v5.19-rc7, retbleed= boot parameter):
    #   "Vulnerable"                                    (RETBLEED_MITIGATION_NONE)
    #   "Mitigation: untrained return thunk"            (RETBLEED_MITIGATION_UNRET)
    #   "Vulnerable: untrained return thunk on non-Zen uarch"  (UNRET on non-AMD/Hygon)
    # 6ad0ad2bf8a6 (v5.19-rc7, Intel mitigations):
    #   "Mitigation: IBRS"                              (RETBLEED_MITIGATION_IBRS)
    #   "Mitigation: Enhanced IBRS"                     (RETBLEED_MITIGATION_EIBRS)
    # 3ebc17006888 (v5.19-rc7, retbleed=ibpb):
    #   "Mitigation: IBPB"                              (RETBLEED_MITIGATION_IBPB)
    # e8ec1b6e08a2 (v5.19-rc7, STIBP for JMP2RET):
    #   UNRET now appends SMT status:
    #   "Mitigation: untrained return thunk; SMT disabled"
    #   "Mitigation: untrained return thunk; SMT enabled with STIBP protection"
    #   "Mitigation: untrained return thunk; SMT vulnerable"
    # e6cfcdda8cbe (v6.0-rc1, STIBP for IBPB):
    #   IBPB now appends SMT status, non-AMD message changed:
    #   "Vulnerable: untrained return thunk / IBPB on non-AMD based uarch"
    #   "Mitigation: IBPB; SMT disabled"
    #   "Mitigation: IBPB; SMT enabled with STIBP protection"
    #   "Mitigation: IBPB; SMT vulnerable"
    # d82a0345cf21 (v6.2-rc1, call depth tracking):
    #   "Mitigation: Stuffing"                          (RETBLEED_MITIGATION_STUFF)
    # e3b78a7ad5ea (v6.16-rc1, restructure):
    #   added RETBLEED_MITIGATION_AUTO (internal, resolved before display)
    #   no new sysfs strings
    #
    # all messages start with either "Not affected", "Vulnerable", or "Mitigation"
    #
    # --- stable backports ---
    # 4.14.y, 4.19.y, 5.4.y: Intel-only mitigations (IBRS, eIBRS); no UNRET, IBPB, STUFF;
    #   no SMT status display; simplified retbleed_show_state().
    # 5.10.y, 5.15.y, 6.1.y: full mitigations (NONE, UNRET, IBPB, IBRS, EIBRS);
    #   SMT status appended for UNRET/IBPB; no STUFF.
    # 6.6.y, 6.12.y: adds STUFF (call depth tracking). 6.12.y uses INTEL_ model prefix.
    # all stable: single retbleed_select_mitigation() (no update/apply split).
    #
    # --- RHEL/CentOS ---
    # centos7 (~4.18): NONE, UNRET, IBPB, IBRS, EIBRS; no STUFF; SMT status for UNRET;
    #   no Hygon check; no UNRET_ENTRY/IBPB_ENTRY/IBRS_ENTRY Kconfig symbols;
    #   unique cpu_in_retbleed_whitelist() function for Intel.
    # rocky8 (~4.18/5.14): NONE, UNRET, IBPB, IBRS, EIBRS; no STUFF;
    #   CONFIG_CPU_UNRET_ENTRY, CONFIG_CPU_IBPB_ENTRY, CONFIG_CPU_IBRS_ENTRY (old names).
    # rocky9 (~6.x): same as mainline; CONFIG_MITIGATION_* names; has STUFF.
    # rocky10 (~6.12+): same as mainline; has select/update/apply split.
    #
    # --- Kconfig symbols ---
    # f43b9876e857 (v5.19-rc7): CONFIG_CPU_UNRET_ENTRY, CONFIG_CPU_IBPB_ENTRY,
    #   CONFIG_CPU_IBRS_ENTRY
    # 80e4c1cd42ff (v6.2-rc1): CONFIG_CALL_DEPTH_TRACKING
    # ac61d43983a4 (v6.9-rc1): renamed to CONFIG_MITIGATION_UNRET_ENTRY,
    #   CONFIG_MITIGATION_IBPB_ENTRY, CONFIG_MITIGATION_IBRS_ENTRY,
    #   CONFIG_MITIGATION_CALL_DEPTH_TRACKING
    # 894e28857c11 (v6.12-rc1): CONFIG_MITIGATION_RETBLEED (master switch)
    #
    # --- kernel functions (for $opt_map / System.map) ---
    # 7fbf47c7ce50 (v5.19-rc7): retbleed_select_mitigation()
    # e3b78a7ad5ea (v6.16-rc1): split into retbleed_select_mitigation() +
    #   retbleed_update_mitigation() + retbleed_apply_mitigation()
    # vendor kernels: centos7/rocky8/rocky9 have retbleed_select_mitigation() only;
    #   rocky10 has the full split.
    #
    # --- CPU affection logic (for is_cpu_affected) ---
    # X86_BUG_RETBLEED is set when X86_FEATURE_BTC_NO is NOT set AND either:
    #   (a) CPU matches cpu_vuln_blacklist[] RETBLEED entries, OR
    #   (b) ARCH_CAP_RSBA is set in IA32_ARCH_CAPABILITIES MSR
    # 6b80b59b3555 (v5.19-rc7, initial AMD):
    #   AMD: family 0x15, 0x16, 0x17; Hygon: family 0x18
    # 6ad0ad2bf8a6 (v5.19-rc7, Intel):
    #   Intel: SKYLAKE_L, SKYLAKE, SKYLAKE_X, KABYLAKE_L, KABYLAKE,
    #     ICELAKE_L, COMETLAKE, COMETLAKE_L, LAKEFIELD, ROCKETLAKE
    #   + any Intel with ARCH_CAP_RSBA set
    # 26aae8ccbc19 (v5.19-rc7, BTC_NO):
    #   AMD Zen 3+ with BTC_NO are excluded
    # f54d45372c6a (post-v5.19, Cannon Lake):
    #   Intel: + CANNONLAKE_L
    # immunity: X86_FEATURE_BTC_NO (AMD) — Zen 3+ declare not affected
    # vendor scope: AMD (0x15-0x17), Hygon (0x18), Intel (Skylake through Rocket Lake + RSBA)
    #

    if sys_interface_check "$VULN_SYSFS_BASE/retbleed"; then
        # this kernel has the /sys interface, trust it over everything
        sys_interface_available=1
        status=$ret_sys_interface_check_status
    fi

    if [ "$opt_sysfs_only" != 1 ]; then
        pr_info_nol "* Kernel supports mitigation: "
        if [ -n "$g_kernel_err" ]; then
            kernel_retbleed_err="$g_kernel_err"
        elif grep -q 'retbleed' "$g_kernel"; then
            kernel_retbleed="found retbleed mitigation logic in kernel image"
        fi
        if [ -z "$kernel_retbleed" ] && [ -n "$opt_map" ]; then
            if grep -q 'retbleed_select_mitigation' "$opt_map"; then
                kernel_retbleed="found retbleed_select_mitigation in System.map"
            fi
        fi
        if [ -n "$kernel_retbleed" ]; then
            pstatus green YES "$kernel_retbleed"
        elif [ -n "$kernel_retbleed_err" ]; then
            pstatus yellow UNKNOWN "$kernel_retbleed_err"
        else
            pstatus yellow NO
        fi

        pr_info_nol "* Kernel compiled with UNRET_ENTRY support (untrained return thunk): "
        if [ -r "$opt_config" ]; then
            # CONFIG_CPU_UNRET_ENTRY: Linux < 6.9
            # CONFIG_MITIGATION_UNRET_ENTRY: Linux >= 6.9
            if grep -Eq '^CONFIG_(CPU|MITIGATION)_UNRET_ENTRY=y' "$opt_config"; then
                pstatus green YES
                kernel_unret="CONFIG_(CPU|MITIGATION)_UNRET_ENTRY=y found in kernel config"
            else
                pstatus yellow NO
            fi
        else
            if [ -n "$g_kernel_err" ]; then
                pstatus yellow UNKNOWN "$g_kernel_err"
            elif [ -n "$kernel_retbleed" ]; then
                # if the kernel has retbleed logic, assume UNRET_ENTRY is likely compiled in
                # (we can't tell for certain without the config)
                kernel_unret="retbleed mitigation logic present in kernel (UNRET_ENTRY status unknown)"
                pstatus yellow UNKNOWN "kernel has retbleed mitigation but config not available to verify"
            else
                pstatus yellow NO "your kernel is too old and doesn't have the retbleed mitigation logic"
            fi
        fi

        pr_info_nol "* Kernel compiled with IBPB_ENTRY support: "
        if [ -r "$opt_config" ]; then
            # CONFIG_CPU_IBPB_ENTRY: Linux < 6.9
            # CONFIG_MITIGATION_IBPB_ENTRY: Linux >= 6.9
            if grep -Eq '^CONFIG_(CPU|MITIGATION)_IBPB_ENTRY=y' "$opt_config"; then
                pstatus green YES
                kernel_ibpb_entry="CONFIG_(CPU|MITIGATION)_IBPB_ENTRY=y found in kernel config"
            else
                pstatus yellow NO
            fi
        else
            if [ -n "$g_kernel_err" ]; then
                pstatus yellow UNKNOWN "$g_kernel_err"
            elif [ -n "$kernel_retbleed" ]; then
                kernel_ibpb_entry="retbleed mitigation logic present in kernel (IBPB_ENTRY status unknown)"
                pstatus yellow UNKNOWN "kernel has retbleed mitigation but config not available to verify"
            else
                pstatus yellow NO "your kernel is too old and doesn't have the retbleed mitigation logic"
            fi
        fi

        # Zen/Zen+/Zen2: check IBPB microcode support and SMT
        if [ "$cpu_family" = $((0x17)) ]; then
            pr_info_nol "* CPU supports IBPB: "
            if [ "$g_mode" = live ]; then
                if [ -n "$cap_ibpb" ]; then
                    pstatus green YES "$cap_ibpb"
                else
                    pstatus yellow NO
                fi
            else
                pstatus blue N/A "not testable in no-runtime mode"
            fi

            pr_info_nol "* Hyper-Threading (SMT) is enabled: "
            is_cpu_smt_enabled
            smt_enabled=$?
            if [ "$smt_enabled" = 0 ]; then
                pstatus yellow YES
            else
                pstatus green NO
            fi
        fi

    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 msg is empty, sysfs check didn't fill it, rely on our own test
        if [ "$opt_sysfs_only" != 1 ]; then
            if [ "$cpu_family" = $((0x17)) ]; then
                # Zen/Zen+/Zen2
                if [ -z "$kernel_retbleed" ]; then
                    pvulnstatus "$cve" VULN "Your kernel is too old and doesn't have the retbleed mitigation logic"
                elif [ "$opt_paranoid" = 1 ] && [ "$smt_enabled" = 0 ]; then
                    pvulnstatus "$cve" VULN "SMT is enabled, which weakens the IBPB-based mitigation"
                    explain "For Zen/Zen+/Zen2 CPUs in paranoid mode, proper mitigation needs SMT to be disabled\n" \
                        "(this can be done by adding \`nosmt\` to your kernel command line), because IBPB alone\n" \
                        "doesn't fully protect cross-thread speculation."
                elif [ -z "$kernel_unret" ] && [ -z "$kernel_ibpb_entry" ]; then
                    pvulnstatus "$cve" VULN "Your kernel doesn't have either UNRET_ENTRY or IBPB_ENTRY compiled-in"
                elif [ "$smt_enabled" = 0 ] && [ -z "$cap_ibpb" ] && [ "$g_mode" = live ]; then
                    pvulnstatus "$cve" VULN "SMT is enabled and your microcode doesn't support IBPB"
                    explain "Update your CPU microcode to get IBPB support, or disable SMT by adding\n" \
                        "\`nosmt\` to your kernel command line."
                else
                    pvulnstatus "$cve" OK "Your kernel and CPU support mitigation"
                fi
            elif [ "$cpu_family" = $((0x15)) ] || [ "$cpu_family" = $((0x16)) ]; then
                # older AMD families: basic mitigation check
                if [ -z "$kernel_retbleed" ]; then
                    pvulnstatus "$cve" VULN "Your kernel is too old and doesn't have the retbleed mitigation logic"
                elif [ -n "$kernel_unret" ] || [ -n "$kernel_ibpb_entry" ]; then
                    pvulnstatus "$cve" OK "Your kernel supports mitigation"
                else
                    pvulnstatus "$cve" VULN "Your kernel doesn't have UNRET_ENTRY or IBPB_ENTRY compiled-in"
                fi
            else
                # not supposed to happen
                pvulnstatus "$cve" OK "your CPU vendor reported your CPU model as not affected"
            fi
        else
            pvulnstatus "$cve" "$status" "$ret_sys_interface_check_fullmsg"
        fi
    else
        pvulnstatus "$cve" "$status" "$msg"
    fi
}

check_CVE_2022_29900_bsd() {
    if ! is_cpu_affected "$cve"; then
        pvulnstatus "$cve" OK "your CPU vendor reported your CPU model as not affected"
    else
        pvulnstatus "$cve" UNK "your CPU is affected, but mitigation detection has not yet been implemented for BSD in this script"
    fi
}
