From ff42393fa63e16c1e888e8ee884733ffe9b77293 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Lesimple?= Date: Wed, 8 Apr 2026 21:57:03 +0200 Subject: [PATCH] new batch mode docs, add doc/ to -build branch --- .github/workflows/build.yml | 5 +- DEVELOPMENT.md | 26 +- dist/README.md | 10 +- dist/{ => doc}/FAQ.md | 2 +- .../doc/UNSUPPORTED_CVE_LIST.md | 2 +- dist/doc/batch_json.md | 391 ++++++++++++++++++ dist/doc/batch_json.schema.json | 382 +++++++++++++++++ dist/doc/batch_nrpe.md | 149 +++++++ dist/doc/batch_prometheus.md | 377 +++++++++++++++++ src/main.sh | 4 +- src/vulns/CVE-2017-5715.sh | 2 +- src/vulns/CVE-2017-5753.sh | 4 +- src/vulns/CVE-2018-3640.sh | 2 +- 13 files changed, 1330 insertions(+), 26 deletions(-) rename dist/{ => doc}/FAQ.md (98%) rename UNSUPPORTED_CVE_LIST.md => dist/doc/UNSUPPORTED_CVE_LIST.md (99%) create mode 100644 dist/doc/batch_json.md create mode 100644 dist/doc/batch_json.schema.json create mode 100644 dist/doc/batch_nrpe.md create mode 100644 dist/doc/batch_prometheus.md diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d632100..3e5ff26 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -159,15 +159,18 @@ jobs: fi - name: create a pull request to ${{ github.ref_name }}-build run: | + # all the files in dist/* and .github/* must be moved as is to the -build branch root, move them out for now: tmpdir=$(mktemp -d) mv ./dist/* .github $tmpdir/ rm -rf ./dist + git fetch origin ${{ github.ref_name }}-build git checkout -f ${{ github.ref_name }}-build mv $tmpdir/* . - rm -rf src/ + rm -rf src/ scripts/ img/ mkdir -p .github rsync -vaP --delete $tmpdir/.github/ .github/ + git add --all echo =#=#= DIFF CACHED git diff --cached diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 8d54875..e844947 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -19,7 +19,7 @@ Even though the Linux `sysfs` hierarchy (`/sys/devices/system/cpu/vulnerabilitie - **Independent of kernel knowledge**: A given kernel only understands vulnerabilities known at compile time. This script's detection logic is maintained independently, so it can identify gaps a kernel doesn't yet know about. - **Detailed prerequisite breakdown**: Mitigating a vulnerability can involve multiple layers (microcode, host kernel, hypervisor, guest kernel, software). The script shows exactly which pieces are in place and which are missing. -- **Offline kernel analysis**: The script can inspect a kernel image before it is booted (`--kernel`, `--config`, `--map`), verifying it carries the expected mitigations. +- **No-runtime kernel analysis**: The script can inspect a kernel image before it is booted (`--kernel`, `--config`, `--map`), verifying it carries the expected mitigations. - **Backport-aware**: It detects actual capabilities rather than checking version strings, so it works correctly with vendor kernels that silently backport or forward-port patches. - **Covers gaps in sysfs**: Some vulnerabilities (e.g. Zenbleed) are not reported through `sysfs` at all. @@ -159,7 +159,7 @@ This works because the kernel always has direct access to CPUID (it doesn't need **Rules:** - This is strictly a fallback: `read_cpuid` via `/dev/cpu/N/cpuid` remains the primary method. - Only use it when `read_cpuid` returned `READ_CPUID_RET_ERR` (device unavailable), **never** when it returned `READ_CPUID_RET_KO` (device available but bit is 0 — meaning the CPU/hypervisor explicitly reports the feature as absent). -- Only in live mode (`$opt_live = 1`), since `/proc/cpuinfo` is not available in offline mode. +- Only in live mode (`$opt_runtime = 1`), since `/proc/cpuinfo` is not available in no-runtime mode. - Only for CPUID bits that the kernel exposes as `/proc/cpuinfo` flags. Not all bits have a corresponding flag — only those listed in the kernel's `capflags.c`. If a bit has no `/proc/cpuinfo` flag, no fallback is possible. - The fallback depends on the running kernel being recent enough to know about the CPUID bit in question. An older kernel won't expose a flag it doesn't know about, so the fallback will silently not trigger — which is fine (we just stay at UNKNOWN, same as the ERR case without fallback). @@ -182,7 +182,7 @@ read_cpuid 0x7 0x0 $EDX 31 1 1 ret=$? if [ $ret = $READ_CPUID_RET_OK ]; then cap_ssbd='Intel SSBD' -elif [ $ret = $READ_CPUID_RET_ERR ] && [ "$opt_live" = 1 ]; then +elif [ $ret = $READ_CPUID_RET_ERR ] && [ "$opt_runtime" = 1 ]; then # CPUID device unavailable (e.g. in a VM): fall back to /proc/cpuinfo if grep ^flags "$g_procfs/cpuinfo" | grep -qw ssbd; then cap_ssbd='Intel SSBD (cpuinfo)' @@ -197,7 +197,7 @@ When the fallback sets a `cap_*` variable, append ` (cpuinfo)` to the value stri When a CPU is not explicitly known to be unaffected by a vulnerability, assume that it is affected. This conservative default has been the right call since the early Spectre/Meltdown days and remains sound. -### 6. Offline mode +### 6. No-runtime mode The script can analyze a non-running kernel via `--kernel`, `--config`, `--map` flags, allowing verification before deployment. @@ -388,18 +388,18 @@ This is where the real detection lives. Check for mitigations at each layer: fi ``` - Each source may independently be unavailable (offline mode without the file, or stripped kernel), so check all that are present. A match in any one confirms kernel support. + Each source may independently be unavailable (no-runtime mode without the file, or stripped kernel), so check all that are present. A match in any one confirms kernel support. -- **Runtime state** (live mode only): Read MSRs, check cpuinfo flags, parse dmesg, inspect debugfs. All runtime-only checks — including `/proc/cpuinfo` flags — must be guarded by `if [ "$opt_live" = 1 ]`, both when collecting the evidence in Phase 2 and when using it in Phase 4. In Phase 4, use explicit live/offline branches so that live-only variables (e.g. cpuinfo flags, MSR values) are never referenced in the offline path. +- **Runtime state** (live mode only): Read MSRs, check cpuinfo flags, parse dmesg, inspect debugfs. All runtime-only checks — including `/proc/cpuinfo` flags — must be guarded by `if [ "$opt_runtime" = 1 ]`, both when collecting the evidence in Phase 2 and when using it in Phase 4. In Phase 4, use explicit live/no-runtime branches so that live-only variables (e.g. cpuinfo flags, MSR values) are never referenced in the no-runtime path. ```sh - if [ "$opt_live" = 1 ]; then + if [ "$opt_runtime" = 1 ]; then read_msr 0xADDRESS ret=$? if [ "$ret" = "$READ_MSR_RET_OK" ]; then # check specific bits in ret_read_msr_value_lo / ret_read_msr_value_hi fi else - pstatus blue N/A "not testable in offline mode" + pstatus blue N/A "not testable in no-runtime mode" fi ``` @@ -490,15 +490,15 @@ four categories of information that the script consumes in different modes: 1. **Sysfs messages** — every version of the string the kernel has ever produced for `/sys/devices/system/cpu/vulnerabilities/`. Used in live mode to parse the - kernel's own assessment, and in offline mode to grep for known strings in `$g_kernel`. + kernel's own assessment, and in no-runtime mode to grep for known strings in `$g_kernel`. 2. **Kconfig option names** — every `CONFIG_*` symbol that enables or controls the - mitigation. Used in offline mode to check `$opt_config`. Kconfig names change over + mitigation. Used in no-runtime mode to check `$opt_config`. Kconfig names change over time (e.g. `CONFIG_GDS_FORCE_MITIGATION` → `CONFIG_MITIGATION_GDS_FORCE` → `CONFIG_MITIGATION_GDS`), and vendor kernels may use their own names, so all variants must be catalogued. 3. **Kernel function names** — functions introduced specifically for the mitigation (e.g. `gds_select_mitigation`, `gds_apply_mitigation`, `l1tf_select_mitigation`). Used in - offline mode to check `$opt_map` (System.map): the presence of a mitigation function + no-runtime mode to check `$opt_map` (System.map): the presence of a mitigation function proves the kernel was compiled with the mitigation code, even if the config file is unavailable. 4. **CPU affection logic** — the complete algorithm the kernel uses to decide whether a @@ -776,7 +776,7 @@ CVEs that need VMM context should call `check_has_vmm` early in their `_linux()` - The new CVE appears in the `vulnerabilities` array with correct `cve`, `name`, `aliases`, `cpu_affected`, `status`, `vulnerable`, `info`, `sysfs_status`, and `sysfs_message` fields. - If new `cap_*` variables were added in `check_cpu()`, they appear in `cpu.capabilities` (see Step 2 JSON note). - Run with `--batch json-terse` as well to verify backward-compatible output. -7. **Test offline**: Run with `--kernel`/`--config`/`--map` pointing to a kernel image and verify the offline code path reports correctly. +7. **Test no-runtime**: Run with `--kernel`/`--config`/`--map` pointing to a kernel image and verify the no-runtime code path reports correctly. 8. **Test `--variant` and `--cve`**: Run with `--variant ` and `--cve CVE-YYYY-NNNNN` separately to confirm both selection methods work and produce the same output. 9. **Lint**: Run `shellcheck` on the monolithic script and fix any warnings. @@ -784,7 +784,7 @@ CVEs that need VMM context should call `check_has_vmm` early in their `_linux()` - **Never hardcode kernel or microcode versions** - detect capabilities directly (design principles 2 and 3). Exception: when a microcode fix has no detectable indicator, hardcode fixing versions per CPU (see principle 3). - **Assume affected by default** - only mark a CPU as unaffected when there is positive evidence (design principle 4). -- **Always handle both live and offline modes** - use `$opt_live` to branch, and print `N/A "not testable in offline mode"` for runtime-only checks when offline. +- **Always handle both live and no-runtime modes** - use `$opt_runtime` to branch, and print `N/A "not testable in no-runtime mode"` for runtime-only checks when in no-runtime mode. - **Use `explain()`** when reporting VULN to give actionable remediation advice (see "Cross-Cutting Features" above). - **Handle `--paranoid` and `--vmm`** when the CVE has stricter mitigation tiers or VMM-specific aspects (see "Cross-Cutting Features" above). - **Keep JSON output in sync** - when adding new `cap_*` variables, add them to `_build_json_cpu()` in `src/libs/250_output_emitters.sh` (see Step 2 JSON note above). Per-CVE fields are handled automatically. diff --git a/dist/README.md b/dist/README.md index 8ed34b4..69d176f 100644 --- a/dist/README.md +++ b/dist/README.md @@ -214,17 +214,17 @@ A race condition in the branch predictor update mechanism of Intel processors (C Several transient execution CVEs are not covered by this tool, for various reasons (duplicates, only affecting non-supported hardware or OS, theoretical with no known exploitation, etc.). The complete list along with the reason for each exclusion is available in the -[UNSUPPORTED_CVE_LIST.md](https://github.com/speed47/spectre-meltdown-checker/blob/source/UNSUPPORTED_CVE_LIST.md) file. +[UNSUPPORTED_CVE_LIST.md](doc/UNSUPPORTED_CVE_LIST.md) file. ## Scope Supported operating systems: - Linux (all versions, flavors and distros) -- FreeBSD, NetBSD, DragonFlyBSD and derivatives (others BSDs are [not supported](FAQ.md#which-bsd-oses-are-supported)) +- FreeBSD, NetBSD, DragonFlyBSD and derivatives (others BSDs are [not supported](doc/FAQ.md#which-bsd-oses-are-supported)) -For Linux systems, the tool will detect mitigations, including backported non-vanilla patches, regardless of the advertised kernel version number and the distribution (such as Debian, Ubuntu, CentOS, RHEL, Fedora, openSUSE, Arch, ...), it also works if you've compiled your own kernel. More information [here](FAQ.md#how-does-this-script-work). +For Linux systems, the tool will detect mitigations, including backported non-vanilla patches, regardless of the advertised kernel version number and the distribution (such as Debian, Ubuntu, CentOS, RHEL, Fedora, openSUSE, Arch, ...), it also works if you've compiled your own kernel. More information [here](doc/FAQ.md#how-does-this-script-work). -Other operating systems such as MacOS, Windows, ESXi, etc. [will never be supported](FAQ.md#why-is-my-os-not-supported). +Other operating systems such as MacOS, Windows, ESXi, etc. [will never be supported](doc/FAQ.md#why-is-my-os-not-supported). Supported architectures: - `x86` (32 bits) @@ -236,7 +236,7 @@ Supported architectures: What is the purpose of this tool? Why was it written? How can it be useful to me? How does it work? What can I expect from it? -All these questions (and more) have detailed answers in the [FAQ](FAQ.md), please have a look! +All these questions (and more) have detailed answers in the [FAQ](doc/FAQ.md), please have a look! ## Operating modes diff --git a/dist/FAQ.md b/dist/doc/FAQ.md similarity index 98% rename from dist/FAQ.md rename to dist/doc/FAQ.md index af0922e..246902c 100644 --- a/dist/FAQ.md +++ b/dist/doc/FAQ.md @@ -85,7 +85,7 @@ However I see a few reasons why this script might still be useful to you, and th - The script can be pointed at a kernel image, and will deep dive into it, telling you if this kernel will mitigate vulnerabilities that might be present on your system. This is a good way to verify before booting a new kernel, that it'll mitigate the vulnerabilities you expect it to, especially if you modified a few config options around these topics. -- The script will also work regardless of the custom patches that might be integrated in the kernel you're running (or you're pointing it to, in offline mode), and completely ignores the advertised kernel version, to tell whether a given kernel mitigates vulnerabilities. This is especially useful for non-vanilla kernel, where patches might be backported, sometimes silently (this has already happened, too). +- The script will also work regardless of the custom patches that might be integrated in the kernel you're running (or you're pointing it to, in no-runtime mode), and completely ignores the advertised kernel version, to tell whether a given kernel mitigates vulnerabilities. This is especially useful for non-vanilla kernel, where patches might be backported, sometimes silently (this has already happened, too). - Educational purposes: the script gives interesting insights about a vulnerability, and how the different parts of the system work together to mitigate it. diff --git a/UNSUPPORTED_CVE_LIST.md b/dist/doc/UNSUPPORTED_CVE_LIST.md similarity index 99% rename from UNSUPPORTED_CVE_LIST.md rename to dist/doc/UNSUPPORTED_CVE_LIST.md index 12330b9..03c9d60 100644 --- a/UNSUPPORTED_CVE_LIST.md +++ b/dist/doc/UNSUPPORTED_CVE_LIST.md @@ -81,7 +81,7 @@ ARM processors may speculatively execute instructions past unconditional control VUSec researchers demonstrated that the original BHI mitigation (disabling unprivileged eBPF) was insufficient: 1,511 native kernel gadgets exist that allow exploiting Branch History Injection without eBPF, leaking arbitrary kernel memory at ~3.5 kB/sec on Intel CPUs. -**Why out of scope:** CVE-2024-2201 is not a new hardware vulnerability — it is the same BHI hardware bug as CVE-2022-0002, but proves that eBPF restriction alone was never sufficient. The required mitigations are identical: `BHI_DIS_S` hardware control (MSR `IA32_SPEC_CTRL` bit 10), software BHB clearing loop at syscall entry and VM exit, or retpoline with RRSBA disabled. These are all already detected by this tool's CVE-2017-5715 (Spectre V2) checks, which parse the `BHI:` suffix from `/sys/devices/system/cpu/vulnerabilities/spectre_v2` and check for `CONFIG_MITIGATION_SPECTRE_BHI` in offline mode. No new sysfs entry, MSR, kernel config option, or boot parameter was introduced for this CVE. +**Why out of scope:** CVE-2024-2201 is not a new hardware vulnerability — it is the same BHI hardware bug as CVE-2022-0002, but proves that eBPF restriction alone was never sufficient. The required mitigations are identical: `BHI_DIS_S` hardware control (MSR `IA32_SPEC_CTRL` bit 10), software BHB clearing loop at syscall entry and VM exit, or retpoline with RRSBA disabled. These are all already detected by this tool's CVE-2017-5715 (Spectre V2) checks, which parse the `BHI:` suffix from `/sys/devices/system/cpu/vulnerabilities/spectre_v2` and check for `CONFIG_MITIGATION_SPECTRE_BHI` in no-runtime mode. No new sysfs entry, MSR, kernel config option, or boot parameter was introduced for this CVE. ## CVE-2020-0549 — L1D Eviction Sampling (CacheOut) diff --git a/dist/doc/batch_json.md b/dist/doc/batch_json.md new file mode 100644 index 0000000..f6224a4 --- /dev/null +++ b/dist/doc/batch_json.md @@ -0,0 +1,391 @@ +# JSON Output Format + +`--batch json` emits a single, self-contained JSON object that describes the +scan environment and the result of every CVE check. You can feed it to your +monitoring system, to a SIEM, to a time-series database, you name it. + +```sh +sudo ./spectre-meltdown-checker.sh --batch json | jq . +``` + +## Top-level schema + +``` +{ + "meta": { ... }, // Run metadata and flags + "system": { ... }, // Kernel and host context + "cpu": { ... }, // CPU hardware identification + "cpu_microcode": { ... }, // Microcode version and status + "vulnerabilities": [ ... ] // One object per checked CVE +} +``` + +`format_version` in `meta` is an integer that will be incremented on +backward-incompatible schema changes. The current value is **1**. + +## Section reference + +### `meta` + +Run metadata. Always present. + +| Field | Type | Values | Meaning | +|---|---|---|---| +| `script_version` | string | e.g. `"25.30.0250400123"` | Script version | +| `format_version` | integer | `1` | JSON schema version; incremented on breaking changes | +| `timestamp` | string | ISO 8601 UTC, e.g. `"2025-04-07T12:00:00Z"` | When the scan started | +| `os` | string | e.g. `"Linux"`, `"FreeBSD"` | Output of `uname -s` | +| `mode` | string | `"live"` / `"no-runtime"` / `"no-hw"` / `"hw-only"` | Operating mode (see [modes](README.md#operating-modes)) | +| `run_as_root` | boolean | | Whether the script ran as root. Non-root scans skip MSR reads and may miss mitigations | +| `reduced_accuracy` | boolean | | Kernel image, config, or System.map was missing; some checks fall back to weaker heuristics | +| `paranoid` | boolean | | `--paranoid` mode: stricter criteria (e.g. requires SMT disabled, IBPB always-on) | +| `sysfs_only` | boolean | | `--sysfs-only`: only the kernel's own sysfs report was used, not independent detection | +| `extra` | boolean | | `--extra`: additional experimental checks were enabled | +| `mocked` | boolean | | One or more CPU values were overridden for testing. Results do **not** reflect the real system | + +**Example:** +```json +"meta": { + "script_version": "25.30.025040123", + "format_version": 1, + "timestamp": "2025-04-07T12:00:00Z", + "os": "Linux", + "mode": "live", + "run_as_root": true, + "reduced_accuracy": false, + "paranoid": false, + "sysfs_only": false, + "extra": false, + "mocked": false +} +``` + +**Important flags for fleet operators:** + +- `run_as_root: false` means the scan was incomplete. Treat results as lower + confidence. Alert separately: results may be missing or wrong. +- `sysfs_only: true` means the script trusted the kernel's self-report without + independent verification. Some older kernels misreport their mitigation + status. Do not use `--sysfs-only` for production fleet monitoring. +- `paranoid: true` raises the bar: only compare `vulnerable` counts across + hosts with the same `paranoid` value. +- `mocked: true` must never appear on a production host. If it does, every + downstream result is fabricated. + +--- + +### `system` + +Kernel and host environment. Always present. + +| Field | Type | Values | Meaning | +|---|---|---|---| +| `kernel_release` | string \| null | e.g. `"6.1.0-21-amd64"` | Output of `uname -r` (null in no-runtime, no-hw, and hw-only modes) | +| `kernel_version` | string \| null | e.g. `"#1 SMP Debian …"` | Output of `uname -v` (null in no-runtime, no-hw, and hw-only modes) | +| `kernel_arch` | string \| null | e.g. `"x86_64"` | Output of `uname -m` (null in no-runtime, no-hw, and hw-only modes) | +| `kernel_image` | string \| null | e.g. `"/boot/vmlinuz-6.1.0-21-amd64"` | Path passed via `--kernel`, or null if not specified | +| `kernel_config` | string \| null | | Path passed via `--config`, or null | +| `kernel_version_string` | string \| null | | Kernel version banner extracted from the image | +| `kernel_cmdline` | string \| null | | Kernel command line from `/proc/cmdline` (live mode) or the image | +| `cpu_count` | integer \| null | | Number of logical CPUs detected | +| `smt_enabled` | boolean \| null | | Whether SMT (HyperThreading) is currently active; null if undeterminable | +| `hypervisor_host` | boolean \| null | | Whether this machine is detected as a VM host (running KVM, Xen, VMware, etc.) | +| `hypervisor_host_reason` | string \| null | | Human-readable explanation of why `hypervisor_host` was set | + +**`hypervisor_host`** materially changes the risk profile of several CVEs. +L1TF (CVE-2018-3646) and MDS (CVE-2018-12126/12130/12127) are significantly +more severe on hypervisor hosts because they can be exploited across VM +boundaries by a malicious guest. Prioritise remediation where +`hypervisor_host: true`. + +--- + +### `cpu` + +CPU hardware identification. `null` when `--no-hw` is active. + +The object uses `arch` as a discriminator: `"x86"` for Intel/AMD/Hygon CPUs, +`"arm"` for ARM/Cavium/Phytium. Arch-specific fields live under a matching +sub-object (`cpu.x86` or `cpu.arm`), so consumers never see irrelevant null +fields from the other architecture. + +#### Common fields + +| Field | Type | Values | Meaning | +|---|---|---|---| +| `arch` | string | `"x86"` / `"arm"` | CPU architecture family; determines which sub-object is present | +| `vendor` | string \| null | e.g. `"GenuineIntel"`, `"ARM"` | CPU vendor string | +| `friendly_name` | string \| null | e.g. `"Intel(R) Core(TM) i7-9700K CPU @ 3.60GHz"` | Human-readable CPU model | + +#### `cpu.x86` (present when `arch == "x86"`) + +| Field | Type | Values | Meaning | +|---|---|---|---| +| `family` | integer \| null | | CPU family number | +| `model` | integer \| null | | CPU model number | +| `stepping` | integer \| null | | CPU stepping number | +| `cpuid` | string \| null | hex, e.g. `"0x000906ed"` | Full CPUID leaf 1 EAX value | +| `platform_id` | integer \| null | | Intel platform ID (from MSR 0x17); null on AMD | +| `hybrid` | boolean \| null | | Whether this is a hybrid CPU (P-cores + E-cores, e.g. Alder Lake) | +| `codename` | string \| null | e.g. `"Coffee Lake"` | Intel CPU codename; null on AMD | +| `capabilities` | object | | CPU feature flags (see below) | + +#### `cpu.arm` (present when `arch == "arm"`) + +| Field | Type | Values | Meaning | +|---|---|---|---| +| `part_list` | string \| null | e.g. `"0xd0b 0xd05"` | Space-separated ARM part numbers across cores (big.LITTLE may have several) | +| `arch_list` | string \| null | e.g. `"8 8"` | Space-separated ARM architecture levels across cores | +| `capabilities` | object | | ARM-specific capability flags (currently empty; reserved for future use) | + +#### `cpu.x86.capabilities` + +Each capability is a **tri-state**: `true` (present), `false` (absent), or +`null` (not applicable or could not be read, e.g. when not root or on AMD for +Intel-specific features). + +| Capability | Meaning | +|---|---| +| `spec_ctrl` | SPEC_CTRL MSR (Intel: ibrs + ibpb via WRMSR; required for many mitigations) | +| `ibrs` | Indirect Branch Restricted Speculation | +| `ibpb` | Indirect Branch Prediction Barrier | +| `ibpb_ret` | IBPB on return (enhanced form) | +| `stibp` | Single Thread Indirect Branch Predictors | +| `ssbd` | Speculative Store Bypass Disable | +| `l1d_flush` | L1D cache flush instruction | +| `md_clear` | VERW clears CPU buffers (MDS mitigation) | +| `arch_capabilities` | IA32_ARCH_CAPABILITIES MSR is present | +| `rdcl_no` | Not susceptible to RDCL (Meltdown-like attacks) | +| `ibrs_all` | Enhanced IBRS always-on mode supported | +| `rsba` | RSB may use return predictions from outside the RSB | +| `l1dflush_no` | Not susceptible to L1D flush side-channel | +| `ssb_no` | Not susceptible to Speculative Store Bypass | +| `mds_no` | Not susceptible to MDS | +| `taa_no` | Not susceptible to TSX Asynchronous Abort | +| `pschange_msc_no` | Page-size-change MSC not susceptible | +| `tsx_ctrl_msr` | TSX_CTRL MSR is present | +| `tsx_ctrl_rtm_disable` | RTM disabled via TSX_CTRL | +| `tsx_ctrl_cpuid_clear` | CPUID HLE/RTM bits cleared via TSX_CTRL | +| `gds_ctrl` | GDS_CTRL MSR present (GDS mitigation control) | +| `gds_no` | Not susceptible to Gather Data Sampling | +| `gds_mitg_dis` | GDS mitigation disabled | +| `gds_mitg_lock` | GDS mitigation locked | +| `rfds_no` | Not susceptible to Register File Data Sampling | +| `rfds_clear` | VERW clears register file stale data | +| `its_no` | Not susceptible to Indirect Target Selection | +| `sbdr_ssdp_no` | Not susceptible to SBDR/SSDP | +| `fbsdp_no` | Not susceptible to FBSDP | +| `psdp_no` | Not susceptible to PSDP | +| `fb_clear` | Fill buffer cleared on idle/C6 | +| `rtm` | Restricted Transactional Memory (TSX RTM) present | +| `tsx_force_abort` | TSX_FORCE_ABORT MSR present | +| `tsx_force_abort_rtm_disable` | RTM disabled via TSX_FORCE_ABORT | +| `tsx_force_abort_cpuid_clear` | CPUID RTM cleared via TSX_FORCE_ABORT | +| `sgx` | Software Guard Extensions present | +| `srbds` | SRBDS affected | +| `srbds_on` | SRBDS mitigation active | +| `amd_ssb_no` | AMD: not susceptible to Speculative Store Bypass | +| `hygon_ssb_no` | Hygon: not susceptible to Speculative Store Bypass | +| `ipred` | Indirect Predictor Barrier support | +| `rrsba` | Restricted RSB Alternate (Intel Retbleed mitigation) | +| `bhi` | Branch History Injection mitigation support | +| `tsa_sq_no` | Not susceptible to TSA-SQ | +| `tsa_l1_no` | Not susceptible to TSA-L1 | +| `verw_clear` | VERW clears CPU buffers | +| `autoibrs` | AMD AutoIBRS (equivalent to enhanced IBRS on Intel) | +| `sbpb` | Selective Branch Predictor Barrier (AMD Inception mitigation) | +| `avx2` | AVX2 supported (relevant to Downfall / GDS) | +| `avx512` | AVX-512 supported (relevant to Downfall / GDS) | + +--- + +### `cpu_microcode` + +Microcode version and status. `null` under the same conditions as `cpu`. + +| Field | Type | Values | Meaning | +|---|---|---|---| +| `installed_version` | string \| null | hex, e.g. `"0xf4"` | Currently running microcode revision | +| `latest_version` | string \| null | hex | Latest known-good version in the firmware database; null if CPU is not in the database | +| `microcode_up_to_date` | boolean \| null | | Whether `installed_version == latest_version`; null if either is unavailable | +| `is_blacklisted` | boolean | | Whether the installed microcode is known to cause instability and must be rolled back | +| `message` | string \| null | | Human-readable note from the firmware database (e.g. changelog excerpt) | +| `db_source` | string \| null | | Which database was used (e.g. `"Intel-SA"`, `"MCExtractor"`) | +| `db_info` | string \| null | | Database revision or date | + +**`is_blacklisted: true`** means the installed microcode is known to cause +system instability or incorrect behaviour. Treat this as a P1 incident: roll +back to the previous microcode immediately. + +**`microcode_up_to_date: false`** means a newer microcode is available. This +does not necessarily mean the system is vulnerable (the current microcode may +still include all required mitigations), but warrants investigation. + +--- + +### `vulnerabilities` + +Array of CVE check results. One object per checked CVE, in check order. +Empty array (`[]`) if no CVEs were checked (unusual; would require `--cve` +with an unknown CVE ID). + +| Field | Type | Values | Meaning | +|---|---|---|---| +| `cve` | string | e.g. `"CVE-2017-5753"` | CVE identifier | +| `name` | string | e.g. `"SPECTRE VARIANT 1"` | Short key name used in batch formats | +| `aliases` | string \| null | e.g. `"Spectre Variant 1, bounds check bypass"` | Full name including all known aliases | +| `cpu_affected` | boolean | | Whether this CPU's hardware design is affected by this CVE | +| `status` | string | `"OK"` / `"VULN"` / `"UNK"` | Check outcome (see below) | +| `vulnerable` | boolean \| null | `false` / `true` / `null` | `false`=OK, `true`=VULN, `null`=UNK | +| `info` | string | | Human-readable description of the specific mitigation state or reason | +| `sysfs_status` | string \| null | `"OK"` / `"VULN"` / `"UNK"` / null | Status as reported by the kernel via `/sys/devices/system/cpu/vulnerabilities/`; null if sysfs was not consulted for this CVE | +| `sysfs_message` | string \| null | | Raw text from the sysfs file (e.g. `"Mitigation: PTI"`); null if sysfs was not consulted | + +#### Status values + +| `status` | `vulnerable` | Meaning | +|---|---|---| +| `"OK"` | `false` | CPU is unaffected by design, or all required mitigations are in place | +| `"VULN"` | `true` | CPU is affected and mitigations are missing or insufficient | +| `"UNK"` | `null` | The script could not determine the status (missing kernel info, insufficient privileges, or no detection logic for this platform) | + +#### `cpu_affected` explained + +`cpu_affected: false` with `status: "OK"` means the CPU hardware is +architecturally immune, no patch was ever needed. + +`cpu_affected: true` with `status: "OK"` means the hardware has the weakness +but all required mitigations (kernel, microcode, or both) are in place. + +This distinction matters for fleet auditing: filter on `cpu_affected: true` to +see only systems where mitigation effort was actually required and confirmed. + +#### `sysfs_status` vs `status` + +`sysfs_status` is the raw kernel self-report. `status` is the script's +independent assessment, which may differ: + +- The script may **upgrade** a sysfs `"VULN"` to `"OK"` when it detects a + silent backport that the kernel doesn't know about. +- The script may **downgrade** a sysfs `"OK"` to `"VULN"` when it detects an + incomplete mitigation the kernel doesn't flag (e.g. L1TF on a hypervisor + host with SMT still enabled, or TSA in `user` mode on a VMM host). +- `sysfs_status` is `null` when the kernel has no sysfs entry for this CVE + (older kernels, or CVEs not yet tracked by the kernel). + +Always use `status` / `vulnerable` for alerting. Use `sysfs_status` for +diagnostics and audit trails. + +**Example:** +```json +{ + "cve": "CVE-2017-5715", + "name": "SPECTRE VARIANT 2", + "aliases": "Spectre Variant 2, branch target injection", + "cpu_affected": true, + "status": "OK", + "vulnerable": false, + "info": "Full generic retpoline is mitigating the vulnerability", + "sysfs_status": "OK", + "sysfs_message": "Mitigation: Retpolines; IBPB: conditional; IBRS_FW; STIBP: conditional; RSB filling; PBRSB-eIBRS: Not affected; BHI: Not affected" +} +``` + +--- + +## Exit codes + +The script exits with: + +| Code | Meaning | +|---|---| +| `0` | All checked CVEs are `OK` | +| `2` | At least one CVE is `VULN` | +| `3` | No CVEs are `VULN`, but at least one is `UNK` | + +These exit codes are the same in all batch modes and in interactive mode. +Use them in combination with the JSON body for reliable alerting. + +--- + +## Caveats and edge cases + +**No-runtime mode (`--no-runtime`)** +`system.kernel_release`, `kernel_version`, and `kernel_arch` are null (those +come from `uname`, which reports the running kernel, not the inspected one). +`meta.mode: "no-runtime"` signals this. `system.kernel_image` and +`system.kernel_version_string` carry the inspected image path and banner +instead. + +**No-hardware mode (`--no-hw`)** +`cpu` and `cpu_microcode` are null. CVE checks that rely on hardware +capability detection (`cap_*` flags, MSR reads) will report `status: "UNK"`. +`cpu_affected` will be `false` for all CVEs (cannot determine affection without +hardware info). `meta.mode: "no-hw"` signals this. + +**Hardware-only mode (`--hw-only`)** +Only CPU information and per-CVE affectedness are reported. No kernel +inspection is performed, so vulnerability mitigations are not checked. +`meta.mode: "hw-only"` signals this. + +**`--sysfs-only`** +The script trusts the kernel's sysfs report without running independent +detection. `meta.sysfs_only: true` flags this. Some older kernels misreport +their status. Do not use for production fleet monitoring. + +**`--paranoid`** +Enables defense-in-depth checks beyond the security community consensus. +A `status: "OK"` under `paranoid: true` means a higher bar was met. Do not +compare results across hosts with different `paranoid` values. + +**`reduced_accuracy`** +Set when the kernel image, config file, or System.map could not be read. +Some checks fall back to weaker heuristics and may report `"UNK"` for CVEs +that are actually mitigated. + +**Non-x86 architectures (ARM, ARM64)** +On ARM, `cpu.arch` is `"arm"` and the `cpu.arm` sub-object carries `part_list` +and `arch_list`. The x86-specific sub-object is absent (no null noise). +`cpu.arm.capabilities` is currently empty; ARM-specific flags will be added +there as needed. + +**`mocked: true`** +Must never appear on a production host. If it does, the results are +fabricated and every downstream alert is unreliable. + +--- + +## Schema stability + +`meta.format_version` is incremented on backward-incompatible changes (field +removal or type change). Additive changes (new fields) do not increment the +version; consumers must tolerate unknown fields. + +Recommended practice: check `format_version == 1` at parse time and reject +or alert on any other value until you have tested compatibility with the new +version. + +--- + +## Migration from `json-terse` + +The legacy `--batch json-terse` format emits a flat array of objects: + +```json +[ + {"NAME": "SPECTRE VARIANT 1", "CVE": "CVE-2017-5753", "VULNERABLE": false, "INFOS": "..."}, + ... +] +``` + +It carries no system, CPU, or microcode context. It has no sysfs data. It +uses uppercase field names. + +To migrate: + +1. Replace `--batch json-terse` with `--batch json`. +2. The equivalent of the old `VULNERABLE` field is `vulnerabilities[].vulnerable`. +3. The equivalent of the old `INFOS` field is `vulnerabilities[].info`. +4. The equivalent of the old `NAME` field is `vulnerabilities[].name`. +5. The old format is still available as `--batch json-terse` for transition + periods. diff --git a/dist/doc/batch_json.schema.json b/dist/doc/batch_json.schema.json new file mode 100644 index 0000000..75e4f71 --- /dev/null +++ b/dist/doc/batch_json.schema.json @@ -0,0 +1,382 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/speed47/spectre-meltdown-checker/dist/batch_json.schema.json", + "title": "spectre-meltdown-checker --batch json output", + "description": "Schema for the comprehensive JSON output produced by spectre-meltdown-checker.sh --batch json. format_version 1.", + "type": "object", + "required": ["meta", "system", "cpu", "cpu_microcode", "vulnerabilities"], + "additionalProperties": false, + "properties": { + + "meta": { + "description": "Run metadata and option flags.", + "type": "object", + "required": [ + "script_version", "format_version", "timestamp", "os", "mode", + "run_as_root", "reduced_accuracy", "paranoid", "sysfs_only", + "extra", "mocked" + ], + "additionalProperties": false, + "properties": { + "script_version": { + "description": "Script version string, e.g. '25.30.0250400123'.", + "type": ["string", "null"] + }, + "format_version": { + "description": "JSON schema version. Incremented on backward-incompatible changes. Current value: 1.", + "type": "integer", + "const": 1 + }, + "timestamp": { + "description": "ISO 8601 UTC timestamp of when the scan started, e.g. '2025-04-07T12:00:00Z'.", + "type": ["string", "null"] + }, + "os": { + "description": "Operating system name from uname -s, e.g. 'Linux', 'FreeBSD'.", + "type": ["string", "null"] + }, + "mode": { + "description": "Operating mode: 'live' (default), 'no-runtime' (--no-runtime), 'no-hw' (--no-hw), or 'hw-only' (--hw-only).", + "type": "string", + "enum": ["live", "no-runtime", "no-hw", "hw-only"] + }, + "run_as_root": { + "description": "Whether the script ran as root. Non-root scans skip MSR reads and may produce incomplete or inaccurate results.", + "type": "boolean" + }, + "reduced_accuracy": { + "description": "True when the kernel image, config, or System.map was missing. Some checks fall back to weaker heuristics.", + "type": ["boolean", "null"] + }, + "paranoid": { + "description": "True when --paranoid was set: stricter criteria (e.g. requires SMT disabled, IBPB always-on).", + "type": "boolean" + }, + "sysfs_only": { + "description": "True when --sysfs-only was set: the script trusted the kernel's own sysfs report without independent detection.", + "type": "boolean" + }, + "extra": { + "description": "True when --extra was set: additional experimental checks were enabled.", + "type": "boolean" + }, + "mocked": { + "description": "True when one or more CPU values were overridden for testing. Results do NOT reflect the real system.", + "type": ["boolean", "null"] + } + } + }, + + "system": { + "description": "Kernel and host environment context.", + "type": ["object", "null"], + "required": [ + "kernel_release", "kernel_version", "kernel_arch", + "kernel_image", "kernel_config", "kernel_version_string", + "kernel_cmdline", "cpu_count", "smt_enabled", + "hypervisor_host", "hypervisor_host_reason" + ], + "additionalProperties": false, + "properties": { + "kernel_release": { + "description": "Output of uname -r (live mode only), e.g. '6.1.0-21-amd64'. Null in other modes.", + "type": ["string", "null"] + }, + "kernel_version": { + "description": "Output of uname -v (live mode only), e.g. '#1 SMP Debian …'. Null in other modes.", + "type": ["string", "null"] + }, + "kernel_arch": { + "description": "Output of uname -m (live mode only), e.g. 'x86_64'. Null in other modes.", + "type": ["string", "null"] + }, + "kernel_image": { + "description": "Path to the kernel image passed via --kernel. Null in live mode.", + "type": ["string", "null"] + }, + "kernel_config": { + "description": "Path to the kernel config passed via --config. Null if not provided.", + "type": ["string", "null"] + }, + "kernel_version_string": { + "description": "Kernel version banner extracted from the image. Null if unavailable.", + "type": ["string", "null"] + }, + "kernel_cmdline": { + "description": "Kernel command line from /proc/cmdline (live mode) or the image. Null if unavailable.", + "type": ["string", "null"] + }, + "cpu_count": { + "description": "Number of logical CPUs detected (max core ID + 1). Null if undeterminable.", + "type": ["integer", "null"], + "minimum": 1 + }, + "smt_enabled": { + "description": "Whether SMT (HyperThreading) is currently enabled. Null if the script could not determine the state.", + "type": ["boolean", "null"] + }, + "hypervisor_host": { + "description": "Whether this machine is detected as a VM host (running KVM, Xen, VMware, etc.). Null if undeterminable.", + "type": ["boolean", "null"] + }, + "hypervisor_host_reason": { + "description": "Human-readable explanation of why hypervisor_host was set. Null if hypervisor_host is false or null.", + "type": ["string", "null"] + } + } + }, + + "cpu": { + "description": "CPU hardware identification. Null when --no-hw is active. Contains an 'arch' discriminator ('x86' or 'arm') and a matching arch-specific sub-object with identification fields and capabilities.", + "oneOf": [ + { "type": "null" }, + { + "type": "object", + "description": "x86 CPU (Intel, AMD, Hygon).", + "required": ["arch", "vendor", "friendly_name", "x86"], + "additionalProperties": false, + "properties": { + "arch": { "type": "string", "const": "x86" }, + "vendor": { + "description": "CPU vendor string: 'GenuineIntel', 'AuthenticAMD', or 'HygonGenuine'.", + "type": ["string", "null"] + }, + "friendly_name": { + "description": "Human-readable CPU model from /proc/cpuinfo, e.g. 'Intel(R) Core(TM) i7-9700K CPU @ 3.60GHz'.", + "type": ["string", "null"] + }, + "x86": { + "type": "object", + "required": ["family", "model", "stepping", "cpuid", "platform_id", "hybrid", "codename", "capabilities"], + "additionalProperties": false, + "properties": { + "family": { + "description": "CPU family number.", + "type": ["integer", "null"] + }, + "model": { + "description": "CPU model number.", + "type": ["integer", "null"] + }, + "stepping": { + "description": "CPU stepping number.", + "type": ["integer", "null"] + }, + "cpuid": { + "description": "Full CPUID leaf 1 EAX value as a hex string, e.g. '0x000906ed'.", + "type": ["string", "null"], + "pattern": "^0x[0-9a-f]+$" + }, + "platform_id": { + "description": "Intel platform ID from MSR 0x17. Null on AMD.", + "type": ["integer", "null"] + }, + "hybrid": { + "description": "Whether this is a hybrid CPU (P-cores + E-cores, e.g. Alder Lake). Null if undeterminable.", + "type": ["boolean", "null"] + }, + "codename": { + "description": "Intel CPU codename, e.g. 'Coffee Lake'. Null on AMD.", + "type": ["string", "null"] + }, + "capabilities": { + "description": "CPU feature flags detected via CPUID and MSR reads. Each value is true (present), false (absent), or null (not applicable or could not be read).", + "type": "object", + "additionalProperties": false, + "properties": { + "spec_ctrl": { "type": ["boolean", "null"], "description": "SPEC_CTRL MSR present (Intel; enables IBRS + IBPB via WRMSR)" }, + "ibrs": { "type": ["boolean", "null"], "description": "Indirect Branch Restricted Speculation" }, + "ibpb": { "type": ["boolean", "null"], "description": "Indirect Branch Prediction Barrier" }, + "ibpb_ret": { "type": ["boolean", "null"], "description": "IBPB on return (enhanced form)" }, + "stibp": { "type": ["boolean", "null"], "description": "Single Thread Indirect Branch Predictors" }, + "ssbd": { "type": ["boolean", "null"], "description": "Speculative Store Bypass Disable" }, + "l1d_flush": { "type": ["boolean", "null"], "description": "L1D cache flush instruction" }, + "md_clear": { "type": ["boolean", "null"], "description": "VERW clears CPU buffers (MDS mitigation)" }, + "arch_capabilities": { "type": ["boolean", "null"], "description": "IA32_ARCH_CAPABILITIES MSR is present" }, + "rdcl_no": { "type": ["boolean", "null"], "description": "Not susceptible to RDCL (Meltdown-like attacks)" }, + "ibrs_all": { "type": ["boolean", "null"], "description": "Enhanced IBRS always-on mode supported" }, + "rsba": { "type": ["boolean", "null"], "description": "RSB may use return predictions from outside the RSB" }, + "l1dflush_no": { "type": ["boolean", "null"], "description": "Not susceptible to L1D flush side-channel" }, + "ssb_no": { "type": ["boolean", "null"], "description": "Not susceptible to Speculative Store Bypass" }, + "mds_no": { "type": ["boolean", "null"], "description": "Not susceptible to MDS" }, + "taa_no": { "type": ["boolean", "null"], "description": "Not susceptible to TSX Asynchronous Abort" }, + "pschange_msc_no": { "type": ["boolean", "null"], "description": "Page-size-change MSC not susceptible" }, + "tsx_ctrl_msr": { "type": ["boolean", "null"], "description": "TSX_CTRL MSR is present" }, + "tsx_ctrl_rtm_disable": { "type": ["boolean", "null"], "description": "RTM disabled via TSX_CTRL" }, + "tsx_ctrl_cpuid_clear": { "type": ["boolean", "null"], "description": "CPUID HLE/RTM bits cleared via TSX_CTRL" }, + "gds_ctrl": { "type": ["boolean", "null"], "description": "GDS_CTRL MSR present" }, + "gds_no": { "type": ["boolean", "null"], "description": "Not susceptible to Gather Data Sampling" }, + "gds_mitg_dis": { "type": ["boolean", "null"], "description": "GDS mitigation disabled" }, + "gds_mitg_lock": { "type": ["boolean", "null"], "description": "GDS mitigation locked" }, + "rfds_no": { "type": ["boolean", "null"], "description": "Not susceptible to Register File Data Sampling" }, + "rfds_clear": { "type": ["boolean", "null"], "description": "VERW clears register file stale data" }, + "its_no": { "type": ["boolean", "null"], "description": "Not susceptible to Indirect Target Selection" }, + "sbdr_ssdp_no": { "type": ["boolean", "null"], "description": "Not susceptible to SBDR/SSDP" }, + "fbsdp_no": { "type": ["boolean", "null"], "description": "Not susceptible to FBSDP" }, + "psdp_no": { "type": ["boolean", "null"], "description": "Not susceptible to PSDP" }, + "fb_clear": { "type": ["boolean", "null"], "description": "Fill buffer cleared on idle/C6" }, + "rtm": { "type": ["boolean", "null"], "description": "Restricted Transactional Memory (TSX RTM) present" }, + "tsx_force_abort": { "type": ["boolean", "null"], "description": "TSX_FORCE_ABORT MSR present" }, + "tsx_force_abort_rtm_disable": { "type": ["boolean", "null"], "description": "RTM disabled via TSX_FORCE_ABORT" }, + "tsx_force_abort_cpuid_clear": { "type": ["boolean", "null"], "description": "CPUID RTM cleared via TSX_FORCE_ABORT" }, + "sgx": { "type": ["boolean", "null"], "description": "Software Guard Extensions present" }, + "srbds": { "type": ["boolean", "null"], "description": "SRBDS affected" }, + "srbds_on": { "type": ["boolean", "null"], "description": "SRBDS mitigation active" }, + "amd_ssb_no": { "type": ["boolean", "null"], "description": "AMD: not susceptible to Speculative Store Bypass" }, + "hygon_ssb_no": { "type": ["boolean", "null"], "description": "Hygon: not susceptible to Speculative Store Bypass" }, + "ipred": { "type": ["boolean", "null"], "description": "Indirect Predictor Barrier support" }, + "rrsba": { "type": ["boolean", "null"], "description": "Restricted RSB Alternate (Intel Retbleed mitigation)" }, + "bhi": { "type": ["boolean", "null"], "description": "Branch History Injection mitigation support" }, + "tsa_sq_no": { "type": ["boolean", "null"], "description": "Not susceptible to TSA-SQ" }, + "tsa_l1_no": { "type": ["boolean", "null"], "description": "Not susceptible to TSA-L1" }, + "verw_clear": { "type": ["boolean", "null"], "description": "VERW clears CPU buffers" }, + "autoibrs": { "type": ["boolean", "null"], "description": "AMD AutoIBRS (equivalent to enhanced IBRS on Intel)" }, + "sbpb": { "type": ["boolean", "null"], "description": "Selective Branch Predictor Barrier (AMD Inception mitigation)" }, + "avx2": { "type": ["boolean", "null"], "description": "AVX2 supported (relevant to Downfall / GDS)" }, + "avx512": { "type": ["boolean", "null"], "description": "AVX-512 supported (relevant to Downfall / GDS)" } + } + } + } + } + } + }, + { + "type": "object", + "description": "ARM CPU (ARM, Cavium, Phytium).", + "required": ["arch", "vendor", "friendly_name", "arm"], + "additionalProperties": false, + "properties": { + "arch": { "type": "string", "const": "arm" }, + "vendor": { + "description": "CPU vendor string: 'ARM', 'CAVIUM', or 'PHYTIUM'.", + "type": ["string", "null"] + }, + "friendly_name": { + "description": "Human-readable CPU model, e.g. 'ARM v8 model 0xd0b'.", + "type": ["string", "null"] + }, + "arm": { + "type": "object", + "required": ["part_list", "arch_list", "capabilities"], + "additionalProperties": false, + "properties": { + "part_list": { + "description": "Space-separated list of ARM part numbers detected across cores, e.g. '0xd0b 0xd05' (big.LITTLE).", + "type": ["string", "null"] + }, + "arch_list": { + "description": "Space-separated list of ARM architecture levels detected across cores, e.g. '8 8'.", + "type": ["string", "null"] + }, + "capabilities": { + "description": "ARM-specific CPU capability flags. Currently empty; reserved for future use.", + "type": "object", + "additionalProperties": false, + "properties": {} + } + } + } + } + } + ] + }, + + "cpu_microcode": { + "description": "Microcode version and firmware database status. Null under the same conditions as cpu.", + "type": ["object", "null"], + "required": [ + "installed_version", "latest_version", "microcode_up_to_date", + "is_blacklisted", "message", "db_source", "db_info" + ], + "additionalProperties": false, + "properties": { + "installed_version": { + "description": "Currently running microcode revision as a hex string, e.g. '0xf4'. Null if unreadable.", + "type": ["string", "null"], + "pattern": "^0x[0-9a-f]+$" + }, + "latest_version": { + "description": "Latest known-good microcode version from the firmware database, as a hex string. Null if the CPU is not in the database.", + "type": ["string", "null"], + "pattern": "^0x[0-9a-f]+$" + }, + "microcode_up_to_date": { + "description": "True when installed_version equals latest_version. Null if either is unavailable.", + "type": ["boolean", "null"] + }, + "is_blacklisted": { + "description": "True when the installed microcode is known to cause instability and must be rolled back immediately.", + "type": "boolean" + }, + "message": { + "description": "Human-readable note from the firmware database (e.g. changelog excerpt). Null if absent.", + "type": ["string", "null"] + }, + "db_source": { + "description": "Which firmware database was used, e.g. 'Intel-SA', 'MCExtractor'. Null if unavailable.", + "type": ["string", "null"] + }, + "db_info": { + "description": "Firmware database revision or date string. Null if unavailable.", + "type": ["string", "null"] + } + } + }, + + "vulnerabilities": { + "description": "Array of CVE check results, one per checked CVE, in check order.", + "type": "array", + "items": { + "type": "object", + "required": [ + "cve", "name", "aliases", "cpu_affected", + "status", "vulnerable", "info", + "sysfs_status", "sysfs_message" + ], + "additionalProperties": false, + "properties": { + "cve": { + "description": "CVE identifier, e.g. 'CVE-2017-5753'. May be 'CVE-0000-0001' for non-CVE checks such as SLS.", + "type": "string", + "pattern": "^CVE-[0-9]{4}-[0-9]+$" + }, + "name": { + "description": "Short key name used across batch formats, e.g. 'SPECTRE VARIANT 1'.", + "type": "string" + }, + "aliases": { + "description": "Full name including all known aliases, e.g. 'Spectre Variant 1, bounds check bypass'. Null if not in the registry.", + "type": ["string", "null"] + }, + "cpu_affected": { + "description": "Whether this CPU's hardware design is affected by this CVE. False when hardware is architecturally immune.", + "type": "boolean" + }, + "status": { + "description": "Check outcome: 'OK'=not vulnerable or unaffected, 'VULN'=vulnerable, 'UNK'=could not determine.", + "type": "string", + "enum": ["OK", "VULN", "UNK"] + }, + "vulnerable": { + "description": "Boolean encoding of status: false=OK, true=VULN, null=UNK.", + "type": ["boolean", "null"] + }, + "info": { + "description": "Human-readable description of the specific mitigation state or reason for the verdict.", + "type": "string" + }, + "sysfs_status": { + "description": "Status as reported by the kernel via /sys/devices/system/cpu/vulnerabilities/. Null if sysfs was not consulted for this CVE (older kernels, or CVE not tracked by the kernel).", + "type": ["string", "null"], + "enum": ["OK", "VULN", "UNK", null] + }, + "sysfs_message": { + "description": "Raw text from the sysfs vulnerability file, e.g. 'Mitigation: PTI'. Null if sysfs was not consulted.", + "type": ["string", "null"] + } + } + } + } + + } +} diff --git a/dist/doc/batch_nrpe.md b/dist/doc/batch_nrpe.md new file mode 100644 index 0000000..5a669a2 --- /dev/null +++ b/dist/doc/batch_nrpe.md @@ -0,0 +1,149 @@ +# NRPE Output Format + +`--batch nrpe` produces output that conforms to the +[Nagios Plugin Development Guidelines](https://nagios-plugins.org/doc/guidelines.html), +making it directly consumable by Nagios, Icinga, Zabbix (via NRPE), and +compatible monitoring stacks. + +```sh +sudo ./spectre-meltdown-checker.sh --batch nrpe +``` + +## Output structure + +The plugin emits one mandatory status line followed by optional long output: + +``` +STATUS: summary | checked=N vulnerable=N unknown=N +NOTE: ... ← context notes (when applicable) +[CRITICAL] CVE-XXXX-YYYY (NAME): description +[UNKNOWN] CVE-XXXX-YYYY (NAME): description +``` + +### Line 1 (status line) + +Always present. Parsed by every Nagios-compatible monitoring system. + +``` +STATUS: summary | perfdata +``` + +| Field | Values | Meaning | +|---|---|---| +| `STATUS` | `OK` / `CRITICAL` / `UNKNOWN` | Overall check outcome (see below) | +| `summary` | human-readable string | Count and CVE IDs of affected checks | +| `perfdata` | `checked=N vulnerable=N unknown=N` | Machine-readable counters for graphing | + +#### Status values + +| Status | Exit code | Condition | +|---|---|---| +| `OK` | `0` | All CVE checks passed | +| `CRITICAL` | `2` | At least one CVE is vulnerable | +| `UNKNOWN` | `3` | No VULN found, but at least one check is inconclusive **or** the script was not run as root and found apparent vulnerabilities (see below) | + +#### Summary format + +| Condition | Summary | +|---|---| +| All OK | `All N CVE checks passed` | +| VULN only | `N/T CVE(s) vulnerable: CVE-A CVE-B ...` | +| VULN + UNK | `N/T CVE(s) vulnerable: CVE-A CVE-B ..., M inconclusive` | +| UNK only | `N/T CVE checks inconclusive` | +| Non-root + VULN | `N/T CVE(s) appear vulnerable (unconfirmed, not root): CVE-A ...` | + +### Lines 2+ (long output) + +Shown in the detail/extended info view of most monitoring frontends. +Never parsed by the monitoring core; safe to add or reorder. + +#### Context notes + +Printed before per-CVE details when applicable: + +| Note | Condition | +|---|---| +| `NOTE: paranoid mode active, stricter mitigation requirements applied` | `--paranoid` was used | +| `NOTE: hypervisor host detected (reason); L1TF/MDS severity is elevated` | System is a VM host (KVM, Xen, VMware…) | +| `NOTE: not a hypervisor host` | System is confirmed not a VM host | +| `NOTE: not running as root; MSR reads skipped, results may be incomplete` | Script ran without root privileges | + +#### Per-CVE detail lines + +One line per non-OK CVE. VULN entries (`[CRITICAL]`) appear before UNK +entries (`[UNKNOWN]`); within each group the order follows the CVE registry. + +``` +[CRITICAL] CVE-XXXX-YYYY (SHORT NAME): mitigation status description +[UNKNOWN] CVE-XXXX-YYYY (SHORT NAME): reason check was inconclusive +``` + +## Exit codes + +| Code | Nagios meaning | Condition | +|---|---|---| +| `0` | OK | All checked CVEs are mitigated or hardware-unaffected | +| `2` | CRITICAL | At least one CVE is vulnerable (script ran as root) | +| `3` | UNKNOWN | At least one check inconclusive, or apparent VULN found without root | +| `255` | - | Script error (bad arguments, unsupported platform) | + +Exit code `1` (WARNING) is not used; there is no "degraded but acceptable" +state for CPU vulnerability mitigations. + +## Non-root behaviour + +Running without root privileges skips MSR reads and limits access to some +kernel interfaces. When the script finds apparent vulnerabilities without root: + +- The status word becomes `UNKNOWN` instead of `CRITICAL` +- The exit code is `3` instead of `2` +- The summary says `appear vulnerable (unconfirmed, not root)` +- A `NOTE: not running as root` line is added to the long output + +**Recommendation:** always run with `sudo` for authoritative results. A +`CRITICAL` from a root-run scan is a confirmed vulnerability; an `UNKNOWN` +from a non-root scan is a signal to investigate further. + +## Hypervisor hosts + +When `NOTE: hypervisor host detected` is present, L1TF (CVE-2018-3646) and +MDS (CVE-2018-12126/12130/12127) carry significantly higher risk because +they can be exploited across VM boundaries by a malicious guest. Prioritise +remediation on these hosts. + +## Examples + +**All mitigated (root):** +``` +OK: All 31 CVE checks passed | checked=31 vulnerable=0 unknown=0 +NOTE: not a hypervisor host +``` +Exit: `0` + +**Two CVEs vulnerable (root):** +``` +CRITICAL: 2/31 CVE(s) vulnerable: CVE-2018-3615 CVE-2019-11135 | checked=31 vulnerable=2 unknown=0 +NOTE: not a hypervisor host +[CRITICAL] CVE-2018-3615 (L1TF SGX): your CPU supports SGX and the microcode is not up to date +[CRITICAL] CVE-2019-11135 (TAA): Your kernel doesn't support TAA mitigation, update it +``` +Exit: `2` + +**Apparent vulnerabilities, non-root scan:** +``` +UNKNOWN: 2/31 CVE(s) appear vulnerable (unconfirmed, not root): CVE-2018-3615 CVE-2019-11135 | checked=31 vulnerable=2 unknown=0 +NOTE: not a hypervisor host +NOTE: not running as root; MSR reads skipped, results may be incomplete +[CRITICAL] CVE-2018-3615 (L1TF SGX): your CPU supports SGX and the microcode is not up to date +[CRITICAL] CVE-2019-11135 (TAA): Your kernel doesn't support TAA mitigation, update it +``` +Exit: `3` + +**Inconclusive checks, paranoid mode, VMM host:** +``` +UNKNOWN: 3/31 CVE checks inconclusive | checked=31 vulnerable=0 unknown=3 +NOTE: paranoid mode active, stricter mitigation requirements applied +NOTE: hypervisor host detected (kvm); L1TF/MDS severity is elevated +[UNKNOWN] CVE-2018-3646 (L1TF VMM): SMT is enabled on a hypervisor host, not mitigated under paranoid mode +``` +Exit: `3` diff --git a/dist/doc/batch_prometheus.md b/dist/doc/batch_prometheus.md new file mode 100644 index 0000000..353b4fa --- /dev/null +++ b/dist/doc/batch_prometheus.md @@ -0,0 +1,377 @@ +# Prometheus Batch Mode + +`--batch prometheus` emits Prometheus text-format metrics that can be fed into any +Prometheus-compatible monitoring stack. It is designed for **fleet-scale security +monitoring**: run the script periodically on every host, push the output to a +Prometheus Pushgateway (or drop it into a node_exporter textfile directory), then +alert and dashboard from Prometheus/Grafana like any other infrastructure metric. + +--- + +## Quick start + +### Pushgateway (recommended for cron/batch fleet scans) + +```sh +#!/bin/sh +PUSHGATEWAY="http://pushgateway.internal:9091" +INSTANCE=$(hostname -f) + +spectre-meltdown-checker.sh --batch prometheus \ + | curl --silent --show-error --data-binary @- \ + "${PUSHGATEWAY}/metrics/job/smc/instance/${INSTANCE}" +``` + +Run this as root via cron or a systemd timer on every host. The Pushgateway +retains the last pushed value, so Prometheus scrapes it on its own schedule. +A stale-data alert (`smc_last_scan_timestamp_seconds`) catches hosts that stopped +reporting. + +### node_exporter textfile collector + +```sh +#!/bin/sh +TEXTFILE_DIR="/var/lib/node_exporter/textfile_collector" +TMP="${TEXTFILE_DIR}/smc.prom.$$" + +spectre-meltdown-checker.sh --batch prometheus > "$TMP" +mv "$TMP" "${TEXTFILE_DIR}/smc.prom" +``` + +The atomic `mv` prevents node_exporter from reading a partially written file. +node_exporter must be started with `--collector.textfile.directory` pointing at +`TEXTFILE_DIR`. + +--- + +## Metric reference + +All metric names are prefixed `smc_` (spectre-meltdown-checker). All metrics +are **gauges**: they represent the state at the time of the scan, not a running +counter. + +--- + +### `smc_build_info` + +Script metadata. Always value `1`; all data is in labels. + +| Label | Values | Meaning | +|---|---|---| +| `version` | string | Script version (e.g. `25.30.0250400123`) | +| `mode` | `live` / `offline` | `live` = running on the active kernel; `offline` = inspecting a kernel image | +| `run_as_root` | `true` / `false` | Whether the script ran as root. Non-root scans skip MSR reads and may miss mitigations | +| `paranoid` | `true` / `false` | `--paranoid` mode: stricter criteria (e.g. requires SMT disabled) | +| `sysfs_only` | `true` / `false` | `--sysfs-only` mode: only the kernel's own sysfs report was used, not independent detection | +| `reduced_accuracy` | `true` / `false` | Kernel information was incomplete (no kernel image, config, or map); some checks may be less precise | +| `mocked` | `true` / `false` | Debug/test mode: CPU values were overridden. Results do **not** reflect the real system | + +**Example:** +``` +smc_build_info{version="25.30.0250400123",mode="live",run_as_root="true",paranoid="false",sysfs_only="false",reduced_accuracy="false",mocked="false"} 1 +``` + +**Important labels for fleet operators:** + +- `run_as_root="false"` means the scan was incomplete. Treat those results as + lower confidence and alert separately. +- `sysfs_only="true"` means the script trusted the kernel's self-report without + independent verification. The kernel may be wrong about its own mitigation + status (known to happen on older kernels). +- `paranoid="true"` raises the bar: a host with `paranoid="true"` and + `vulnerable_count=0` is held to a higher standard than one with `paranoid="false"`. + Do not compare counts across hosts with different `paranoid` values. +- `mocked="true"` must never appear on a production host; if it does, the results + are fabricated and every downstream alert is unreliable. + +--- + +### `smc_system_info` + +Operating system and kernel metadata. Always value `1`. + +Absent in offline mode when neither `uname -r` nor `uname -m` is available. + +| Label | Values | Meaning | +|---|---|---| +| `kernel_release` | string | Output of `uname -r` (live mode only) | +| `kernel_arch` | string | Output of `uname -m` (live mode only) | +| `hypervisor_host` | `true` / `false` | Whether this machine is detected as a hypervisor host (running KVM, Xen, VMware, etc.) | + +**Example:** +``` +smc_system_info{kernel_release="5.15.0-100-generic",kernel_arch="x86_64",hypervisor_host="false"} 1 +``` + +**`hypervisor_host`** materially changes the risk profile of several CVEs. +L1TF (CVE-2018-3646) and MDS (CVE-2018-12126/12130/12127) are significantly more +severe on hypervisor hosts because they can be exploited across VM boundaries by +a malicious guest. Always prioritise remediation on hosts where +`hypervisor_host="true"`. + +--- + +### `smc_cpu_info` + +CPU hardware and microcode metadata. Always value `1`. Absent when `--no-hw` +is used. + +| Label | Values | Meaning | +|---|---|---| +| `vendor` | string | CPU vendor (e.g. `Intel`, `AuthenticAMD`) | +| `model` | string | CPU friendly name from `/proc/cpuinfo` | +| `family` | integer string | CPU family number | +| `model_id` | integer string | CPU model number | +| `stepping` | integer string | CPU stepping number | +| `cpuid` | hex string | Full CPUID value (e.g. `0x000906ed`); absent on some ARM CPUs | +| `codename` | string | Intel CPU codename (e.g. `Coffee Lake`); absent on AMD and ARM | +| `smt` | `true` / `false` | Whether SMT (HyperThreading) is currently enabled | +| `microcode` | hex string | Installed microcode version (e.g. `0xf4`) | +| `microcode_latest` | hex string | Latest known-good microcode version from the firmware database | +| `microcode_up_to_date` | `true` / `false` | Whether `microcode == microcode_latest` | +| `microcode_blacklisted` | `true` / `false` | Whether the installed microcode is known to cause problems and should be rolled back | + +**Example:** +``` +smc_cpu_info{vendor="Intel",model="Intel(R) Core(TM) i7-9700K CPU @ 3.60GHz",family="6",model_id="158",stepping="13",cpuid="0x000906ed",codename="Coffee Lake",smt="true",microcode="0xf4",microcode_latest="0xf4",microcode_up_to_date="true",microcode_blacklisted="false"} 1 +``` + +**Microcode labels:** + +- `microcode_up_to_date="false"` means a newer microcode is available in the + firmware database. This does not necessarily mean the system is vulnerable + (the current microcode may still provide all required mitigations), but it + warrants investigation. +- `microcode_blacklisted="true"` means the installed microcode is known to + cause system instability or incorrect behaviour and must be rolled back + immediately. Treat this as a P1 incident. +- `microcode_latest` may be absent if the CPU is not in the firmware database + (very new, very old, or exotic CPUs). + +**`smt`** affects the risk level of several CVEs (MDS, L1TF). For those CVEs, +full mitigation requires disabling SMT in addition to kernel and microcode updates. +The script accounts for this in its status assessment; use this label to audit +which hosts still have SMT enabled. + +--- + +### `smc_vulnerability_status` + +One time series per CVE. The **numeric value** encodes the check result: + +| Value | Meaning | +|---|---| +| `0` | Not vulnerable (CPU is unaffected by design, or all required mitigations are in place) | +| `1` | Vulnerable (mitigations are missing or insufficient) | +| `2` | Unknown (the script could not determine the status, e.g. due to missing kernel info or insufficient privileges) | + +| Label | Values | Meaning | +|---|---|---| +| `cve` | CVE ID string | The CVE identifier (e.g. `CVE-2017-5753`) | +| `name` | string | Human-readable CVE name and aliases (e.g. `Spectre Variant 1, bounds check bypass`) | +| `cpu_affected` | `true` / `false` | Whether this CPU's hardware design is concerned by this CVE | + +**Example:** +``` +smc_vulnerability_status{cve="CVE-2017-5753",name="Spectre Variant 1, bounds check bypass",cpu_affected="true"} 0 +smc_vulnerability_status{cve="CVE-2017-5715",name="Spectre Variant 2, branch target injection",cpu_affected="true"} 1 +smc_vulnerability_status{cve="CVE-2022-29900",name="Retbleed, arbitrary speculative code execution with return instructions (AMD)",cpu_affected="false"} 0 +``` + +**`cpu_affected` explained:** + +A value of `0` with `cpu_affected="false"` means the CPU hardware is architecturally +immune to this CVE, no patch was needed or applied. + +A value of `0` with `cpu_affected="true"` means the CPU has the hardware weakness +but all required mitigations (kernel, microcode, or both) are in place. + +This distinction is important when auditing a fleet: if you need to verify that +all at-risk systems are patched, filter on `cpu_affected="true"` to exclude +hardware-immune systems from the analysis. + +--- + +### `smc_vulnerable_count` + +Number of CVEs with status `1` (vulnerable) in this scan. Value is `0` when +no CVEs are vulnerable. + +--- + +### `smc_unknown_count` + +Number of CVEs with status `2` (unknown) in this scan. A non-zero value +typically means the scan lacked sufficient privileges or kernel information. +Treat unknown the same as vulnerable for alerting purposes. + +--- + +### `smc_last_scan_timestamp_seconds` + +Unix timestamp (seconds since epoch) when the scan completed. Use this to +detect hosts that have stopped reporting. + +--- + +## Alerting rules + +```yaml +groups: + - name: spectre_meltdown_checker + rules: + + # Fire when any CVE is confirmed vulnerable + - alert: SMCVulnerable + expr: smc_vulnerable_count > 0 + for: 0m + labels: + severity: critical + annotations: + summary: "{{ $labels.instance }} has {{ $value }} vulnerable CVE(s)" + description: > + Run spectre-meltdown-checker.sh interactively on {{ $labels.instance }} + for remediation guidance. + + # Fire when status is unknown (usually means scan ran without root) + - alert: SMCUnknown + expr: smc_unknown_count > 0 + for: 0m + labels: + severity: warning + annotations: + summary: "{{ $labels.instance }} has {{ $value }} CVE(s) with unknown status" + description: > + Ensure the checker runs as root on {{ $labels.instance }}. + + # Fire when a host stops reporting (scan not run in 8 days) + - alert: SMCScanStale + expr: time() - smc_last_scan_timestamp_seconds > 8 * 86400 + for: 0m + labels: + severity: warning + annotations: + summary: "{{ $labels.instance }} has not reported scan results in 8 days" + + # Fire when installed microcode is known-bad + - alert: SMCMicrocodeBlacklisted + expr: smc_cpu_info{microcode_blacklisted="true"} == 1 + for: 0m + labels: + severity: critical + annotations: + summary: "{{ $labels.instance }} is running blacklisted microcode" + description: > + The installed microcode ({{ $labels.microcode }}) is known to cause + instability. Roll back to the previous version immediately. + + # Fire when scan ran without root (results may be incomplete) + - alert: SMCScanNotRoot + expr: smc_build_info{run_as_root="false"} == 1 + for: 0m + labels: + severity: warning + annotations: + summary: "{{ $labels.instance }} scan ran without root privileges" + + # Fire when mocked data is detected on a production host + - alert: SMCScanMocked + expr: smc_build_info{mocked="true"} == 1 + for: 0m + labels: + severity: critical + annotations: + summary: "{{ $labels.instance }} scan results are mocked and unreliable" +``` + +--- + +## Useful PromQL queries + +```promql +# All vulnerable CVEs across the fleet +smc_vulnerability_status == 1 + +# Vulnerable CVEs on hosts that are also hypervisor hosts (highest priority) +smc_vulnerability_status == 1 + * on(instance) group_left(hypervisor_host) + smc_system_info{hypervisor_host="true"} + +# Vulnerable CVEs on affected CPUs only (excludes hardware-immune systems) +smc_vulnerability_status{cpu_affected="true"} == 1 + +# Fleet-wide: how many hosts are vulnerable to each CVE +count by (cve, name) (smc_vulnerability_status == 1) + +# Hosts with outdated microcode, with CPU model context +smc_cpu_info{microcode_up_to_date="false"} + +# Hosts with SMT still enabled (relevant for MDS/L1TF remediation) +smc_cpu_info{smt="true"} + +# For a specific CVE: hosts affected by hardware but fully mitigated +smc_vulnerability_status{cve="CVE-2018-3646", cpu_affected="true"} == 0 + +# Proportion of fleet that is fully clean (no vulnerable, no unknown) +( + count(smc_vulnerable_count == 0 and smc_unknown_count == 0) + / + count(smc_vulnerable_count >= 0) +) + +# Hosts where scan ran without root, results less reliable +smc_build_info{run_as_root="false"} + +# Hosts with sysfs_only mode, independent detection was skipped +smc_build_info{sysfs_only="true"} + +# Vulnerable CVEs joined with kernel release for patch tracking +smc_vulnerability_status == 1 + * on(instance) group_left(kernel_release) + smc_system_info + +# Vulnerable CVEs joined with CPU model and microcode version +smc_vulnerability_status == 1 + * on(instance) group_left(vendor, model, microcode, microcode_up_to_date) + smc_cpu_info +``` + +--- + +## Caveats and edge cases + +**Offline mode (`--kernel`)** +`smc_system_info` will have no `kernel_release` or `kernel_arch` labels (those +come from `uname`, which reports the running kernel, not the inspected one). +`mode="offline"` in `smc_build_info` signals this. Offline mode is primarily +useful for pre-deployment auditing, not fleet runtime monitoring. + +**`--no-hw`** +`smc_cpu_info` is not emitted. CPU and microcode labels are absent from all +queries. CVE checks that rely on hardware capability detection (`cap_*` flags, +MSR reads) will report `unknown` status. + +**`--sysfs-only`** +The script trusts the kernel's sysfs report (`/sys/devices/system/cpu/vulnerabilities/`) +without running its own independent detection. Some older kernels are known to +misreport their mitigation status. `sysfs_only="true"` in `smc_build_info` +flags this condition. Do not use `--sysfs-only` for production fleet monitoring. + +**`--paranoid`** +Enables defense-in-depth checks beyond the security community consensus (e.g. +requires SMT to be disabled, IBPB always-on). A host is only `vulnerable_count=0` +under `paranoid` if it meets this higher bar. Do not compare `vulnerable_count` +across hosts with different `paranoid` values. + +**`reduced_accuracy`** +Set when the kernel image, config file, or System.map could not be read. Some +checks fall back to weaker heuristics and may report `unknown` for CVEs that are +actually mitigated. This typically happens when the script runs without root or +on a kernel with an inaccessible image. + +**Label stability** +Prometheus identifies time series by their full label set. If a script upgrade +adds or renames a label (e.g. a new `smc_cpu_info` label is added for a new CVE), +Prometheus will create a new time series and the old one will become stale. Plan +for this in long-retention dashboards by using `group_left` joins rather than +hardcoding label matchers. diff --git a/src/main.sh b/src/main.sh index 52e0b60..9fb42e0 100644 --- a/src/main.sh +++ b/src/main.sh @@ -166,7 +166,9 @@ fi if [ "$opt_batch" = 1 ] && [ "$opt_batch_format" = "prometheus" ]; then prom_run_as_root='false' [ "$(id -u)" -eq 0 ] && prom_run_as_root='true' - if [ "$opt_no_hw" = 1 ]; then + if [ "$opt_hw_only" = 1 ]; then + prom_mode='hw-only' + elif [ "$opt_no_hw" = 1 ]; then prom_mode='no-hw' elif [ "$opt_runtime" = 0 ]; then prom_mode='no-runtime' diff --git a/src/vulns/CVE-2017-5715.sh b/src/vulns/CVE-2017-5715.sh index 8fecf0a..24b0b15 100644 --- a/src/vulns/CVE-2017-5715.sh +++ b/src/vulns/CVE-2017-5715.sh @@ -1157,7 +1157,7 @@ check_CVE_2017_5715_linux() { elif [ "$g_ibpb_enabled" = 2 ] && [ "$smt_enabled" != 0 ]; then pvulnstatus "$cve" OK "Full IBPB is mitigating the vulnerability" - # Offline mode fallback + # No-runtime mode fallback elif [ "$opt_runtime" != 1 ]; then if [ "$retpoline" = 1 ] && [ -n "$g_ibpb_supported" ]; then pvulnstatus "$cve" OK "no-runtime mode: kernel supports retpoline + IBPB to mitigate the vulnerability" diff --git a/src/vulns/CVE-2017-5753.sh b/src/vulns/CVE-2017-5753.sh index f4ed40a..6750f7e 100644 --- a/src/vulns/CVE-2017-5753.sh +++ b/src/vulns/CVE-2017-5753.sh @@ -62,7 +62,7 @@ check_CVE_2017_5753_linux() { # 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 offline. + # 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='' @@ -98,7 +98,7 @@ check_CVE_2017_5753_linux() { # 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 offline analysis of these old kernels, match the specific instruction patterns. + # 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): " # vanilla: look for the Linus' mask aka array_index_mask_nospec() diff --git a/src/vulns/CVE-2018-3640.sh b/src/vulns/CVE-2018-3640.sh index 3d3ec7d..32d6bab 100644 --- a/src/vulns/CVE-2018-3640.sh +++ b/src/vulns/CVE-2018-3640.sh @@ -12,7 +12,7 @@ check_CVE_2018_3640() { msg='' # Detect whether the target kernel is ARM64, for both live and no-runtime modes. - # In offline cross-inspection (x86 host, ARM kernel), cpu_vendor reflects the host, + # In no-runtime cross-inspection (x86 host, ARM kernel), cpu_vendor reflects the host, # so also check for arm64_sys_ symbols (same pattern used in CVE-2018-3639). is_arm64_kernel=0 if [ "$cpu_vendor" = ARM ] || [ "$cpu_vendor" = CAVIUM ] || [ "$cpu_vendor" = PHYTIUM ]; then