# vim: set ts=4 sw=4 sts=4 et:
###############################
# CVE-2017-5753, Spectre V1, Bounds Check Bypass

# Sets: (none directly, delegates to check_cve)
check_CVE_2017_5753() {
    check_cve 'CVE-2017-5753'
}

# Sets: g_redhat_canonical_spectre (via check_redhat_canonical_spectre)
check_CVE_2017_5753_linux() {
    local status sys_interface_available msg v1_kernel_mitigated v1_kernel_mitigated_err v1_mask_nospec ret explain_text
    status=UNK
    sys_interface_available=0
    msg=''
    if sys_interface_check "$VULN_SYSFS_BASE/spectre_v1"; then
        # this kernel has the /sys interface, trust it over everything
        # v0.33+: don't. some kernels have backported the array_index_mask_nospec() workaround without
        # modifying the vulnerabilities/spectre_v1 file. that's bad. we can't trust it when it says Vulnerable :(
        # see "silent backport" detection at the bottom of this func
        sys_interface_available=1
        #
        # Complete sysfs message inventory for spectre_v1, traced via git blame:
        #
        # all versions:
        #   "Not affected"                                                              (cpu_show_common, pre-existing)
        #
        # --- x86 mainline ---
        # 61dc0f555b5c (v4.15, initial spectre_v1 sysfs):
        #   "Vulnerable"
        # edfbae53dab8 (v4.16, report get_user mitigation):
        #   "Mitigation: __user pointer sanitization"
        # a2059825986a (v5.3, swapgs awareness via spectre_v1_strings[]):
        #   "Vulnerable: __user pointer sanitization and usercopy barriers only; no swapgs barriers"
        #   "Mitigation: usercopy/swapgs barriers and __user pointer sanitization"
        # ca01c0d8d030 (v6.12, CONFIG_MITIGATION_SPECTRE_V1 controls default):
        #   same strings as v5.3+
        # All stable branches (4.4.y through 6.12.y) have v5.3+ strings backported.
        #
        # --- x86 RHEL (centos6, centos7 branches) ---
        #   "Vulnerable: Load fences, __user pointer sanitization and usercopy barriers only; no swapgs barriers"
        #   "Mitigation: Load fences, usercopy/swapgs barriers and __user pointer sanitization"
        #
        # --- ARM64 ---
        # 3891ebccace1 (v5.2, first arm64 spectre_v1 sysfs, backported to 4.14.y+):
        #   "Mitigation: __user pointer sanitization"                                   (hardcoded)
        # 455697adefdb (v5.10, moved to proton-pack.c):
        #   same string
        # Before v5.2: no sysfs override (generic "Not affected" fallback).
        # Actual mitigation (array_index_mask_nospec with CSDB) landed in v4.16.
        #
        # --- ARM32 ---
        # 9dd78194a372 (v5.17+):
        #   "Mitigation: __user pointer sanitization"                                   (hardcoded)
        #
        # all messages start with either "Not affected", "Mitigation", or "Vulnerable"
        status=$ret_sys_interface_check_status
    fi
    if [ "$opt_sysfs_only" != 1 ]; then
        # no /sys interface (or no-runtime mode), fallback to our own ways

        # Primary detection: grep for sysfs mitigation strings in the kernel binary.
        # The string "__user pointer sanitization" is present in all kernel versions
        # that have spectre_v1 sysfs support (x86 v4.16+, ARM64 v5.2+, ARM32 v5.17+),
        # including RHEL "Load fences" variants. This is cheap and works in no-runtime mode.
        pr_info_nol "* Kernel has spectre_v1 mitigation (kernel image): "
        v1_kernel_mitigated=''
        v1_kernel_mitigated_err=''
        if [ -n "$g_kernel_err" ]; then
            v1_kernel_mitigated_err="$g_kernel_err"
        elif grep -q '__user pointer sanitization' "$g_kernel"; then
            if grep -q 'usercopy/swapgs barriers' "$g_kernel"; then
                v1_kernel_mitigated="usercopy/swapgs barriers and target sanitization"
            elif grep -q 'Load fences' "$g_kernel"; then
                v1_kernel_mitigated="RHEL Load fences mitigation"
            else
                v1_kernel_mitigated="__user pointer sanitization"
            fi
        fi
        if [ -z "$v1_kernel_mitigated" ] && [ -r "$opt_config" ]; then
            if grep -q '^CONFIG_MITIGATION_SPECTRE_V1=y' "$opt_config"; then
                v1_kernel_mitigated="CONFIG_MITIGATION_SPECTRE_V1 found in kernel config"
            fi
        fi
        if [ -z "$v1_kernel_mitigated" ] && [ -n "$opt_map" ]; then
            if grep -q 'spectre_v1_select_mitigation' "$opt_map"; then
                v1_kernel_mitigated="found spectre_v1_select_mitigation in System.map"
            fi
        fi
        if [ -n "$v1_kernel_mitigated" ]; then
            pstatus green YES "$v1_kernel_mitigated"
        elif [ -n "$v1_kernel_mitigated_err" ]; then
            pstatus yellow UNKNOWN "couldn't check ($v1_kernel_mitigated_err)"
        else
            pstatus yellow NO
        fi

        # Fallback for v4.15-era kernels: binary pattern matching for array_index_mask_nospec().
        # The sysfs mitigation strings were not present in the kernel image until v4.16 (x86)
        # and v5.2 (ARM64), but the actual mitigation code landed in v4.15 (x86) and v4.16 (ARM64).
        # For no-runtime analysis of these old kernels, match the specific instruction patterns.
        if [ -z "$v1_kernel_mitigated" ]; then
            pr_info_nol "* Kernel has array_index_mask_nospec (v4.15 binary pattern): "
            v1_mask_nospec=''
            if [ -n "$g_kernel_err" ]; then
                pstatus yellow UNKNOWN "couldn't check ($g_kernel_err)"
            elif is_x86_kernel; then
                # x86: binary pattern matching for array_index_mask_nospec()
                # x86 64bits: jae(0x0f 0x83 ....) sbb(0x48 0x19 0xd2) and(0x48 0x21 0xd0)
                # x86 32bits: cmp(0x3b 0x82 .. .. 0x00 0x00) jae(0x73 ..) sbb(0x19 0xd2) and(0x21 0xd0)
                if ! command -v perl >/dev/null 2>&1; then
                    pstatus yellow UNKNOWN "missing 'perl' binary, please install it"
                else
                    perl -ne '/\x0f\x83....\x48\x19\xd2\x48\x21\xd0/ and $found++; END { exit($found ? 0 : 1) }' "$g_kernel"
                    ret=$?
                    if [ "$ret" -eq 0 ]; then
                        pstatus green YES "x86 64 bits array_index_mask_nospec()"
                        v1_mask_nospec="x86 64 bits array_index_mask_nospec"
                    else
                        perl -ne '/\x3b\x82..\x00\x00\x73.\x19\xd2\x21\xd0/ and $found++; END { exit($found ? 0 : 1) }' "$g_kernel"
                        ret=$?
                        if [ "$ret" -eq 0 ]; then
                            pstatus green YES "x86 32 bits array_index_mask_nospec()"
                            v1_mask_nospec="x86 32 bits array_index_mask_nospec"
                        else
                            pstatus yellow NO
                        fi
                    fi
                fi
            elif is_arm_kernel; then
                # arm32: match CSDB instruction (0xf3af8014 Thumb2 or 0xe320f014 ARM) preceded by sbc+cmp
                # http://git.arm.linux.org.uk/cgit/linux-arm.git/commit/?h=spectre&id=a78d156587931a2c3b354534aa772febf6c9e855
                if ! command -v "${opt_arch_prefix}objdump" >/dev/null 2>&1; then
                    pstatus yellow UNKNOWN "missing '${opt_arch_prefix}objdump' tool, please install it, usually it's in the binutils package"
                else
                    ret=$("${opt_arch_prefix}objdump" "$g_objdump_options" "$g_kernel" | grep -w -e f3af8014 -e e320f014 -B2 | grep -B1 -w sbc | grep -w -c cmp)
                    if [ "$ret" -gt 0 ]; then
                        pstatus green YES "$ret occurrence(s) found of arm 32 bits array_index_mask_nospec()"
                        v1_mask_nospec="arm 32 bits array_index_mask_nospec"
                    else
                        pstatus yellow NO
                    fi
                fi
            else
                pstatus yellow NO
            fi
        fi

        pr_info_nol "* Kernel has the Red Hat/Ubuntu patch: "
        check_redhat_canonical_spectre
        if [ "$g_redhat_canonical_spectre" = -1 ]; then
            pstatus yellow UNKNOWN "missing '${opt_arch_prefix}strings' tool, please install it, usually it's in the binutils package"
        elif [ "$g_redhat_canonical_spectre" = -2 ]; then
            pstatus yellow UNKNOWN "couldn't check ($g_kernel_err)"
        elif [ "$g_redhat_canonical_spectre" = 1 ]; then
            pstatus green YES
        elif [ "$g_redhat_canonical_spectre" = 2 ]; then
            pstatus green YES "but without IBRS"
        else
            pstatus yellow NO
        fi

        if is_arm_kernel; then
            pr_info_nol "* Kernel has mask_nospec64 (arm64): "
            #.macro	mask_nospec64, idx, limit, tmp
            #sub	\tmp, \idx, \limit
            #bic	\tmp, \tmp, \idx
            #and	\idx, \idx, \tmp, asr #63
            #csdb
            #.endm
            #$ aarch64-linux-gnu-objdump -d vmlinux | grep -w bic -A1 -B1 | grep -w sub -A2 | grep -w and -B2
            #ffffff8008082e44:       cb190353        sub     x19, x26, x25
            #ffffff8008082e48:       8a3a0273        bic     x19, x19, x26
            #ffffff8008082e4c:       8a93ff5a        and     x26, x26, x19, asr #63
            #ffffff8008082e50:       d503229f        hint    #0x14
            # /!\ can also just be "csdb" instead of "hint #0x14" for native objdump
            #
            # if we already have a detection, don't bother disassembling the kernel, the answer is no.
            if [ -n "$v1_kernel_mitigated" ] || [ -n "$v1_mask_nospec" ] || [ "$g_redhat_canonical_spectre" -gt 0 ]; then
                pstatus yellow NO
            elif [ -n "$g_kernel_err" ]; then
                pstatus yellow UNKNOWN "couldn't check ($g_kernel_err)"
            elif ! command -v perl >/dev/null 2>&1; then
                pstatus yellow UNKNOWN "missing 'perl' binary, please install it"
            elif ! command -v "${opt_arch_prefix}objdump" >/dev/null 2>&1; then
                pstatus yellow UNKNOWN "missing '${opt_arch_prefix}objdump' tool, please install it, usually it's in the binutils package"
            else
                "${opt_arch_prefix}objdump" "$g_objdump_options" "$g_kernel" | perl -ne 'push @r, $_; /\s(hint|csdb)\s/ && $r[0]=~/\ssub\s+(x\d+)/ && $r[1]=~/\sbic\s+$1,\s+$1,/ && $r[2]=~/\sand\s/ && exit(9); shift @r if @r>3'
                ret=$?
                if [ "$ret" -eq 9 ]; then
                    pstatus green YES "mask_nospec64 macro is present and used"
                    v1_mask_nospec="arm64 mask_nospec64"
                else
                    pstatus yellow NO
                fi
            fi

            pr_info_nol "* Kernel has array_index_nospec (arm64): "
            # in 4.19+ kernels, the mask_nospec64 asm64 macro is replaced by array_index_nospec, defined in nospec.h, and used in invoke_syscall()
            # ffffff8008090a4c:       2a0203e2        mov     w2, w2
            # ffffff8008090a50:       eb0200bf        cmp     x5, x2
            # ffffff8008090a54:       da1f03e2        ngc     x2, xzr
            # ffffff8008090a58:       d503229f        hint    #0x14
            # /!\ can also just be "csdb" instead of "hint #0x14" for native objdump
            #
            # if we already have a detection, don't bother disassembling the kernel, the answer is no.
            if [ -n "$v1_kernel_mitigated" ] || [ -n "$v1_mask_nospec" ] || [ "$g_redhat_canonical_spectre" -gt 0 ]; then
                pstatus yellow NO
            elif [ -n "$g_kernel_err" ]; then
                pstatus yellow UNKNOWN "couldn't check ($g_kernel_err)"
            elif ! command -v perl >/dev/null 2>&1; then
                pstatus yellow UNKNOWN "missing 'perl' binary, please install it"
            elif ! command -v "${opt_arch_prefix}objdump" >/dev/null 2>&1; then
                pstatus yellow UNKNOWN "missing '${opt_arch_prefix}objdump' tool, please install it, usually it's in the binutils package"
            else
                "${opt_arch_prefix}objdump" "$g_objdump_options" "$g_kernel" | perl -ne 'push @r, $_; /\s(hint|csdb)\s/ && $r[0]=~/\smov\s+(w\d+),\s+(w\d+)/ && $r[1]=~/\scmp\s+(x\d+),\s+(x\d+)/ && $r[2]=~/\sngc\s+$2,/ && exit(9); shift @r if @r>3'
                ret=$?
                if [ "$ret" -eq 9 ]; then
                    pstatus green YES "array_index_nospec macro is present and used"
                    v1_mask_nospec="arm64 array_index_nospec"
                else
                    pstatus yellow NO
                fi
            fi
        fi # is_arm_kernel

    elif [ "$sys_interface_available" = 0 ]; then
        msg="/sys vulnerability interface use forced, but it's not available!"
        status=UNK
    fi

    # report status
    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 [ -n "$v1_kernel_mitigated" ]; then
                pvulnstatus "$cve" OK "Kernel source has been patched to mitigate the vulnerability ($v1_kernel_mitigated)"
            elif [ -n "$v1_mask_nospec" ]; then
                pvulnstatus "$cve" OK "Kernel source has been patched to mitigate the vulnerability ($v1_mask_nospec)"
            elif [ "$g_redhat_canonical_spectre" = 1 ] || [ "$g_redhat_canonical_spectre" = 2 ]; then
                pvulnstatus "$cve" OK "Kernel source has been patched to mitigate the vulnerability (Red Hat/Ubuntu patch)"
            elif [ -n "$g_kernel_err" ]; then
                pvulnstatus "$cve" UNK "Couldn't find kernel image or tools missing to execute the checks"
                explain "Re-run this script with root privileges, after installing the missing tools indicated above"
            else
                pvulnstatus "$cve" VULN "Kernel source needs to be patched to mitigate the vulnerability"
                explain "Your kernel is too old to have the mitigation for Variant 1, you should upgrade to a newer kernel. If you're using a Linux distro and didn't compile the kernel yourself, you should upgrade your distro to get a newer kernel."
            fi
        else
            pvulnstatus "$cve" "$status" "$ret_sys_interface_check_fullmsg"
        fi
    else
        if [ "$msg" = "Vulnerable" ] && { [ -n "$v1_kernel_mitigated" ] || [ -n "$v1_mask_nospec" ]; }; then
            pvulnstatus "$cve" OK "Kernel source has been patched to mitigate the vulnerability (silent backport of spectre_v1 mitigation)"
        else
            if [ "$msg" = "Vulnerable" ]; then
                msg="Kernel source needs to be patched to mitigate the vulnerability"
                explain_text="Your kernel is too old to have the mitigation for Variant 1, you should upgrade to a newer kernel. If you're using a Linux distro and didn't compile the kernel yourself, you should upgrade your distro to get a newer kernel."
            fi
            pvulnstatus "$cve" "$status" "$msg"
            [ -n "${explain_text:-}" ] && explain "$explain_text"
            unset explain_text
        fi
    fi
}

check_CVE_2017_5753_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
}
