mirror of
https://github.com/speed47/spectre-meltdown-checker.git
synced 2026-06-06 22:53:03 +02:00
7329c1fd2f
CVE_REGISTRY gains an optional fifth field that tags checks as x86-only or arm-only, untagged entries apply everywhere. The main CVE dispatcher and the affectedness summary both skip gated entries in default "all CVEs" runs, removing the noise of arm64 errata on x86 hosts and of x86 CVEs on ARM hosts across text, json, nrpe and prometheus outputs. Explicit --cve/--variant/--errata selection bypasses the gate so manual queries still run anywhere. The gate honours no-hw mode by ignoring the host CPU and keying off the inspected kernel's architecture only, which handles cross-arch offline analysis driven by --kernel/--config/--map.
234 lines
9.6 KiB
Bash
234 lines
9.6 KiB
Bash
# vim: set ts=4 sw=4 sts=4 et:
|
|
|
|
check_kernel_info
|
|
|
|
# Detect arch mismatch between host CPU and target kernel (e.g. x86 host
|
|
# inspecting an ARM kernel): force no-hw mode so CPUID/MSR/sysfs reads
|
|
# from the host don't pollute the results.
|
|
check_kernel_cpu_arch_mismatch
|
|
|
|
# Build JSON meta and system sections early (after kernel info is resolved)
|
|
if [ "$opt_batch" = 1 ] && [ "$opt_batch_format" = "json" ]; then
|
|
_build_json_meta
|
|
fi
|
|
|
|
pr_info
|
|
|
|
if [ "$g_mode" != no-hw ] && [ -z "$opt_arch_prefix" ]; then
|
|
pr_info "\033[1;34mHardware check\033[0m"
|
|
check_cpu
|
|
check_cpu_vulnerabilities
|
|
pr_info
|
|
fi
|
|
|
|
# Build JSON system/cpu/microcode sections (after check_cpu has populated cap_* vars and VMM detection)
|
|
if [ "$opt_batch" = 1 ] && [ "$opt_batch_format" = "json" ]; then
|
|
_build_json_system
|
|
if [ "$g_mode" != no-hw ] && [ -z "$opt_arch_prefix" ]; then
|
|
_build_json_cpu
|
|
_build_json_cpu_microcode
|
|
fi
|
|
fi
|
|
|
|
# Build Prometheus info metric lines (same timing requirement as JSON builders above)
|
|
if [ "$opt_batch" = 1 ] && [ "$opt_batch_format" = "prometheus" ]; then
|
|
_build_prometheus_system_info
|
|
if [ "$g_mode" != no-hw ] && [ -z "$opt_arch_prefix" ]; then
|
|
_build_prometheus_cpu_info
|
|
fi
|
|
fi
|
|
|
|
# now run the checks the user asked for (hw-only mode skips CVE checks)
|
|
if [ "$g_mode" = hw-only ]; then
|
|
pr_info "Hardware-only mode, skipping vulnerability checks"
|
|
else
|
|
for cve in $g_supported_cve_list; do
|
|
# In a default "all CVEs" run, skip checks whose arch tag doesn't match
|
|
# the host CPU or the inspected kernel. Explicit --cve/--variant/--errata
|
|
# selection bypasses the gate.
|
|
if [ "$opt_cve_all" = 1 ]; then
|
|
if ! _is_cve_relevant_arch "$cve"; then
|
|
pr_debug "main: skipping $cve (arch tag not relevant)"
|
|
continue
|
|
fi
|
|
elif ! echo "$opt_cve_list" | grep -qw "$cve"; then
|
|
continue
|
|
fi
|
|
check_"$(echo "$cve" | tr - _)"
|
|
pr_info
|
|
done
|
|
fi # g_mode != hw-only
|
|
|
|
if [ -n "$g_final_summary" ]; then
|
|
pr_info "> \033[46m\033[30mSUMMARY:\033[0m$g_final_summary"
|
|
pr_info ""
|
|
fi
|
|
|
|
if [ "$g_bad_accuracy" = 1 ]; then
|
|
pr_warn "We're missing some kernel information (see kernel section at the top), accuracy might be reduced"
|
|
fi
|
|
|
|
g_vars=$(set | grep -Ev '^[A-Z_[:space:]]' | grep -v -F 'g_mockme=' | sort | tr "\n" '|')
|
|
pr_debug "variables at end of script: $g_vars"
|
|
|
|
if [ -n "$g_mockme" ] && [ "$opt_mock" = 1 ]; then
|
|
if command -v "gzip" >/dev/null 2>&1; then
|
|
# not a useless use of cat: gzipping cpuinfo directly doesn't work well
|
|
# shellcheck disable=SC2002
|
|
if command -v "base64" >/dev/null 2>&1; then
|
|
g_mock_cpuinfo="$(cat /proc/cpuinfo | gzip -c | base64 | tr -d '\n')"
|
|
elif command -v "uuencode" >/dev/null 2>&1; then
|
|
g_mock_cpuinfo="$(cat /proc/cpuinfo | gzip -c | uuencode -m - | grep -Fv 'begin-base64' | grep -Fxv -- '====' | tr -d "\n")"
|
|
fi
|
|
fi
|
|
if [ -n "$g_mock_cpuinfo" ]; then
|
|
g_mockme=$(printf "%b\n%b" "$g_mockme" "SMC_MOCK_CPUINFO='$g_mock_cpuinfo'")
|
|
unset g_mock_cpuinfo
|
|
fi
|
|
pr_info ""
|
|
# shellcheck disable=SC2046
|
|
pr_warn "To mock this CPU, set those vars: "$(echo "$g_mockme" | sort -u)
|
|
fi
|
|
|
|
# root check
|
|
if [ "$(id -u)" -ne 0 ]; then
|
|
pr_warn "Note that you should launch this script with root privileges to get completely accurate information."
|
|
pr_warn "To run it as root, you can try the following command: sudo $0"
|
|
pr_warn
|
|
fi
|
|
|
|
if [ "$opt_explain" = 0 ]; then
|
|
pr_info "Need more detailed information about mitigation options? Use --explain"
|
|
fi
|
|
|
|
pr_info "A false sense of security is worse than no security at all, see --disclaimer"
|
|
|
|
if [ "$g_mocked" = 1 ]; then
|
|
pr_info ""
|
|
pr_warn "One or several values have been g_mocked. This should only be done when debugging/testing this script."
|
|
pr_warn "The results do NOT reflect the actual status of the system we're running on."
|
|
fi
|
|
|
|
if [ "$opt_batch" = 1 ] && [ "$opt_batch_format" = "nrpe" ]; then
|
|
_nrpe_is_root=0
|
|
[ "$(id -u)" -eq 0 ] && _nrpe_is_root=1
|
|
|
|
# Non-root + VULN: demote to UNKNOWN, MSR reads were skipped so VULN findings
|
|
# may be false positives or genuine mitigations may have gone undetected
|
|
_nrpe_demoted=0
|
|
[ "$g_nrpe_vuln_count" -gt 0 ] && [ "$_nrpe_is_root" = 0 ] && _nrpe_demoted=1
|
|
|
|
# Determine status word and build the one-line summary
|
|
if [ "$_nrpe_demoted" = 1 ]; then
|
|
_nrpe_status_word='UNKNOWN'
|
|
_nrpe_summary="${g_nrpe_vuln_count}/${g_nrpe_total} CVE(s) appear vulnerable (unconfirmed, not root): ${g_nrpe_vuln_ids}"
|
|
[ "$g_nrpe_unk_count" -gt 0 ] && _nrpe_summary="${_nrpe_summary}, ${g_nrpe_unk_count} inconclusive"
|
|
elif [ "$g_nrpe_vuln_count" -gt 0 ]; then
|
|
_nrpe_status_word='CRITICAL'
|
|
_nrpe_summary="${g_nrpe_vuln_count}/${g_nrpe_total} CVE(s) vulnerable: ${g_nrpe_vuln_ids}"
|
|
[ "$g_nrpe_unk_count" -gt 0 ] && _nrpe_summary="${_nrpe_summary}, ${g_nrpe_unk_count} inconclusive"
|
|
elif [ "$g_nrpe_unk_count" -gt 0 ]; then
|
|
_nrpe_status_word='UNKNOWN'
|
|
_nrpe_summary="${g_nrpe_unk_count}/${g_nrpe_total} CVE checks inconclusive"
|
|
else
|
|
_nrpe_status_word='OK'
|
|
_nrpe_summary="All ${g_nrpe_total} CVE checks passed"
|
|
fi
|
|
|
|
# Line 1: status word + summary + performance data (Nagios plugin spec)
|
|
echo "${_nrpe_status_word}: ${_nrpe_summary} | checked=${g_nrpe_total} vulnerable=${g_nrpe_vuln_count} unknown=${g_nrpe_unk_count}"
|
|
|
|
# Long output (lines 2+): context notes, then per-CVE details
|
|
[ "$opt_paranoid" = 1 ] && echo "NOTE: paranoid mode active, stricter mitigation requirements applied"
|
|
case "${g_has_vmm:-}" in
|
|
1) echo "NOTE: hypervisor host detected (${g_has_vmm_reason:-VMM}); L1TF/MDS severity is elevated" ;;
|
|
0) echo "NOTE: not a hypervisor host" ;;
|
|
esac
|
|
[ "$_nrpe_is_root" = 0 ] && echo "NOTE: not running as root; MSR reads skipped, results may be incomplete"
|
|
|
|
# VULN details first, then UNK details (each group in CVE-registry order)
|
|
[ -n "${g_nrpe_vuln_details:-}" ] && printf "%b\n" "$g_nrpe_vuln_details"
|
|
[ -n "${g_nrpe_unk_details:-}" ] && printf "%b\n" "$g_nrpe_unk_details"
|
|
|
|
# Exit with the correct Nagios code when we demoted VULN→UNKNOWN due to non-root
|
|
# (g_critical=1 would otherwise cause exit 2 below)
|
|
[ "$_nrpe_demoted" = 1 ] && exit 3
|
|
fi
|
|
|
|
if [ "$opt_batch" = 1 ] && [ "$opt_batch_format" = "short" ]; then
|
|
_pr_echo 0 "${g_short_output% }"
|
|
fi
|
|
|
|
if [ "$opt_batch" = 1 ] && [ "$opt_batch_format" = "json-terse" ]; then
|
|
_pr_echo 0 "${g_json_output%?}]"
|
|
fi
|
|
|
|
if [ "$opt_batch" = 1 ] && [ "$opt_batch_format" = "json" ]; then
|
|
# Assemble the comprehensive JSON output from pre-built sections
|
|
# Inject mocked flag into meta (g_mocked can be set at any point during the run)
|
|
g_json_meta="${g_json_meta%\}},\"mocked\":$(_json_bool "${g_mocked:-0}")}"
|
|
_json_final='{'
|
|
_json_final="${_json_final}\"meta\":${g_json_meta:-null}"
|
|
_json_final="${_json_final},\"system\":${g_json_system:-null}"
|
|
_json_final="${_json_final},\"cpu\":${g_json_cpu:-null}"
|
|
_json_final="${_json_final},\"cpu_microcode\":${g_json_cpu_microcode:-null}"
|
|
if [ -n "${g_json_vulns:-}" ]; then
|
|
_json_final="${_json_final},\"vulnerabilities\":[${g_json_vulns%,}]"
|
|
else
|
|
_json_final="${_json_final},\"vulnerabilities\":[]"
|
|
fi
|
|
_json_final="${_json_final}}"
|
|
_pr_echo 0 "$_json_final"
|
|
fi
|
|
|
|
if [ "$opt_batch" = 1 ] && [ "$opt_batch_format" = "prometheus" ]; then
|
|
prom_run_as_root='false'
|
|
[ "$(id -u)" -eq 0 ] && prom_run_as_root='true'
|
|
prom_mode="$g_mode"
|
|
prom_paranoid='false'
|
|
[ "$opt_paranoid" = 1 ] && prom_paranoid='true'
|
|
prom_sysfs_only='false'
|
|
[ "$opt_sysfs_only" = 1 ] && prom_sysfs_only='true'
|
|
prom_reduced_accuracy='false'
|
|
[ "${g_bad_accuracy:-0}" = 1 ] && prom_reduced_accuracy='true'
|
|
prom_mocked='false'
|
|
[ "${g_mocked:-0}" = 1 ] && prom_mocked='true'
|
|
echo "# HELP smc_build_info spectre-meltdown-checker script metadata (always 1)"
|
|
echo "# TYPE smc_build_info gauge"
|
|
printf 'smc_build_info{version="%s",mode="%s",run_as_root="%s",paranoid="%s",sysfs_only="%s",reduced_accuracy="%s",mocked="%s"} 1\n' \
|
|
"$(_prom_escape "$VERSION")" \
|
|
"$prom_mode" \
|
|
"$prom_run_as_root" \
|
|
"$prom_paranoid" \
|
|
"$prom_sysfs_only" \
|
|
"$prom_reduced_accuracy" \
|
|
"$prom_mocked"
|
|
if [ -n "${g_smc_system_info_line:-}" ]; then
|
|
echo "# HELP smc_system_info Operating system and kernel metadata (always 1)"
|
|
echo "# TYPE smc_system_info gauge"
|
|
echo "$g_smc_system_info_line"
|
|
fi
|
|
if [ -n "${g_smc_cpu_info_line:-}" ]; then
|
|
echo "# HELP smc_cpu_info CPU hardware and microcode metadata (always 1)"
|
|
echo "# TYPE smc_cpu_info gauge"
|
|
echo "$g_smc_cpu_info_line"
|
|
fi
|
|
echo "# HELP smc_vulnerability_status Vulnerability check result per CVE: 0=not_vulnerable, 1=vulnerable, 2=unknown"
|
|
echo "# TYPE smc_vulnerability_status gauge"
|
|
printf "%b\n" "$g_smc_vuln_output"
|
|
echo "# HELP smc_vulnerable_count Number of CVEs with vulnerable status"
|
|
echo "# TYPE smc_vulnerable_count gauge"
|
|
echo "smc_vulnerable_count $g_smc_vuln_count"
|
|
echo "# HELP smc_unknown_count Number of CVEs with unknown status"
|
|
echo "# TYPE smc_unknown_count gauge"
|
|
echo "smc_unknown_count $g_smc_unk_count"
|
|
echo "# HELP smc_last_scan_timestamp_seconds Unix timestamp when this scan completed"
|
|
echo "# TYPE smc_last_scan_timestamp_seconds gauge"
|
|
echo "smc_last_scan_timestamp_seconds $(date +%s 2>/dev/null || echo 0)"
|
|
fi
|
|
|
|
# exit with the proper exit code
|
|
[ "$g_critical" = 1 ] && exit 2 # critical
|
|
[ "$g_unknown" = 1 ] && exit 3 # unknown
|
|
exit 0 # ok
|