# vim: set ts=4 sw=4 sts=4 et:
###############################
# CVE-2018-3646, Foreshadow-NG (VMM), L1 Terminal Fault

check_CVE_2018_3646() {
    check_cve 'CVE-2018-3646'
}

check_CVE_2018_3646_linux() {
    local status sys_interface_available msg l1d_mode ept_disabled l1d_kernel l1d_kernel_err l1d_xen_hardware l1d_xen_hypervisor l1d_xen_pv_domU smt_enabled
    status=UNK
    sys_interface_available=0
    msg=''
    if sys_interface_check "$VULN_SYSFS_BASE/l1tf" '.*' quiet; then
        # this kernel has the /sys interface, trust it over everything
        sys_interface_available=1
        # quiet mode doesn't set ret_sys_interface_check_status, derive it ourselves.
        #
        # Complete sysfs message inventory for l1tf, traced via git blame
        # on mainline (~/linux) and stable (~/linux-stable):
        #
        # all versions:
        #   "Not affected"                          (cpu_show_common, d1059518b4789)
        #   "Vulnerable"                            (cpu_show_common fallthrough, d1059518b4789)
        #
        # --- mainline ---
        # 17dbca119312 (v4.18-rc1, initial l1tf sysfs):
        #   "Mitigation: Page Table Inversion"
        # 72c6d2db64fa (v4.18-rc1, renamed + added VMX reporting):
        #   "Mitigation: PTE Inversion"                                  (no KVM_INTEL, or VMX=AUTO)
        #   "Mitigation: PTE Inversion; VMX: SMT <smt>, L1D <flush>"    (KVM_INTEL enabled)
        #     <flush>: auto | vulnerable | conditional cache flushes | cache flushes
        # a7b9020b06ec (v4.18-rc1, added EPT disabled state):
        #     <flush>: + EPT disabled
        # ea156d192f52 (v4.18-rc7, reordered VMX/SMT fields):
        #   "Mitigation: PTE Inversion; VMX: EPT disabled"              (no SMT part)
        #   "Mitigation: PTE Inversion; VMX: vulnerable"                (NEVER + SMT active, no SMT part)
        #   "Mitigation: PTE Inversion; VMX: <flush>, SMT <smt>"        (all other cases)
        # 8e0b2b916662 (v4.18, added flush not necessary):
        #     <flush>: + flush not necessary
        # 130d6f946f6f (v4.20-rc4, no string change):
        #     SMT detection changed from cpu_smt_control to sched_smt_active()
        #
        # --- stable backports ---
        # 4.4.y: no VMX reporting (only "PTE Inversion" / "Vulnerable" / "Not affected").
        #   initially backported as "Page Table Inversion" (bf0cca01b873),
        #   renamed to "PTE Inversion" in stable-only commit 6db8c0882912 (May 2019).
        # 4.9.y, 4.14.y: full VMX reporting, post-reorder format.
        #   the pre-reorder format ("SMT <smt>, L1D <flush>") and the post-reorder
        #   format ("VMX: <flush>, SMT <smt>") landed in the same stable release
        #   (4.9.120, 4.14.63), so no stable release ever shipped the pre-reorder format.
        #   sched_smt_active() backported (same strings, different runtime behavior).
        # 4.17.y, 4.18.y: full VMX reporting, post-reorder format.
        #   still uses cpu_smt_control (sched_smt_active() not backported to these EOL branches).
        #
        # <smt> is one of: vulnerable | disabled
        #
        # all messages start with either "Not affected", "Mitigation", or "Vulnerable"
        if echo "$ret_sys_interface_check_fullmsg" | grep -qEi '^(Not affected|Mitigation)'; then
            status=OK
        elif echo "$ret_sys_interface_check_fullmsg" | grep -qi '^Vulnerable'; then
            status=VULN
        fi
    fi
    l1d_mode=-1
    if [ "$opt_sysfs_only" != 1 ]; then
        check_has_vmm

        pr_info "* Mitigation 1 (KVM)"
        pr_info_nol "  * EPT is disabled: "
        ept_disabled=-1
        if [ "$g_mode" = live ]; then
            if ! [ -r "$SYS_MODULE_BASE/kvm_intel/parameters/ept" ]; then
                pstatus blue N/A "the kvm_intel module is not loaded"
            else
                ept_value="$(cat "$SYS_MODULE_BASE/kvm_intel/parameters/ept" 2>/dev/null || echo ERROR)"
                if [ "$ept_value" = N ]; then
                    pstatus green YES
                    ept_disabled=1
                elif [ "$ept_value" = ERROR ]; then
                    pstatus yellow UNK "Couldn't read $SYS_MODULE_BASE/kvm_intel/parameters/ept"
                else
                    pstatus yellow NO
                fi
            fi
        else
            pstatus blue N/A "not testable in no-runtime mode"
        fi

        pr_info "* Mitigation 2"
        pr_info_nol "  * L1D flush is supported by kernel: "
        if [ "$g_mode" = live ] && cpuinfo_has_flag flush_l1d; then
            l1d_kernel="found flush_l1d in $g_procfs/cpuinfo"
        fi
        if [ -z "$l1d_kernel" ]; then
            if ! command -v "${opt_arch_prefix}strings" >/dev/null 2>&1; then
                l1d_kernel_err="missing '${opt_arch_prefix}strings' tool, please install it, usually it's in the binutils package"
            elif [ -n "$g_kernel_err" ]; then
                l1d_kernel_err="$g_kernel_err"
            elif "${opt_arch_prefix}strings" "$g_kernel" | grep -qw flush_l1d; then
                l1d_kernel='found flush_l1d in kernel image'
            fi
        fi

        if [ -n "$l1d_kernel" ]; then
            pstatus green YES "$l1d_kernel"
        elif [ -n "$l1d_kernel_err" ]; then
            pstatus yellow UNKNOWN "$l1d_kernel_err"
        else
            pstatus yellow NO
        fi

        pr_info_nol "  * L1D flush enabled: "
        if [ "$g_mode" = live ]; then
            if [ -n "$ret_sys_interface_check_fullmsg" ]; then
                # vanilla: VMX: $l1dstatus, SMT $smtstatus
                # Red Hat: VMX: SMT $smtstatus, L1D $l1dstatus
                # $l1dstatus is one of (auto|vulnerable|conditional cache flushes|cache flushes|EPT disabled|flush not necessary)
                # $smtstatus is one of (vulnerable|disabled)
                # can also just be "Not affected"
                if echo "$ret_sys_interface_check_fullmsg" | grep -Eq -e 'Not affected' -e '(VMX:|L1D) (EPT disabled|vulnerable|flush not necessary)'; then
                    l1d_mode=0
                    pstatus yellow NO
                elif echo "$ret_sys_interface_check_fullmsg" | grep -Eq '(VMX:|L1D) conditional cache flushes'; then
                    l1d_mode=1
                    pstatus green YES "conditional flushes"
                elif echo "$ret_sys_interface_check_fullmsg" | grep -Eq '(VMX:|L1D) cache flushes'; then
                    l1d_mode=2
                    pstatus green YES "unconditional flushes"
                else
                    if is_xen_dom0; then
                        l1d_xen_hardware=$(xl dmesg 2>/dev/null | grep 'Hardware features:' | grep 'L1D_FLUSH' | head -n1)
                        l1d_xen_hypervisor=$(xl dmesg 2>/dev/null | grep 'Xen settings:' | grep 'L1D_FLUSH' | head -n1)
                        l1d_xen_pv_domU=$(xl dmesg 2>/dev/null | grep 'PV L1TF shadowing:' | grep 'DomU enabled' | head -n1)

                        if [ -n "$l1d_xen_hardware" ] && [ -n "$l1d_xen_hypervisor" ] && [ -n "$l1d_xen_pv_domU" ]; then
                            l1d_mode=5
                            pstatus green YES "for XEN guests"
                        elif [ -n "$l1d_xen_hardware" ] && [ -n "$l1d_xen_hypervisor" ]; then
                            l1d_mode=4
                            pstatus yellow YES "for XEN guests (HVM only)"
                        elif [ -n "$l1d_xen_pv_domU" ]; then
                            l1d_mode=3
                            pstatus yellow YES "for XEN guests (PV only)"
                        else
                            l1d_mode=0
                            pstatus yellow NO "for XEN guests"
                        fi
                    else
                        l1d_mode=-1
                        pstatus yellow UNKNOWN "unrecognized mode"
                    fi
                fi
            else
                l1d_mode=-1
                pstatus yellow UNKNOWN "can't find or read $VULN_SYSFS_BASE/l1tf"
            fi
        else
            l1d_mode=-1
            pstatus blue N/A "not testable in no-runtime mode"
        fi

        pr_info_nol "  * Hardware-backed L1D flush supported: "
        if [ "$g_mode" = live ]; then
            if cpuinfo_has_flag flush_l1d || [ -n "$l1d_xen_hardware" ]; then
                pstatus green YES "performance impact of the mitigation will be greatly reduced"
            else
                pstatus blue NO "flush will be done in software, this is slower"
            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
        elif [ "$smt_enabled" = 1 ]; then
            pstatus green NO
        else
            pstatus yellow UNKNOWN
        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
        l1d_mode=-1
    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 [ "$ret_sys_interface_check_fullmsg" = "Not affected" ]; then
        # just in case a very recent kernel knows better than we do
        pvulnstatus "$cve" OK "your kernel reported your CPU model as not affected"
    elif [ -z "$msg" ]; then
        if [ "$opt_sysfs_only" != 1 ]; then
            if [ "$g_has_vmm" = 0 ]; then
                pvulnstatus "$cve" OK "this system is not running a hypervisor"
            elif [ "$ept_disabled" = 1 ]; then
                pvulnstatus "$cve" OK "EPT is disabled which mitigates the vulnerability"
            elif [ "$opt_paranoid" = 0 ]; then
                if [ "$l1d_mode" -ge 1 ]; then
                    pvulnstatus "$cve" OK "L1D flushing is enabled and mitigates the vulnerability"
                else
                    pvulnstatus "$cve" VULN "disable EPT or enable L1D flushing to mitigate the vulnerability"
                fi
            else
                if [ "$l1d_mode" -ge 2 ]; then
                    if [ "$smt_enabled" = 1 ]; then
                        pvulnstatus "$cve" OK "L1D unconditional flushing and Hyper-Threading disabled are mitigating the vulnerability"
                    else
                        pvulnstatus "$cve" VULN "Hyper-Threading must be disabled to fully mitigate the vulnerability"
                    fi
                else
                    if [ "$smt_enabled" = 1 ]; then
                        pvulnstatus "$cve" VULN "L1D unconditional flushing should be enabled to fully mitigate the vulnerability"
                    else
                        pvulnstatus "$cve" VULN "enable L1D unconditional flushing and disable Hyper-Threading to fully mitigate the vulnerability"
                    fi
                fi
            fi

            if [ "$l1d_mode" -gt 3 ]; then
                pr_warn
                pr_warn "This host is a Xen Dom0. Please make sure that you are running your DomUs"
                pr_warn "with a kernel which contains CVE-2018-3646 mitigations."
                pr_warn
                pr_warn "See https://www.suse.com/support/kb/doc/?id=7023078 and XSA-273 for details."
            fi
        else
            # --sysfs-only: sysfs was available (otherwise msg would be set), use its result
            pvulnstatus "$cve" "$status" "$ret_sys_interface_check_fullmsg"
        fi
    else
        # msg was set explicitly: either sysfs-not-available error, or a sysfs override
        pvulnstatus "$cve" "$status" "$msg"
    fi
}

