# vim: set ts=4 sw=4 sts=4 et:
###############################
# CVE-2023-20593, Zenbleed, Cross-Process Information Leak

check_CVE_2023_20593() {
    check_cve 'CVE-2023-20593'
}

check_CVE_2023_20593_linux() {
    local status sys_interface_available msg kernel_zenbleed kernel_zenbleed_err fp_backup_fix ucode_zenbleed zenbleed_print_vuln ret
    status=UNK
    sys_interface_available=0
    msg=''
    if [ "$opt_sysfs_only" != 1 ]; then
        pr_info_nol "* Zenbleed mitigation is supported by kernel: "
        kernel_zenbleed=''
        if [ -n "$g_kernel_err" ]; then
            kernel_zenbleed_err="$g_kernel_err"
        # commit 522b1d69219d8f083173819fde04f994aa051a98
        elif grep -q 'Zenbleed:' "$g_kernel"; then
            kernel_zenbleed="found zenbleed message in kernel image"
        fi
        if [ -n "$kernel_zenbleed" ]; then
            pstatus green YES "$kernel_zenbleed"
        elif [ -n "$kernel_zenbleed_err" ]; then
            pstatus yellow UNKNOWN "$kernel_zenbleed_err"
        else
            pstatus yellow NO
        fi
        pr_info_nol "* Zenbleed kernel mitigation enabled and active: "
        if [ "$g_mode" = live ]; then
            # read the DE_CFG MSR, we want to check the 9th bit
            # don't do it on non-Zen2 AMD CPUs or later, aka Family 17h,
            # as the behavior could be unknown on others
            if is_amd && [ "$cpu_family" -ge $((0x17)) ]; then
                read_msr 0xc0011029
                ret=$?
                if [ "$ret" = "$READ_MSR_RET_OK" ]; then
                    if [ $((ret_read_msr_value_lo >> 9 & 1)) -eq 1 ]; then
                        pstatus green YES "FP_BACKUP_FIX bit set in DE_CFG"
                        fp_backup_fix=1
                    else
                        pstatus yellow NO "FP_BACKUP_FIX is cleared in DE_CFG"
                        fp_backup_fix=0
                    fi
                elif [ "$ret" = "$READ_MSR_RET_KO" ]; then
                    pstatus yellow UNKNOWN "Couldn't read the DE_CFG MSR"
                else
                    pstatus yellow UNKNOWN "$ret_read_msr_msg"
                fi
            else
                fp_backup_fix=0
                pstatus blue N/A "CPU is incompatible"
            fi
        else
            pstatus blue N/A "not testable in no-runtime mode"
        fi

        pr_info_nol "* Zenbleed mitigation is supported by CPU microcode: "
        has_zenbleed_fixed_firmware
        ret=$?
        if [ "$ret" -eq 0 ]; then
            pstatus green YES
            ucode_zenbleed=1
        elif [ "$ret" -eq 1 ]; then
            pstatus yellow NO
            ucode_zenbleed=2
        else
            pstatus yellow UNKNOWN
            ucode_zenbleed=3
        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
        zenbleed_print_vuln=0
        if [ "$g_mode" = live ]; then
            if [ "$fp_backup_fix" = 1 ] && [ "$ucode_zenbleed" = 1 ]; then
                # this should never happen, but if it does, it's interesting to know
                pvulnstatus "$cve" OK "Both your CPU microcode and kernel are mitigating Zenbleed"
            elif [ "$ucode_zenbleed" = 1 ]; then
                pvulnstatus "$cve" OK "Your CPU microcode mitigates Zenbleed"
            elif [ "$fp_backup_fix" = 1 ]; then
                pvulnstatus "$cve" OK "Your kernel mitigates Zenbleed"
            else
                zenbleed_print_vuln=1
            fi
        else
            if [ "$ucode_zenbleed" = 1 ]; then
                pvulnstatus "$cve" OK "Your CPU microcode mitigates Zenbleed"
            elif [ -n "$kernel_zenbleed" ]; then
                pvulnstatus "$cve" OK "Your kernel mitigates Zenbleed"
            else
                zenbleed_print_vuln=1
            fi
        fi
        if [ "$zenbleed_print_vuln" = 1 ]; then
            if [ "$g_mode" = live ] && is_running_as_guest; then
                # Both Zenbleed mitigations are applied at the host level: an
                # up-to-date microcode, or the host kernel setting FP_BACKUP_FIX
                # in DE_CFG. From inside a guest we can't read that MSR and can't
                # trust the microcode version the hypervisor presents, so we can't
                # confirm or deny the mitigation -- don't cry VULN (#488).
                pvulnstatus "$cve" UNK "Zenbleed mitigation can't be verified from inside a VM guest ($g_is_guest_vm_reason): it may be applied by the hypervisor host, but that isn't observable from here"
                explain "Zenbleed is mitigated either by an up-to-date CPU microcode or by the host kernel setting the FP_BACKUP_FIX bit (DE_CFG MSR 0xc0011029 bit 9). Both are host-level: a guest can neither read that MSR nor trust the microcode version the hypervisor presents (see the VM note in the hardware section above). Re-run this script on the hypervisor host to get an accurate result."
            else
                pvulnstatus "$cve" VULN "Your kernel is too old to mitigate Zenbleed and your CPU microcode doesn't mitigate it either"
                explain "Your CPU vendor may have a new microcode for your CPU model that mitigates this issue (refer to the hardware section above).\n " \
                    "Otherwise, the Linux kernel is able to mitigate this issue regardless of the microcode version you have, but in this case\n " \
                    "your kernel is too old to support this, your Linux distribution vendor might have a more recent version you should upgrade to.\n " \
                    "Note that either having an up to date microcode OR an up to date kernel is enough to mitigate this issue.\n " \
                    "To manually mitigate the issue right now, you may use the following command: \`wrmsr -a 0xc0011029 \$((\$(rdmsr -c 0xc0011029) | (1<<9)))\`,\n " \
                    "however note that this manual mitigation will only be active until the next reboot."
            fi
        fi
        unset zenbleed_print_vuln
    else
        pvulnstatus "$cve" "$status" "$msg"
    fi
}

