# vim: set ts=4 sw=4 sts=4 et:
###############################
# CVE-2017-5754, Meltdown, Rogue Data Cache Load

# no security impact but give a hint to the user in verbose mode
# about PCID/INVPCID cpuid features that must be present to avoid
# Check whether PCID/INVPCID are available to reduce PTI performance impact
# refs:
# https://marc.info/?t=151532047900001&r=1&w=2
# https://groups.google.com/forum/m/#!topic/mechanical-sympathy/L9mHTbeQLNU
pti_performance_check() {
    local ret pcid invpcid
    pr_info_nol "  * Reduced performance impact of PTI: "
    if cpuinfo_has_flag pcid; then
        pcid=1
    else
        read_cpuid 0x1 0x0 "$ECX" 17 1 1
        ret=$?
        if [ "$ret" = "$READ_CPUID_RET_OK" ]; then
            pcid=1
        fi
    fi

    if cpuinfo_has_flag invpcid; then
        invpcid=1
    else
        read_cpuid 0x7 0x0 "$EBX" 10 1 1
        ret=$?
        if [ "$ret" = "$READ_CPUID_RET_OK" ]; then
            invpcid=1
        fi
    fi

    if [ "$invpcid" = 1 ]; then
        pstatus green YES 'CPU supports INVPCID, performance impact of PTI will be greatly reduced'
    elif [ "$pcid" = 1 ]; then
        pstatus green YES 'CPU supports PCID, performance impact of PTI will be reduced'
    else
        pstatus blue NO 'PCID/INVPCID not supported, performance impact of PTI will be significant'
    fi
}

check_CVE_2017_5754() {
    check_cve 'CVE-2017-5754'
}

