diff --git a/dist/doc/batch_json.md b/dist/doc/batch_json.md index f6224a4..a76cceb 100644 --- a/dist/doc/batch_json.md +++ b/dist/doc/batch_json.md @@ -102,7 +102,9 @@ boundaries by a malicious guest. Prioritise remediation where ### `cpu` -CPU hardware identification. `null` when `--no-hw` is active. +CPU hardware identification. `null` when `--no-hw` is active, or when +`--arch-prefix` is set (host CPU info is then suppressed to avoid mixing +with a different-arch target kernel). 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 @@ -140,7 +142,7 @@ fields from the other architecture. #### `cpu.x86.capabilities` -Each capability is a **tri-state**: `true` (present), `false` (absent), or +Every 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). @@ -238,7 +240,7 @@ with an unknown CVE ID). | `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_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, or if the CVE's check read sysfs in silent/quiet mode (raw message is still captured in `sysfs_message`) | | `sysfs_message` | string \| null | | Raw text from the sysfs file (e.g. `"Mitigation: PTI"`); null if sysfs was not consulted | #### Status values diff --git a/dist/doc/batch_json.schema.json b/dist/doc/batch_json.schema.json index 75e4f71..eee0c66 100644 --- a/dist/doc/batch_json.schema.json +++ b/dist/doc/batch_json.schema.json @@ -127,7 +127,7 @@ }, "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.", + "description": "CPU hardware identification. Null when --no-hw is active or when --arch-prefix is set (host CPU info is then suppressed to avoid mixing with a different-arch target kernel). Contains an 'arch' discriminator ('x86' or 'arm') and a matching arch-specific sub-object with identification fields and capabilities.", "oneOf": [ { "type": "null" }, { @@ -180,16 +180,16 @@ "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).", + "description": "CPU feature flags detected via CPUID and MSR reads. Every value is tri-state: true=present, false=absent, null=not applicable or unreadable.", "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" }, + "ibrs": { "type": ["boolean", "null"], "description": "IBRS supported (via SPEC_CTRL, IBRS_SUPPORT, or cpuinfo fallback)" }, + "ibpb": { "type": ["boolean", "null"], "description": "IBPB supported (via SPEC_CTRL, IBPB_SUPPORT, or cpuinfo fallback)" }, "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" }, + "stibp": { "type": ["boolean", "null"], "description": "STIBP supported (Intel/AMD/HYGON or cpuinfo fallback)" }, + "ssbd": { "type": ["boolean", "null"], "description": "SSBD supported (SPEC_CTRL, VIRT_SPEC_CTRL, non-architectural MSR, or cpuinfo fallback)" }, "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" }, @@ -231,7 +231,7 @@ "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)" }, + "sbpb": { "type": ["boolean", "null"], "description": "Selective Branch Predictor Barrier (AMD Inception mitigation): true if PRED_CMD MSR SBPB bit write succeeded; false if write failed; null if not verifiable (non-root, CPUID error, or CPU does not report SBPB support)" }, "avx2": { "type": ["boolean", "null"], "description": "AVX2 supported (relevant to Downfall / GDS)" }, "avx512": { "type": ["boolean", "null"], "description": "AVX-512 supported (relevant to Downfall / GDS)" } } diff --git a/dist/doc/batch_nrpe.md b/dist/doc/batch_nrpe.md index 5a669a2..bbc3da7 100644 --- a/dist/doc/batch_nrpe.md +++ b/dist/doc/batch_nrpe.md @@ -51,6 +51,7 @@ STATUS: summary | perfdata | 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 ...` | +| Non-root + VULN + UNK | `N/T CVE(s) appear vulnerable (unconfirmed, not root): CVE-A ..., M inconclusive` | ### Lines 2+ (long output) @@ -59,15 +60,19 @@ Never parsed by the monitoring core; safe to add or reorder. #### Context notes -Printed before per-CVE details when applicable: +Printed before per-CVE details when applicable. Notes are emitted in this +order when more than one applies: | 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: hypervisor host detected (reason); L1TF/MDS severity is elevated` | System is detected as 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 | +When VMM detection did not run (e.g. `--no-hw`), neither the +`hypervisor host detected` nor the `not a hypervisor host` note is printed. + #### Per-CVE detail lines One line per non-OK CVE. VULN entries (`[CRITICAL]`) appear before UNK diff --git a/dist/doc/batch_prometheus.md b/dist/doc/batch_prometheus.md index cd13c5a..75b7d27 100644 --- a/dist/doc/batch_prometheus.md +++ b/dist/doc/batch_prometheus.md @@ -90,13 +90,16 @@ smc_build_info{version="25.30.0250400123",mode="live",run_as_root="true",paranoi Operating system and kernel metadata. Always value `1`. -Absent in offline mode when neither `uname -r` nor `uname -m` is available. +Absent entirely when none of `kernel_release`, `kernel_arch`, or +`hypervisor_host` can be determined (e.g. non-live mode with no VMM detection). +Each label is emitted only when its value is known; missing labels are +omitted rather than set to an empty string. | 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.) | +| `kernel_release` | string | Output of `uname -r`; emitted only in live mode | +| `kernel_arch` | string | Output of `uname -m`; emitted only in live mode | +| `hypervisor_host` | `true` / `false` | Whether this machine is detected as a hypervisor host (running KVM, Xen, VMware, etc.); absent when VMM detection did not run (e.g. `--no-hw`) | **Example:** ``` @@ -114,26 +117,47 @@ a malicious guest. Always prioritise remediation on hosts where ### `smc_cpu_info` CPU hardware and microcode metadata. Always value `1`. Absent when `--no-hw` -is used. +is used or when `--arch-prefix` is set (host CPU info is suppressed to avoid +mixing with a different-arch target kernel). + +Common labels (always emitted when the data is available): | Label | Values | Meaning | |---|---|---| -| `vendor` | string | CPU vendor (e.g. `Intel`, `AuthenticAMD`) | +| `vendor` | string | CPU vendor (e.g. `GenuineIntel`, `AuthenticAMD`, `HygonGenuine`, `ARM`) | | `model` | string | CPU friendly name from `/proc/cpuinfo` | +| `arch` | `x86` / `arm` | Architecture family; determines which arch-specific labels follow | +| `smt` | `true` / `false` | Whether SMT (HyperThreading) is currently enabled; absent if undeterminable | +| `microcode` | hex string | Installed microcode version (e.g. `0xf4`); absent if unreadable | +| `microcode_latest` | hex string | Latest known-good microcode version from the firmware database; absent if the CPU is not in the database | +| `microcode_up_to_date` | `true` / `false` | Whether `microcode == microcode_latest`; absent if either is unavailable | +| `microcode_blacklisted` | `true` / `false` | Whether the installed microcode is known to cause problems and should be rolled back; emitted whenever `microcode` is emitted | + +x86-only labels (emitted when `arch="x86"`): + +| Label | Values | Meaning | +|---|---|---| | `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 | +| `cpuid` | hex string | Full CPUID value (e.g. `0x000906ed`) | +| `codename` | string | Intel CPU codename (e.g. `Coffee Lake`); absent on AMD/Hygon | -**Example:** +ARM-only labels (emitted when `arch="arm"`): + +| Label | Values | Meaning | +|---|---|---| +| `part_list` | string | Space-separated list of ARM part numbers across cores (e.g. `0xd0b 0xd05` on big.LITTLE) | +| `arch_list` | string | Space-separated list of ARM architecture levels across cores (e.g. `8 8`) | + +**x86 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 +smc_cpu_info{vendor="GenuineIntel",model="Intel(R) Core(TM) i7-9700K CPU @ 3.60GHz",arch="x86",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 +``` + +**ARM example:** +``` +smc_cpu_info{vendor="ARM",model="ARM v8 model 0xd0b",arch="arm",part_list="0xd0b 0xd05",arch_list="8 8",smt="false"} 1 ``` **Microcode labels:** @@ -352,9 +376,15 @@ queries. CVE checks that rely on hardware capability detection (`cap_*` flags, MSR reads) will report `unknown` status. `mode="no-hw"` in `smc_build_info` signals this. +**Cross-arch inspection (`--arch-prefix`)** +When a cross-arch toolchain prefix is passed, the script suppresses the host +CPU metadata so it does not get mixed with data from a different-arch target +kernel: `smc_cpu_info` is not emitted, the same as under `--no-hw`. + **Hardware-only mode (`--hw-only`)** Only hardware detection is performed; CVE checks are skipped. `smc_cpu_info` -is emitted but no `smc_vuln` metrics appear. `mode="hw-only"` in +is emitted but no `smc_vulnerability_status` metrics appear (and +`smc_vulnerable_count` / `smc_unknown_count` are `0`). `mode="hw-only"` in `smc_build_info` signals this. **`--sysfs-only`** diff --git a/src/libs/250_output_emitters.sh b/src/libs/250_output_emitters.sh index b75db13..1c8db79 100644 --- a/src/libs/250_output_emitters.sh +++ b/src/libs/250_output_emitters.sh @@ -15,15 +15,17 @@ _prom_escape() { printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' | tr '\n' ' ' } -# Convert a shell capability value to a JSON token -# Args: $1=value (1=true, 0=false, -1/empty=null, other string=quoted string) -# Prints: JSON token +# Convert a shell capability value to a JSON boolean token +# Args: $1=value (1=true, 0=false, -1/empty=null, any other non-empty string=true) +# Prints: JSON token (true/false/null) +# Note: capability variables can be set to arbitrary strings internally to carry +# detection-path context (e.g. cap_ssbd='Intel SSBD'); for the JSON output those +# are normalized to true so consumers see a clean boolean | null type. _json_cap() { case "${1:-}" in - 1) printf 'true' ;; 0) printf 'false' ;; -1 | '') printf 'null' ;; - *) printf '"%s"' "$(_json_escape "$1")" ;; + *) printf 'true' ;; esac } @@ -126,7 +128,7 @@ _build_json_system() { # Sets: g_json_cpu # shellcheck disable=SC2034 _build_json_cpu() { - local cpuid_hex codename caps arch_sub arch_type + local cpuid_hex codename caps arch_sub arch_type sbpb_norm if [ -n "${cpu_cpuid:-}" ]; then cpuid_hex=$(printf '0x%08x' "$cpu_cpuid") else @@ -137,6 +139,15 @@ _build_json_cpu() { codename=$(get_intel_codename 2>/dev/null || true) fi + # cap_sbpb uses non-standard encoding (1=YES, 2=NO, 3=UNKNOWN) because the + # CVE-2023-20569 check distinguishes the unknown case. Normalize for JSON. + case "${cap_sbpb:-}" in + 1) sbpb_norm=1 ;; + 2) sbpb_norm=0 ;; + 3) sbpb_norm=-1 ;; + *) sbpb_norm='' ;; + esac + # Determine architecture type and build the arch-specific sub-object case "${cpu_vendor:-}" in GenuineIntel | AuthenticAMD | HygonGenuine) @@ -190,7 +201,7 @@ _build_json_cpu() { "$(_json_cap "${cap_tsa_l1_no:-}")" \ "$(_json_cap "${cap_verw_clear:-}")" \ "$(_json_cap "${cap_autoibrs:-}")" \ - "$(_json_cap "${cap_sbpb:-}")" \ + "$(_json_cap "$sbpb_norm")" \ "$(_json_cap "${cap_avx2:-}")" \ "$(_json_cap "${cap_avx512:-}")") arch_sub=$(printf '{"family":%s,"model":%s,"stepping":%s,"cpuid":%s,"platform_id":%s,"hybrid":%s,"codename":%s,"capabilities":%s}' \