check_CVE_2023_20593_bsd() {
    local zenbleed_enable zenbleed_state kernel_zenbleed
    pr_info_nol "* Kernel supports Zenbleed mitigation (machdep.mitigations.zenbleed.enable): "
    zenbleed_enable=$(sysctl -n machdep.mitigations.zenbleed.enable 2>/dev/null)
    if [ -n "$zenbleed_enable" ]; then
        kernel_zenbleed=1
        case "$zenbleed_enable" in
            0) pstatus yellow YES "force disabled" ;;
            1) pstatus green YES "force enabled" ;;
            2) pstatus green YES "automatic (default)" ;;
            *) pstatus yellow YES "unknown value: $zenbleed_enable" ;;
        esac
    else
        kernel_zenbleed=0
        pstatus yellow NO
    fi

    pr_info_nol "* Zenbleed mitigation state: "
    zenbleed_state=$(sysctl -n machdep.mitigations.zenbleed.state 2>/dev/null)
    if [ -n "$zenbleed_state" ]; then
        if echo "$zenbleed_state" | grep -qi 'not.applicable\|mitigation.enabled'; then
            pstatus green YES "$zenbleed_state"
        elif echo "$zenbleed_state" | grep -qi 'mitigation.disabled'; then
            pstatus yellow NO "$zenbleed_state"
        else
            pstatus yellow UNKNOWN "$zenbleed_state"
        fi
    else
        pstatus yellow NO "sysctl not available"
    fi

    if ! is_cpu_affected "$cve"; then
        pvulnstatus "$cve" OK "your CPU vendor reported your CPU model as not affected"
    elif [ "$kernel_zenbleed" = 1 ] && [ "$zenbleed_enable" != 0 ]; then
        if [ -n "$zenbleed_state" ] && echo "$zenbleed_state" | grep -qi 'mitigation.enabled'; then
            pvulnstatus "$cve" OK "Zenbleed mitigation is enabled ($zenbleed_state)"
        elif [ -n "$zenbleed_state" ] && echo "$zenbleed_state" | grep -qi 'not.applicable'; then
            pvulnstatus "$cve" OK "Zenbleed mitigation not applicable to this CPU ($zenbleed_state)"
        else
            pvulnstatus "$cve" OK "Zenbleed mitigation is enabled"
        fi
    elif [ "$kernel_zenbleed" = 1 ] && [ "$zenbleed_enable" = 0 ]; then
        pvulnstatus "$cve" VULN "Zenbleed mitigation is supported but force disabled"
        explain "To re-enable Zenbleed mitigation, run \`sysctl machdep.mitigations.zenbleed.enable=2' for automatic mode.\n " \
            "To make this persistent, add 'machdep.mitigations.zenbleed.enable=2' to /etc/sysctl.conf."
    else
        pvulnstatus "$cve" VULN "your kernel doesn't support Zenbleed mitigation, update it"
        explain "Your CPU vendor may also have a new microcode for your CPU model that mitigates this issue.\n " \
            "Updating to FreeBSD 14.0 or later will provide kernel-level Zenbleed mitigation via the\n " \
            "machdep.mitigations.zenbleed sysctl."
    fi
}