check_CVE_2017_5754_linux() {
    local status sys_interface_available msg kpti_support kpti_can_tell kpti_enabled dmesg_grep pti_xen_pv_domU xen_pv_domo xen_pv_domu xen_unknown_container explain_text
    status=UNK
    sys_interface_available=0
    msg=''
    if sys_interface_check "$VULN_SYSFS_BASE/meltdown"; 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 Page Table Isolation (PTI): "
        kpti_support=''
        kpti_can_tell=0
        if [ -n "$opt_config" ]; then
            kpti_can_tell=1
            kpti_support=$(grep -E -w -e 'CONFIG_(MITIGATION_)?PAGE_TABLE_ISOLATION=y' -e CONFIG_KAISER=y -e CONFIG_UNMAP_KERNEL_AT_EL0=y "$opt_config")
            if [ -n "$kpti_support" ]; then
                pr_debug "kpti_support: found option '$kpti_support' in $opt_config"
            fi
        fi
        if [ -z "$kpti_support" ] && [ -n "$opt_map" ]; then
            # it's not an elif: some backports don't have the PTI config but still include the patch
            # so we try to find an exported symbol that is part of the PTI patch in System.map
            # parse_kpti: arm
            kpti_can_tell=1
            kpti_support=$(grep -w -e kpti_force_enabled -e parse_kpti "$opt_map")
            if [ -n "$kpti_support" ]; then
                pr_debug "kpti_support: found '$kpti_support' in $opt_map"
            fi
        fi
        if [ -z "$kpti_support" ] && [ -n "$g_kernel" ]; then
            # same as above but in case we don't have System.map and only kernel, look for the
            # nopti option that is part of the patch (kernel command line option)
            # 'kpti=': arm
            kpti_can_tell=1
            if ! command -v "${opt_arch_prefix}strings" >/dev/null 2>&1; then
                pstatus yellow UNKNOWN "missing '${opt_arch_prefix}strings' tool, please install it, usually it's in the binutils package"
            else
                kpti_support=$("${opt_arch_prefix}strings" "$g_kernel" | grep -w -e nopti -e kpti=)
                if [ -n "$kpti_support" ]; then
                    pr_debug "kpti_support: found '$kpti_support' in $g_kernel"
                fi
            fi
        fi

        if [ -n "$kpti_support" ]; then
            if [ "$opt_verbose" -ge 2 ]; then
                pstatus green YES "found '$kpti_support'"
            else
                pstatus green YES
            fi
        elif [ "$kpti_can_tell" = 1 ]; then
            pstatus yellow NO
        else
            pstatus yellow UNKNOWN "couldn't read your kernel configuration nor System.map file"
        fi

        mount_debugfs
        pr_info_nol "  * PTI enabled and active: "
        if [ "$g_mode" = live ]; then
            dmesg_grep="Kernel/User page tables isolation: enabled"
            dmesg_grep="$dmesg_grep|Kernel page table isolation enabled"
            dmesg_grep="$dmesg_grep|x86/pti: Unmapping kernel while in userspace"
            # aarch64
            dmesg_grep="$dmesg_grep|CPU features: detected( feature)?: Kernel page table isolation \(KPTI\)"
            if cpuinfo_has_flag pti; then
                # vanilla PTI patch sets the 'pti' flag in cpuinfo
                pr_debug "kpti_enabled: found 'pti' flag in $g_procfs/cpuinfo"
                kpti_enabled=1
            elif cpuinfo_has_flag kaiser; then
                # kernel line 4.9 sets the 'kaiser' flag in cpuinfo
                pr_debug "kpti_enabled: found 'kaiser' flag in $g_procfs/cpuinfo"
                kpti_enabled=1
            elif [ -e "$DEBUGFS_BASE/x86/pti_enabled" ]; then
                # Red Hat Backport creates a dedicated file, see https://access.redhat.com/articles/3311301
                kpti_enabled=$(cat "$DEBUGFS_BASE/x86/pti_enabled" 2>/dev/null)
                pr_debug "kpti_enabled: file $DEBUGFS_BASE/x86/pti_enabled exists and says: $kpti_enabled"
            elif is_xen_dom0; then
                pti_xen_pv_domU=$(xl dmesg 2>/dev/null | grep 'XPTI' | grep 'DomU enabled' | head -n1)

                [ -n "$pti_xen_pv_domU" ] && kpti_enabled=1
            fi
            if [ -z "$kpti_enabled" ]; then
                dmesg_grep "$dmesg_grep"
                ret=$?
                if [ "$ret" -eq 0 ]; then
                    pr_debug "kpti_enabled: found hint in dmesg: $ret_dmesg_grep_grepped"
                    kpti_enabled=1
                elif [ "$ret" -eq 2 ]; then
                    pr_debug "kpti_enabled: dmesg truncated"
                    kpti_enabled=-1
                fi
            fi
            if [ -z "$kpti_enabled" ]; then
                pr_debug "kpti_enabled: couldn't find any hint that PTI is enabled"
                kpti_enabled=0
            fi
            if [ "$kpti_enabled" = 1 ]; then
                pstatus green YES
            elif [ "$kpti_enabled" = -1 ]; then
                pstatus yellow UNKNOWN "dmesg truncated, please reboot and relaunch this script"
            else
                pstatus yellow NO
            fi
        else
            pstatus blue N/A "not testable in no-runtime mode"
        fi

        # PCID/INVPCID are x86-only CPU features
        if is_x86_cpu; then
            pti_performance_check
        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

    # Test if the current host is a Xen PV Dom0 / DomU
    xen_pv_domo=0
    xen_pv_domu=0
    xen_unknown_container=0
    if is_xen && ! is_xen_dom0 && is_running_in_container; then
        # We can see Xen, but we're inside a container so /proc/xen/capabilities
        # isn't exposed and dmesg is the host's: we can't tell a safe Dom0 from
        # a vulnerable PV DomU from in here (issue #173).
        xen_unknown_container=1
    else
        is_xen_dom0 && xen_pv_domo=1
        is_xen_domU && xen_pv_domu=1
    fi

    if [ "$g_mode" = live ]; then
        # checking whether we're running under Xen PV 64 bits. If yes, we are affected by affected_variant3
        # (unless we are a Dom0)
        pr_info_nol "* Running as a Xen PV DomU: "
        if [ "$xen_unknown_container" = 1 ]; then
            pstatus yellow UNKNOWN "running in a container, can't query Xen from here"
        elif [ "$xen_pv_domu" = 1 ]; then
            pstatus yellow YES
        else
            pstatus blue NO
        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 [ -z "$msg" ]; then
        # if msg is empty, sysfs check didn't fill it, rely on our own test
        if [ "$g_mode" = live ]; then
            if [ "$xen_unknown_container" = 1 ]; then
                pvulnstatus "$cve" UNK "running inside a container on a Xen host, can't determine if the underlying domain is a vulnerable PV DomU"
                explain "This system looks like a container ($g_container_reason) running on a Xen host. Whether the underlying domain is a safe Dom0 or a vulnerable PV DomU can't be reliably determined from inside a container (/proc/xen is exposed but empty, and dmesg belongs to the host). Please re-run this script directly on the host, outside the container, to get an accurate result."
            elif [ "$kpti_enabled" = 1 ]; then
                pvulnstatus "$cve" OK "PTI mitigates the vulnerability"
            elif [ "$xen_pv_domo" = 1 ]; then
                pvulnstatus "$cve" OK "Xen Dom0s are safe and do not require PTI"
            elif [ "$xen_pv_domu" = 1 ]; then
                pvulnstatus "$cve" VULN "Xen PV DomUs are vulnerable and need to be run in HVM, PVHVM, PVH mode, or the Xen hypervisor must have the Xen's own PTI patch"
                explain "Go to https://blog.xenproject.org/2018/01/22/xen-project-spectre-meltdown-faq-jan-22-update/ for more information"
            elif [ "$kpti_enabled" = -1 ]; then
                pvulnstatus "$cve" UNK "couldn't find any clue of PTI activation due to a truncated dmesg, please reboot and relaunch this script"
            else
                pvulnstatus "$cve" VULN "PTI is needed to mitigate the vulnerability"
                if [ -n "$kpti_support" ]; then
                    if [ -e "$DEBUGFS_BASE/x86/pti_enabled" ]; then
                        explain "Your kernel supports PTI but it's disabled, you can enable it with \`echo 1 > $DEBUGFS_BASE/x86/pti_enabled\`"
                    elif echo "$g_kernel_cmdline" | grep -q -w -e nopti -e pti=off; then
                        explain "Your kernel supports PTI but it has been disabled on command-line, remove the nopti or pti=off option from your bootloader configuration"
                    else
                        explain "Your kernel supports PTI but it has been disabled, check \`dmesg\` right after boot to find clues why the system disabled it"
                    fi
                else
                    explain "If you're using a distro kernel, upgrade your distro to get the latest kernel available. Otherwise, recompile the kernel with the CONFIG_(MITIGATION_)PAGE_TABLE_ISOLATION option (named CONFIG_KAISER for some kernels), or the CONFIG_UNMAP_KERNEL_AT_EL0 option (for ARM64)"
                fi
            fi
        else
            if [ -n "$kpti_support" ]; then
                pvulnstatus "$cve" OK "no-runtime mode: PTI will mitigate the vulnerability if enabled at runtime"
            elif [ "$kpti_can_tell" = 1 ]; then
                pvulnstatus "$cve" VULN "PTI is needed to mitigate the vulnerability"
                explain "If you're using a distro kernel, upgrade your distro to get the latest kernel available. Otherwise, recompile the kernel with the CONFIG_(MITIGATION_)PAGE_TABLE_ISOLATION option (named CONFIG_KAISER for some kernels), or the CONFIG_UNMAP_KERNEL_AT_EL0 option (for ARM64)"
            else
                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
    else
        if [ "$xen_pv_domo" = 1 ]; then
            msg="Xen Dom0s are safe and do not require PTI"
            status="OK"
        elif [ "$xen_pv_domu" = 1 ]; then
            msg="Xen PV DomUs are vulnerable and need to be run in HVM, PVHVM, PVH mode, or the Xen hypervisor must have the Xen's own PTI patch"
            status="VULN"
            explain_text="Go to https://blog.xenproject.org/2018/01/22/xen-project-spectre-meltdown-faq-jan-22-update/ for more information"
        elif [ "$msg" = "Vulnerable" ]; then
            msg="PTI is needed to mitigate the vulnerability"
            explain_text="If you're using a distro kernel, upgrade your distro to get the latest kernel available. Otherwise, recompile the kernel with the CONFIG_(MITIGATION_)PAGE_TABLE_ISOLATION option (named CONFIG_KAISER for some kernels), or the CONFIG_UNMAP_KERNEL_AT_EL0 option (for ARM64)"
        fi
        pvulnstatus "$cve" "$status" "$msg"
        [ -z "${explain_text:-}" ] && [ "$msg" = "Vulnerable" ] && explain_text="If you're using a distro kernel, upgrade your distro to get the latest kernel available. Otherwise, recompile the kernel with the CONFIG_(MITIGATION_)PAGE_TABLE_ISOLATION option (named CONFIG_KAISER for some kernels), or the CONFIG_UNMAP_KERNEL_AT_EL0 option (for ARM64)"
        [ -n "${explain_text:-}" ] && explain "$explain_text"
        unset explain_text
    fi

    # Warn the user about XSA-254 recommended mitigations
    if [ "$xen_pv_domo" = 1 ]; then
        pr_warn
        pr_warn "This host is a Xen Dom0. Please make sure that you are running your DomUs"
        pr_warn "in HVM, PVHVM or PVH mode to prevent any guest-to-host / host-to-guest attacks."
        pr_warn
        pr_warn "See https://blog.xenproject.org/2018/01/22/xen-project-spectre-meltdown-faq-jan-22-update/ and XSA-254 for details."
    fi
}

check_CVE_2017_5754_bsd() {
    local kpti_enabled
    pr_info_nol "* Kernel supports Page Table Isolation (PTI): "
    kpti_enabled=$(sysctl -n vm.pmap.pti 2>/dev/null)
    if [ -z "$kpti_enabled" ]; then
        pstatus yellow NO
    else
        pstatus green YES
    fi

    pr_info_nol "  * PTI enabled and active: "
    if [ "$kpti_enabled" = 1 ]; then
        pstatus green YES
    else
        pstatus yellow NO
    fi

    pti_performance_check

    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 [ "$kpti_enabled" = 1 ]; then
        pvulnstatus "$cve" OK "PTI mitigates the vulnerability"
    elif [ -n "$kpti_enabled" ]; then
        pvulnstatus "$cve" VULN "PTI is supported but disabled on your system"
    else
        pvulnstatus "$cve" VULN "PTI is needed to mitigate the vulnerability"
    fi
}