check_CVE_2018_3646_bsd() {
    local kernel_l1d_supported kernel_l1d_enabled
    pr_info_nol "* Kernel supports L1D flushing: "
    if sysctl hw.vmm.vmx.l1d_flush >/dev/null 2>&1; then
        pstatus green YES
        kernel_l1d_supported=1
    else
        pstatus yellow NO
        kernel_l1d_supported=0
    fi

    pr_info_nol "* L1D flushing is enabled: "
    kernel_l1d_enabled=$(sysctl -n hw.vmm.vmx.l1d_flush 2>/dev/null)
    case "$kernel_l1d_enabled" in
        0) pstatus yellow NO ;;
        1) pstatus green YES ;;
        "") pstatus yellow NO ;;
        *) pstatus yellow UNKNOWN ;;
    esac

    if ! is_cpu_affected "$cve"; then
        pvulnstatus "$cve" OK "your CPU vendor reported your CPU model as not affected"
    else
        if [ "$kernel_l1d_enabled" = 1 ]; then
            pvulnstatus "$cve" OK "L1D flushing mitigates the vulnerability"
        elif [ "$kernel_l1d_supported" = 1 ]; then
            pvulnstatus "$cve" VULN "L1D flushing is supported by your kernel but is disabled"
        else
            pvulnstatus "$cve" VULN "your kernel needs to be updated"
        fi
    fi
}
