mirror of
https://github.com/speed47/spectre-meltdown-checker.git
synced 2026-05-03 05:53:20 +02:00
Compare commits
49 Commits
954eb13468
...
de853fc801
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de853fc801 | ||
|
|
98ec067aef | ||
|
|
ff42393fa6 | ||
|
|
f0fb59310e | ||
|
|
be0f2d20d2 | ||
|
|
3639de9e8a | ||
|
|
df3c2aeaa3 | ||
|
|
945f70bb63 | ||
|
|
db84fc10de | ||
|
|
60ea669e41 | ||
|
|
f1c0d5548c | ||
|
|
9e617a4363 | ||
|
|
b9c203120b | ||
|
|
3f7e0a11f7 | ||
|
|
5c469787ea | ||
|
|
a952fe32c4 | ||
|
|
61fa02d577 | ||
|
|
39dea1245e | ||
|
|
3afbda8430 | ||
|
|
6d69ce9a77 | ||
|
|
3ebfba2ac2 | ||
|
|
a3f6553e65 | ||
|
|
42ed8efa65 | ||
|
|
2c766b7cc6 | ||
|
|
49472f1b64 | ||
|
|
333aa74fea | ||
|
|
8d9504d174 | ||
|
|
6043f586ef | ||
|
|
e1ace7c281 | ||
|
|
24ab98d757 | ||
|
|
155b3808b9 | ||
|
|
b6a41918b0 | ||
|
|
3c56ac35dd | ||
|
|
b0bb1f4676 | ||
|
|
0fa7e44327 | ||
|
|
f100b4e1dc | ||
|
|
6332fc3405 | ||
|
|
3c61c7489b | ||
|
|
3d01978cd4 | ||
|
|
53c45e3363 | ||
|
|
acf8b585a5 | ||
|
|
076a1d5723 | ||
|
|
ee618ead07 | ||
|
|
1ff1dfbe26 | ||
|
|
78e4d25319 | ||
|
|
24ed9ccaf6 | ||
|
|
a49234ed96 | ||
|
|
2ed15da028 | ||
|
|
0fcdc6e6cc |
79
.github/workflows/build.yml
vendored
79
.github/workflows/build.yml
vendored
@@ -25,21 +25,81 @@ jobs:
|
||||
mv spectre-meltdown-checker.sh dist/
|
||||
- name: check direct execution
|
||||
run: |
|
||||
set -x
|
||||
expected=$(cat .github/workflows/expected_cve_count)
|
||||
cd dist
|
||||
nb=$(sudo ./spectre-meltdown-checker.sh --batch json | jq '.[]|.CVE' | wc -l)
|
||||
|
||||
json=$(sudo ./spectre-meltdown-checker.sh --batch json || true)
|
||||
|
||||
# Validate JSON is well-formed (and show it if not)
|
||||
echo "$json" | jq . >/dev/null || {
|
||||
echo "Invalid JSON produced by spectre-meltdown-checker.sh"
|
||||
echo "$json"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Validate required keys exist
|
||||
for key in meta system cpu cpu_microcode vulnerabilities; do
|
||||
echo "$json" | jq -e ".$key" >/dev/null || {
|
||||
echo "Missing top-level key: $key"
|
||||
echo "$json" | jq .
|
||||
exit 1
|
||||
}
|
||||
done
|
||||
|
||||
# Use -r to get raw scalars (no quotes)
|
||||
fmtver=$(echo "$json" | jq -r '.meta.format_version // empty')
|
||||
if [ "$fmtver" != "1" ]; then
|
||||
echo "Unexpected format_version: $fmtver"
|
||||
echo "$json" | jq .
|
||||
exit 1
|
||||
fi
|
||||
|
||||
run_as_root=$(echo "$json" | jq -r '.meta.run_as_root // empty')
|
||||
if [ "$run_as_root" != "true" ]; then
|
||||
echo "Expected run_as_root=true, got: $run_as_root"
|
||||
echo "$json" | jq .
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mocked=$(echo "$json" | jq -r '.meta.mocked // "false"')
|
||||
if [ "$mocked" = "true" ]; then
|
||||
echo "mocked=true must never appear in production"
|
||||
echo "$json" | jq .
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Count CVEs robustly (as a number)
|
||||
nb=$(echo "$json" | jq -r '[.vulnerabilities[].cve] | length')
|
||||
if [ "$nb" -ne "$expected" ]; then
|
||||
echo "Invalid number of CVEs reported: $nb instead of $expected"
|
||||
echo "$json" | jq '.vulnerabilities[].cve'
|
||||
exit 1
|
||||
else
|
||||
echo "OK $nb CVEs reported"
|
||||
fi
|
||||
|
||||
# Validate json-terse backward compatibility
|
||||
nb_terse=$(sudo ./spectre-meltdown-checker.sh --batch json-terse | jq -r 'map(.CVE) | length')
|
||||
if [ "$nb_terse" -ne "$expected" ]; then
|
||||
echo "json-terse backward compat broken: $nb_terse CVEs instead of $expected"
|
||||
exit 1
|
||||
else
|
||||
echo "OK json-terse backward compat: $nb_terse CVEs"
|
||||
fi
|
||||
- name: check docker compose run execution
|
||||
run: |
|
||||
expected=$(cat .github/workflows/expected_cve_count)
|
||||
cd dist
|
||||
docker compose build
|
||||
nb=$(docker compose run --rm spectre-meltdown-checker --batch json | jq '.[]|.CVE' | wc -l)
|
||||
json=$(docker compose run --rm spectre-meltdown-checker --batch json || true)
|
||||
echo "$json" | jq . > /dev/null
|
||||
fmtver=$(echo "$json" | jq '.meta.format_version')
|
||||
if [ "$fmtver" != "1" ]; then
|
||||
echo "Unexpected format_version: $fmtver"
|
||||
exit 1
|
||||
fi
|
||||
nb=$(echo "$json" | jq '.vulnerabilities[].cve' | wc -l)
|
||||
if [ "$nb" -ne "$expected" ]; then
|
||||
echo "Invalid number of CVEs reported: $nb instead of $expected"
|
||||
exit 1
|
||||
@@ -51,7 +111,14 @@ jobs:
|
||||
expected=$(cat .github/workflows/expected_cve_count)
|
||||
cd dist
|
||||
docker build -t spectre-meltdown-checker .
|
||||
nb=$(docker run --rm --privileged -v /boot:/boot:ro -v /dev/cpu:/dev/cpu:ro -v /lib/modules:/lib/modules:ro spectre-meltdown-checker --batch json | jq '.[]|.CVE' | wc -l)
|
||||
json=$(docker run --rm --privileged -v /boot:/boot:ro -v /dev/cpu:/dev/cpu:ro -v /lib/modules:/lib/modules:ro spectre-meltdown-checker --batch json || true)
|
||||
echo "$json" | jq . > /dev/null
|
||||
fmtver=$(echo "$json" | jq '.meta.format_version')
|
||||
if [ "$fmtver" != "1" ]; then
|
||||
echo "Unexpected format_version: $fmtver"
|
||||
exit 1
|
||||
fi
|
||||
nb=$(echo "$json" | jq '.vulnerabilities[].cve' | wc -l)
|
||||
if [ "$nb" -ne "$expected" ]; then
|
||||
echo "Invalid number of CVEs reported: $nb instead of $expected"
|
||||
exit 1
|
||||
@@ -92,15 +159,19 @@ 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
|
||||
rm -rf doc/
|
||||
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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -84,8 +84,11 @@ sudo ./spectre-meltdown-checker.sh --variant l1tf --variant taa
|
||||
# Run specific tests that we might have just added (CVE name)
|
||||
sudo ./spectre-meltdown-checker.sh --cve CVE-2018-3640 --cve CVE-2022-40982
|
||||
|
||||
# Batch JSON mode (CI validates exactly 19 CVEs in output)
|
||||
sudo ./spectre-meltdown-checker.sh --batch json | jq '.[] | .CVE' | wc -l # must be 19
|
||||
# Batch JSON mode (comprehensive output)
|
||||
sudo ./spectre-meltdown-checker.sh --batch json | python3 -m json.tool
|
||||
|
||||
# Batch JSON terse mode (legacy flat array)
|
||||
sudo ./spectre-meltdown-checker.sh --batch json-terse | python3 -m json.tool
|
||||
|
||||
# Update microcode firmware database
|
||||
sudo ./spectre-meltdown-checker.sh --update-fwdb
|
||||
@@ -105,7 +108,25 @@ The entire tool is a single bash script with no external script dependencies. Ke
|
||||
- **Microcode database** (embedded): Intel/AMD microcode version lookup via `read_mcedb`/`read_inteldb`; updated automatically via `.github/workflows/autoupdate.yml`
|
||||
- **Kernel analysis** (~line 1568): `extract_kernel`, `try_decompress` - extracts and inspects kernel images (handles gzip, bzip2, xz, lz4, zstd compression)
|
||||
- **Vulnerability checks**: 19 `check_CVE_<year>_<number>()` functions, each with `_linux()` and `_bsd()` variants. Uses whitelist logic (assumes affected unless proven otherwise)
|
||||
- **Main flow** (~line 6668): Parse options → detect CPU → loop through requested CVEs → output results (text/json/nrpe/prometheus) → cleanup
|
||||
- **Batch output emitters** (`src/libs/250_output_emitters.sh`): `_emit_json_full`, `_emit_json_terse`, `_emit_text`, `_emit_nrpe`, `_emit_prometheus`, plus JSON section builders (`_build_json_meta`, `_build_json_system`, `_build_json_cpu`, `_build_json_cpu_microcode`)
|
||||
- **Main flow** (~line 6668): Parse options → detect CPU → loop through requested CVEs → output results (text/json/json-terse/nrpe/prometheus) → cleanup
|
||||
|
||||
### JSON Batch Output Formats
|
||||
|
||||
Two JSON formats are available via `--batch`:
|
||||
|
||||
- **`--batch json`** (comprehensive): A top-level object with five sections:
|
||||
- `meta` — script version, format version, timestamp, run mode flags (`run_as_root`, `reduced_accuracy`, `mocked`, `paranoid`, `sysfs_only`, `extra`)
|
||||
- `system` — kernel release/version/arch/cmdline, CPU count, SMT status, hypervisor host detection
|
||||
- `cpu` — `arch` discriminator (`x86` or `arm`), vendor, friendly name, then an arch-specific sub-object (`cpu.x86` or `cpu.arm`) with identification fields (family/model/stepping/CPUID/codename for x86; part\_list/arch\_list for ARM) and a `capabilities` sub-object containing hardware flags as booleans/nulls
|
||||
- `cpu_microcode` — `installed_version`, `latest_version`, `microcode_up_to_date`, `is_blacklisted`, firmware DB source/info
|
||||
- `vulnerabilities` — array of per-CVE objects: `cve`, `name`, `aliases`, `cpu_affected`, `status`, `vulnerable`, `info`, `sysfs_status`, `sysfs_message`
|
||||
|
||||
- **`--batch json-terse`** (legacy): A flat array of objects with four fields: `NAME`, `CVE`, `VULNERABLE` (bool/null), `INFOS`. This is the original format, preserved for backward compatibility.
|
||||
|
||||
The comprehensive format is built in two phases: static sections (`meta`, `system`, `cpu`, `cpu_microcode`) are assembled after `check_cpu()` completes, and per-CVE entries are accumulated during the main CVE loop via `_emit_json_full()`. The sysfs data for each CVE is captured by `sys_interface_check()` into `g_json_cve_sysfs_status`/`g_json_cve_sysfs_msg` globals, which are read by the emitter and reset after each CVE to prevent cross-CVE leakage. CPU affection is determined via the already-cached `is_cpu_affected()`.
|
||||
|
||||
When adding new `cap_*` variables (for a new CVE or updated hardware support), they must be added to `_build_json_cpu()` in `src/libs/250_output_emitters.sh`. Per-CVE data is handled automatically.
|
||||
|
||||
## Key Design Principles
|
||||
|
||||
@@ -138,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).
|
||||
|
||||
@@ -161,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)'
|
||||
@@ -176,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.
|
||||
|
||||
@@ -315,6 +336,8 @@ When populating the CPU model list, use the **most recent version** of the Linux
|
||||
|
||||
**Important**: Do not confuse hardware immunity bits with *mitigation* capability bits. A hardware immunity bit (e.g. `GDS_NO`, `TSA_SQ_NO`) declares that the CPU design is architecturally free of the vulnerability - it belongs here in `is_cpu_affected()`. A mitigation capability bit (e.g. `VERW_CLEAR`, `MD_CLEAR`) indicates that updated microcode provides a mechanism to work around a vulnerability the CPU *does* have - it belongs in the `check_CVE_YYYY_NNNNN_linux()` function (Phase 2), where it is used to determine whether mitigations are in place.
|
||||
|
||||
**JSON output**: If the new CVE introduces new `cap_*` variables in `check_cpu()` (whether immunity bits or mitigation bits), these must also be added to the `_build_json_cpu()` function in `src/libs/250_output_emitters.sh`, inside the `capabilities` sub-object. Use the same name as the shell variable without the `cap_` prefix (e.g. `cap_tsa_sq_no` becomes `"tsa_sq_no"` in JSON), and emit it via `_json_cap`. The per-CVE vulnerability data (affection, status, sysfs) is handled automatically by the existing `_emit_json_full()` function and requires no changes when adding a new CVE.
|
||||
|
||||
### Step 3: Implement the Linux Check
|
||||
|
||||
The `_linux()` function follows a standard algorithm with four phases:
|
||||
@@ -365,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
|
||||
```
|
||||
|
||||
@@ -467,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/<name>`. 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
|
||||
@@ -748,8 +771,12 @@ CVEs that need VMM context should call `check_has_vmm` early in their `_linux()`
|
||||
3. **Update `dist/README.md`**: Add the CVE in **both** tables — the "Supported CVEs" reference table at the top (CVE link, description, alias) **and** the "Am I at risk?" matrix (with the correct leak/mitigation indicators per boundary). Also add a detailed description paragraph in the `<details>` section at the bottom.
|
||||
4. **Build** the monolithic script with `make`.
|
||||
5. **Test live**: Run the built script and confirm your CVE appears in the output and reports a sensible status.
|
||||
6. **Test batch JSON**: Run with `--batch json` and verify the CVE appears in the output.
|
||||
7. **Test offline**: Run with `--kernel`/`--config`/`--map` pointing to a kernel image and verify the offline code path reports correctly.
|
||||
6. **Test batch JSON**: Run with `--batch json` and pipe through `python3 -m json.tool` to verify:
|
||||
- The output is valid JSON.
|
||||
- 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 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 <shortname>` 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.
|
||||
|
||||
@@ -757,9 +784,10 @@ 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.
|
||||
- **All indentation must use 4 spaces** (CI enforces this via `fmt-check`; the vim modeline `et` enables expandtab).
|
||||
- **Stay POSIX-compatible** - no bashisms, no GNU-only flags in portable code paths.
|
||||
|
||||
|
||||
45
dist/README.md
vendored
45
dist/README.md
vendored
@@ -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,29 @@ 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
|
||||
|
||||
The script supports four operating modes, depending on whether you want to inspect the running kernel, a kernel image, the CPU hardware, or a combination.
|
||||
|
||||
| Mode | Flag | CPU hardware | Running kernel | Kernel image | Use case |
|
||||
|------|------|:---:|:---:|:---:|----------|
|
||||
| **Live** *(default)* | *(none)* | Yes | Yes | auto-detect | Day-to-day auditing of the current system |
|
||||
| **No-runtime** | `--no-runtime` | Yes | No | required | Check a different kernel against this CPU (e.g. pre-deployment) |
|
||||
| **No-hardware** | `--no-hw` | No | No | required | Pure static analysis of a kernel image for another system or architecture |
|
||||
| **Hardware-only** | `--hw-only` | Yes | No | No | Quickly check CPU affectedness without inspecting any kernel |
|
||||
|
||||
In **Live** mode (the default), the script inspects both the CPU and the running kernel.
|
||||
You can optionally pass `--kernel`, `--config`, or `--map` to point the script at files it couldn't auto-detect.
|
||||
|
||||
In **No-runtime** mode, the script still reads the local CPU (CPUID, MSRs, microcode) but skips all running-kernel artifacts (`/sys`, `/proc`, `dmesg`).
|
||||
Use this when you have a kernel image from another system but want to evaluate it against the current CPU.
|
||||
|
||||
In **No-hardware** mode, both CPU inspection and running-kernel artifacts are skipped entirely.
|
||||
This is useful for cross-architecture analysis, for example inspecting an ARM kernel image on an x86 workstation.
|
||||
|
||||
In **Hardware-only** mode, the script only reports CPU information and per-CVE hardware affectedness, without inspecting any kernel.
|
||||
|
||||
## Running the script
|
||||
|
||||
@@ -288,15 +310,6 @@ docker run --rm --privileged -v /boot:/boot:ro -v /dev/cpu:/dev/cpu:ro -v /lib/m
|
||||
|
||||
## Example of script output
|
||||
|
||||
- Intel Haswell CPU running under Ubuntu 16.04 LTS
|
||||
|
||||

|
||||
|
||||
- AMD Ryzen running under OpenSUSE Tumbleweed
|
||||
|
||||

|
||||
|
||||
- Batch mode (JSON flavor)
|
||||
|
||||

|
||||
- AMD EPYC-Milan running under Debian Trixie
|
||||
|
||||

|
||||
|
||||
2
dist/FAQ.md → dist/doc/FAQ.md
vendored
2
dist/FAQ.md → dist/doc/FAQ.md
vendored
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
391
dist/doc/batch_json.md
vendored
Normal file
391
dist/doc/batch_json.md
vendored
Normal file
@@ -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.
|
||||
382
dist/doc/batch_json.schema.json
vendored
Normal file
382
dist/doc/batch_json.schema.json
vendored
Normal file
@@ -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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
149
dist/doc/batch_nrpe.md
vendored
Normal file
149
dist/doc/batch_nrpe.md
vendored
Normal file
@@ -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`
|
||||
377
dist/doc/batch_prometheus.md
vendored
Normal file
377
dist/doc/batch_prometheus.md
vendored
Normal file
@@ -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.
|
||||
BIN
img/smc_amd_epyc_milan.jpg
Normal file
BIN
img/smc_amd_epyc_milan.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
@@ -27,8 +27,7 @@ trap 'exit_cleanup' EXIT
|
||||
trap 'pr_warn "interrupted, cleaning up..."; exit_cleanup; exit 1' INT
|
||||
# Clean up temporary files and undo module/mount side effects on exit
|
||||
exit_cleanup() {
|
||||
local saved_ret
|
||||
saved_ret=$?
|
||||
local saved_ret=$?
|
||||
# cleanup the temp decompressed config & kernel image
|
||||
[ -n "${g_dumped_config:-}" ] && [ -f "$g_dumped_config" ] && rm -f "$g_dumped_config"
|
||||
[ -n "${g_kerneltmp:-}" ] && [ -f "$g_kerneltmp" ] && rm -f "$g_kerneltmp"
|
||||
|
||||
@@ -3,64 +3,60 @@
|
||||
show_usage() {
|
||||
# shellcheck disable=SC2086
|
||||
cat <<EOF
|
||||
Usage:
|
||||
Live mode (auto): $(basename $0) [options]
|
||||
Live mode (manual): $(basename $0) [options] <[--kernel <kimage>] [--config <kconfig>] [--map <mapfile>]> --live
|
||||
Offline mode: $(basename $0) [options] <[--kernel <kimage>] [--config <kconfig>] [--map <mapfile>]>
|
||||
|
||||
Modes:
|
||||
Two modes are available.
|
||||
* Live mode: $(basename $0) [options] [--kernel <kimage>] [--config <kconfig>] [--map <mapfile>]
|
||||
Inspect the currently running kernel within the context of the CPU it's running on.
|
||||
You can optionally specify --kernel, --config, or --map to help the script locate files it couldn't auto-detect
|
||||
|
||||
First mode is the "live" mode (default), it does its best to find information about the currently running kernel.
|
||||
To run under this mode, just start the script without any option (you can also use --live explicitly)
|
||||
* No-runtime mode: $(basename $0) [options] --no-runtime <--kernel <kimage>> [--config <kconfig>] [--map <mapfile>]
|
||||
Inspect the CPU hardware, but skips all running-kernel artifacts (/sys, /proc, dmesg).
|
||||
Use this when you have a kernel image different from the kernel you're running but want to check it against this CPU.
|
||||
|
||||
Second mode is the "offline" mode, where you can inspect a non-running kernel.
|
||||
This mode is automatically enabled when you specify the location of the kernel file, config and System.map files:
|
||||
* No-hardware mode: $(basename $0) [options] --no-hw <--kernel <kimage>> [--config <kconfig>] [--map <mapfile>]
|
||||
Ignore both CPU hardware and running-kernel artifacts. Use this for pure static analysis of a kernel image,
|
||||
for example when inspecting a kernel targeted for another system or CPU.
|
||||
|
||||
--kernel kernel_file specify a (possibly compressed) Linux or BSD kernel file
|
||||
--config kernel_config specify a kernel config file (Linux only)
|
||||
--map kernel_map_file specify a kernel System.map file (Linux only)
|
||||
* Hardware-only mode: $(basename $0) [options] --hw-only
|
||||
Only inspect the CPU hardware, and report information and affectedness per vulnerability.
|
||||
|
||||
If you want to use live mode while specifying the location of the kernel, config or map file yourself,
|
||||
you can add --live to the above options, to tell the script to run in live mode instead of the offline mode,
|
||||
which is enabled by default when at least one file is specified on the command line.
|
||||
Vulnerability selection:
|
||||
--variant VARIANT specify which variant you'd like to check, by default all variants are checked.
|
||||
can be used multiple times (e.g. --variant 3a --variant l1tf). For a list use 'help'.
|
||||
--cve CVE specify which CVE you'd like to check, by default all supported CVEs are checked
|
||||
can be used multiple times (e.g. --cve CVE-2017-5753 --cve CVE-2020-0543)
|
||||
|
||||
Options:
|
||||
--no-color don't use color codes
|
||||
--verbose, -v increase verbosity level, possibly several times
|
||||
--explain produce an additional human-readable explanation of actions to take to mitigate a vulnerability
|
||||
Check scope:
|
||||
--no-sysfs don't use the /sys interface even if present [Linux]
|
||||
--sysfs-only only use the /sys interface, don't run our own checks [Linux]
|
||||
|
||||
Strictness:
|
||||
--paranoid require all mitigations to be enabled to the fullest extent, including those that
|
||||
are not strictly necessary but provide defense in depth (e.g. SMT disabled, IBPB
|
||||
always-on); without this flag, the script follows the security community consensus
|
||||
--extra run additional checks for issues that don't have a CVE but are still security-relevant,
|
||||
such as compile-time mitigations not enabled by default (e.g. Straight-Line Speculation)
|
||||
|
||||
--no-sysfs don't use the /sys interface even if present [Linux]
|
||||
--sysfs-only only use the /sys interface, don't run our own checks [Linux]
|
||||
--coreos special mode for CoreOS (use an ephemeral toolbox to inspect kernel) [Linux]
|
||||
|
||||
Hardware and platform:
|
||||
--cpu [#,all] interact with CPUID and MSR of CPU core number #, or all (default: CPU core 0)
|
||||
--vmm [auto,yes,no] override the detection of the presence of a hypervisor, default: auto
|
||||
--allow-msr-write allow probing for write-only MSRs, this might produce kernel logs or be blocked by your system
|
||||
--arch-prefix PREFIX specify a prefix for cross-inspecting a kernel of a different arch, for example "aarch64-linux-gnu-",
|
||||
so that invoked tools will be prefixed with this (i.e. aarch64-linux-gnu-objdump)
|
||||
--batch text produce machine readable output, this is the default if --batch is specified alone
|
||||
--batch short produce only one line with the vulnerabilities separated by spaces
|
||||
--batch json produce JSON output formatted for Puppet, Ansible, Chef...
|
||||
--batch nrpe produce machine readable output formatted for NRPE
|
||||
--batch prometheus produce output for consumption by prometheus-node-exporter
|
||||
--coreos special mode for CoreOS (use an ephemeral toolbox to inspect kernel) [Linux]
|
||||
|
||||
--variant VARIANT specify which variant you'd like to check, by default all variants are checked.
|
||||
can be used multiple times (e.g. --variant 3a --variant l1tf)
|
||||
for a list of supported VARIANT parameters, use --variant help
|
||||
--cve CVE specify which CVE you'd like to check, by default all supported CVEs are checked
|
||||
can be used multiple times (e.g. --cve CVE-2017-5753 --cve CVE-2020-0543)
|
||||
--hw-only only check for CPU information, don't check for any variant
|
||||
--no-hw skip CPU information and checks, if you're inspecting a kernel not to be run on this host
|
||||
--vmm [auto,yes,no] override the detection of the presence of a hypervisor, default: auto
|
||||
--no-intel-db don't use the builtin Intel DB of affected processors
|
||||
--allow-msr-write allow probing for write-only MSRs, this might produce kernel logs or be blocked by your system
|
||||
--cpu [#,all] interact with CPUID and MSR of CPU core number #, or all (default: CPU core 0)
|
||||
Output:
|
||||
--batch FORMAT produce machine readable output; FORMAT is one of:
|
||||
text (default), short, json, json-terse, nrpe, prometheus
|
||||
--no-color don't use color codes
|
||||
--verbose, -v increase verbosity level, possibly several times
|
||||
--explain produce an additional human-readable explanation of actions to take to mitigate a vulnerability
|
||||
|
||||
Firmware database:
|
||||
--update-fwdb update our local copy of the CPU microcodes versions database (using the awesome
|
||||
MCExtractor project and the Intel firmwares GitHub repository)
|
||||
--update-builtin-fwdb same as --update-fwdb but update builtin DB inside the script itself
|
||||
|
||||
Debug:
|
||||
--dump-mock-data used to mimick a CPU on an other system, mainly used to help debugging this script
|
||||
|
||||
Return codes:
|
||||
@@ -112,7 +108,7 @@ g_os=$(uname -s)
|
||||
opt_kernel=''
|
||||
opt_config=''
|
||||
opt_map=''
|
||||
opt_live=-1
|
||||
opt_runtime=1
|
||||
opt_no_color=0
|
||||
opt_batch=0
|
||||
opt_batch_format='text'
|
||||
@@ -132,11 +128,21 @@ opt_explain=0
|
||||
opt_paranoid=0
|
||||
opt_extra=0
|
||||
opt_mock=0
|
||||
opt_intel_db=1
|
||||
|
||||
g_critical=0
|
||||
g_unknown=0
|
||||
g_nrpe_vuln=''
|
||||
g_nrpe_total=0
|
||||
g_nrpe_vuln_count=0
|
||||
g_nrpe_unk_count=0
|
||||
g_nrpe_vuln_ids=''
|
||||
g_nrpe_vuln_details=''
|
||||
g_nrpe_unk_details=''
|
||||
g_smc_vuln_output=''
|
||||
g_smc_ok_count=0
|
||||
g_smc_vuln_count=0
|
||||
g_smc_unk_count=0
|
||||
g_smc_system_info_line=''
|
||||
g_smc_cpu_info_line=''
|
||||
|
||||
# CVE Registry: single source of truth for all CVE metadata.
|
||||
# Fields: cve_id|json_key_name|affected_var_suffix|complete_name_and_aliases
|
||||
|
||||
@@ -47,7 +47,7 @@ while [ -n "${1:-}" ]; do
|
||||
opt_arch_prefix="$2"
|
||||
shift 2
|
||||
elif [ "$1" = "--live" ]; then
|
||||
opt_live=1
|
||||
# deprecated, kept for backward compatibility (live is now the default)
|
||||
shift
|
||||
elif [ "$1" = "--no-color" ]; then
|
||||
opt_no_color=1
|
||||
@@ -74,15 +74,16 @@ while [ -n "${1:-}" ]; do
|
||||
elif [ "$1" = "--hw-only" ]; then
|
||||
opt_hw_only=1
|
||||
shift
|
||||
elif [ "$1" = "--no-runtime" ]; then
|
||||
opt_runtime=0
|
||||
shift
|
||||
elif [ "$1" = "--no-hw" ]; then
|
||||
opt_no_hw=1
|
||||
opt_runtime=0
|
||||
shift
|
||||
elif [ "$1" = "--allow-msr-write" ]; then
|
||||
opt_allow_msr_write=1
|
||||
shift
|
||||
elif [ "$1" = "--no-intel-db" ]; then
|
||||
opt_intel_db=0
|
||||
shift
|
||||
elif [ "$1" = "--cpu" ]; then
|
||||
opt_cpu=$2
|
||||
if [ "$opt_cpu" != all ]; then
|
||||
@@ -116,7 +117,7 @@ while [ -n "${1:-}" ]; do
|
||||
opt_no_color=1
|
||||
shift
|
||||
case "$1" in
|
||||
text | short | nrpe | json | prometheus)
|
||||
text | short | nrpe | json | json-terse | prometheus)
|
||||
opt_batch_format="$1"
|
||||
shift
|
||||
;;
|
||||
@@ -124,7 +125,7 @@ while [ -n "${1:-}" ]; do
|
||||
'') ;; # allow nothing at all
|
||||
*)
|
||||
echo "$0: error: unknown batch format '$1'" >&2
|
||||
echo "$0: error: --batch expects a format from: text, nrpe, json" >&2
|
||||
echo "$0: error: --batch expects a format from: text, short, nrpe, json, json-terse, prometheus" >&2
|
||||
exit 255
|
||||
;;
|
||||
esac
|
||||
@@ -334,11 +335,12 @@ if [ "$opt_no_hw" = 1 ] && [ "$opt_hw_only" = 1 ]; then
|
||||
exit 255
|
||||
fi
|
||||
|
||||
if [ "$opt_live" = -1 ]; then
|
||||
if [ -n "$opt_kernel" ] || [ -n "$opt_config" ] || [ -n "$opt_map" ]; then
|
||||
# no --live specified and we have a least one of the kernel/config/map files on the cmdline: offline mode
|
||||
opt_live=0
|
||||
else
|
||||
opt_live=1
|
||||
fi
|
||||
if [ "$opt_runtime" = 0 ] && [ "$opt_sysfs_only" = 1 ]; then
|
||||
pr_warn "Incompatible options specified (--no-runtime and --sysfs-only), aborting"
|
||||
exit 255
|
||||
fi
|
||||
|
||||
if [ "$opt_runtime" = 0 ] && [ -z "$opt_kernel" ] && [ -z "$opt_config" ] && [ -z "$opt_map" ]; then
|
||||
pr_warn "Option --no-runtime requires at least one of --kernel, --config, or --map"
|
||||
exit 255
|
||||
fi
|
||||
|
||||
@@ -1,4 +1,275 @@
|
||||
# vim: set ts=4 sw=4 sts=4 et:
|
||||
# --- JSON helper functions ---
|
||||
|
||||
# Escape a string for use in a JSON value (handles backslashes, double quotes, newlines, tabs)
|
||||
# Args: $1=string
|
||||
# Prints: escaped string (without surrounding quotes)
|
||||
_json_escape() {
|
||||
printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e 's/ /\\t/g' | tr '\n' ' '
|
||||
}
|
||||
|
||||
# Escape a string for use as a Prometheus label value (handles backslashes, double quotes, newlines)
|
||||
# Args: $1=string
|
||||
# Prints: escaped string (without surrounding quotes)
|
||||
_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
|
||||
_json_cap() {
|
||||
case "${1:-}" in
|
||||
1) printf 'true' ;;
|
||||
0) printf 'false' ;;
|
||||
-1 | '') printf 'null' ;;
|
||||
*) printf '"%s"' "$(_json_escape "$1")" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Emit a JSON string value or null
|
||||
# Args: $1=string (empty=null)
|
||||
# Prints: JSON token ("escaped string" or null)
|
||||
_json_str() {
|
||||
if [ -n "${1:-}" ]; then
|
||||
printf '"%s"' "$(_json_escape "$1")"
|
||||
else
|
||||
printf 'null'
|
||||
fi
|
||||
}
|
||||
|
||||
# Emit a JSON number value or null
|
||||
# Args: $1=number (empty=null)
|
||||
# Prints: JSON token
|
||||
_json_num() {
|
||||
if [ -n "${1:-}" ]; then
|
||||
printf '%s' "$1"
|
||||
else
|
||||
printf 'null'
|
||||
fi
|
||||
}
|
||||
|
||||
# Emit a JSON boolean value or null
|
||||
# Args: $1=value (1/0/empty)
|
||||
# Prints: JSON token
|
||||
_json_bool() {
|
||||
case "${1:-}" in
|
||||
1) printf 'true' ;;
|
||||
0) printf 'false' ;;
|
||||
*) printf 'null' ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# --- JSON section builders (comprehensive format) ---
|
||||
|
||||
# Build the "meta" section of the comprehensive JSON output
|
||||
# Sets: g_json_meta
|
||||
# shellcheck disable=SC2034
|
||||
_build_json_meta() {
|
||||
local timestamp mode
|
||||
timestamp=$(date -u '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || echo "unknown")
|
||||
if [ "$opt_hw_only" = 1 ]; then
|
||||
mode="hw-only"
|
||||
elif [ "$opt_no_hw" = 1 ]; then
|
||||
mode="no-hw"
|
||||
elif [ "$opt_runtime" = 0 ]; then
|
||||
mode="no-runtime"
|
||||
else
|
||||
mode="live"
|
||||
fi
|
||||
local run_as_root
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
run_as_root='true'
|
||||
else
|
||||
run_as_root='false'
|
||||
fi
|
||||
g_json_meta=$(printf '{"script_version":%s,"format_version":1,"timestamp":%s,"os":%s,"mode":"%s","run_as_root":%s,"reduced_accuracy":%s,"paranoid":%s,"sysfs_only":%s,"extra":%s}' \
|
||||
"$(_json_str "$VERSION")" \
|
||||
"$(_json_str "$timestamp")" \
|
||||
"$(_json_str "$g_os")" \
|
||||
"$mode" \
|
||||
"$run_as_root" \
|
||||
"$(_json_bool "${g_bad_accuracy:-0}")" \
|
||||
"$(_json_bool "$opt_paranoid")" \
|
||||
"$(_json_bool "$opt_sysfs_only")" \
|
||||
"$(_json_bool "$opt_extra")")
|
||||
}
|
||||
|
||||
# Build the "system" section of the comprehensive JSON output
|
||||
# Sets: g_json_system
|
||||
# shellcheck disable=SC2034
|
||||
_build_json_system() {
|
||||
local kernel_release kernel_version kernel_arch smt_val
|
||||
if [ "$opt_runtime" = 1 ]; then
|
||||
kernel_release=$(uname -r)
|
||||
kernel_version=$(uname -v)
|
||||
kernel_arch=$(uname -m)
|
||||
else
|
||||
kernel_release=''
|
||||
kernel_version=''
|
||||
kernel_arch=''
|
||||
fi
|
||||
# SMT detection
|
||||
is_cpu_smt_enabled
|
||||
smt_val=$?
|
||||
case $smt_val in
|
||||
0) smt_val='true' ;;
|
||||
1) smt_val='false' ;;
|
||||
*) smt_val='null' ;;
|
||||
esac
|
||||
g_json_system=$(printf '{"kernel_release":%s,"kernel_version":%s,"kernel_arch":%s,"kernel_image":%s,"kernel_config":%s,"kernel_version_string":%s,"kernel_cmdline":%s,"cpu_count":%s,"smt_enabled":%s,"hypervisor_host":%s,"hypervisor_host_reason":%s}' \
|
||||
"$(_json_str "$kernel_release")" \
|
||||
"$(_json_str "$kernel_version")" \
|
||||
"$(_json_str "$kernel_arch")" \
|
||||
"$(_json_str "${opt_kernel:-}")" \
|
||||
"$(_json_str "${opt_config:-}")" \
|
||||
"$(_json_str "${g_kernel_version:-}")" \
|
||||
"$(_json_str "${g_kernel_cmdline:-}")" \
|
||||
"$(_json_num "${g_max_core_id:+$((g_max_core_id + 1))}")" \
|
||||
"$smt_val" \
|
||||
"$(_json_bool "${g_has_vmm:-}")" \
|
||||
"$(_json_str "${g_has_vmm_reason:-}")")
|
||||
}
|
||||
|
||||
# Build the "cpu" section of the comprehensive JSON output
|
||||
# Sets: g_json_cpu
|
||||
# shellcheck disable=SC2034
|
||||
_build_json_cpu() {
|
||||
local cpuid_hex codename caps arch_sub arch_type
|
||||
if [ -n "${cpu_cpuid:-}" ]; then
|
||||
cpuid_hex=$(printf '0x%08x' "$cpu_cpuid")
|
||||
else
|
||||
cpuid_hex=''
|
||||
fi
|
||||
codename=''
|
||||
if is_intel; then
|
||||
codename=$(get_intel_codename 2>/dev/null || true)
|
||||
fi
|
||||
|
||||
# Determine architecture type and build the arch-specific sub-object
|
||||
case "${cpu_vendor:-}" in
|
||||
GenuineIntel | AuthenticAMD | HygonGenuine)
|
||||
arch_type='x86'
|
||||
# Build x86 capabilities sub-object
|
||||
caps=$(printf '{"spec_ctrl":%s,"ibrs":%s,"ibpb":%s,"ibpb_ret":%s,"stibp":%s,"ssbd":%s,"l1d_flush":%s,"md_clear":%s,"arch_capabilities":%s,"rdcl_no":%s,"ibrs_all":%s,"rsba":%s,"l1dflush_no":%s,"ssb_no":%s,"mds_no":%s,"taa_no":%s,"pschange_msc_no":%s,"tsx_ctrl_msr":%s,"tsx_ctrl_rtm_disable":%s,"tsx_ctrl_cpuid_clear":%s,"gds_ctrl":%s,"gds_no":%s,"gds_mitg_dis":%s,"gds_mitg_lock":%s,"rfds_no":%s,"rfds_clear":%s,"its_no":%s,"sbdr_ssdp_no":%s,"fbsdp_no":%s,"psdp_no":%s,"fb_clear":%s,"rtm":%s,"tsx_force_abort":%s,"tsx_force_abort_rtm_disable":%s,"tsx_force_abort_cpuid_clear":%s,"sgx":%s,"srbds":%s,"srbds_on":%s,"amd_ssb_no":%s,"hygon_ssb_no":%s,"ipred":%s,"rrsba":%s,"bhi":%s,"tsa_sq_no":%s,"tsa_l1_no":%s,"verw_clear":%s,"autoibrs":%s,"sbpb":%s,"avx2":%s,"avx512":%s}' \
|
||||
"$(_json_cap "${cap_spec_ctrl:-}")" \
|
||||
"$(_json_cap "${cap_ibrs:-}")" \
|
||||
"$(_json_cap "${cap_ibpb:-}")" \
|
||||
"$(_json_cap "${cap_ibpb_ret:-}")" \
|
||||
"$(_json_cap "${cap_stibp:-}")" \
|
||||
"$(_json_cap "${cap_ssbd:-}")" \
|
||||
"$(_json_cap "${cap_l1df:-}")" \
|
||||
"$(_json_cap "${cap_md_clear:-}")" \
|
||||
"$(_json_cap "${cap_arch_capabilities:-}")" \
|
||||
"$(_json_cap "${cap_rdcl_no:-}")" \
|
||||
"$(_json_cap "${cap_ibrs_all:-}")" \
|
||||
"$(_json_cap "${cap_rsba:-}")" \
|
||||
"$(_json_cap "${cap_l1dflush_no:-}")" \
|
||||
"$(_json_cap "${cap_ssb_no:-}")" \
|
||||
"$(_json_cap "${cap_mds_no:-}")" \
|
||||
"$(_json_cap "${cap_taa_no:-}")" \
|
||||
"$(_json_cap "${cap_pschange_msc_no:-}")" \
|
||||
"$(_json_cap "${cap_tsx_ctrl_msr:-}")" \
|
||||
"$(_json_cap "${cap_tsx_ctrl_rtm_disable:-}")" \
|
||||
"$(_json_cap "${cap_tsx_ctrl_cpuid_clear:-}")" \
|
||||
"$(_json_cap "${cap_gds_ctrl:-}")" \
|
||||
"$(_json_cap "${cap_gds_no:-}")" \
|
||||
"$(_json_cap "${cap_gds_mitg_dis:-}")" \
|
||||
"$(_json_cap "${cap_gds_mitg_lock:-}")" \
|
||||
"$(_json_cap "${cap_rfds_no:-}")" \
|
||||
"$(_json_cap "${cap_rfds_clear:-}")" \
|
||||
"$(_json_cap "${cap_its_no:-}")" \
|
||||
"$(_json_cap "${cap_sbdr_ssdp_no:-}")" \
|
||||
"$(_json_cap "${cap_fbsdp_no:-}")" \
|
||||
"$(_json_cap "${cap_psdp_no:-}")" \
|
||||
"$(_json_cap "${cap_fb_clear:-}")" \
|
||||
"$(_json_cap "${cap_rtm:-}")" \
|
||||
"$(_json_cap "${cap_tsx_force_abort:-}")" \
|
||||
"$(_json_cap "${cap_tsx_force_abort_rtm_disable:-}")" \
|
||||
"$(_json_cap "${cap_tsx_force_abort_cpuid_clear:-}")" \
|
||||
"$(_json_cap "${cap_sgx:-}")" \
|
||||
"$(_json_cap "${cap_srbds:-}")" \
|
||||
"$(_json_cap "${cap_srbds_on:-}")" \
|
||||
"$(_json_cap "${cap_amd_ssb_no:-}")" \
|
||||
"$(_json_cap "${cap_hygon_ssb_no:-}")" \
|
||||
"$(_json_cap "${cap_ipred:-}")" \
|
||||
"$(_json_cap "${cap_rrsba:-}")" \
|
||||
"$(_json_cap "${cap_bhi:-}")" \
|
||||
"$(_json_cap "${cap_tsa_sq_no:-}")" \
|
||||
"$(_json_cap "${cap_tsa_l1_no:-}")" \
|
||||
"$(_json_cap "${cap_verw_clear:-}")" \
|
||||
"$(_json_cap "${cap_autoibrs:-}")" \
|
||||
"$(_json_cap "${cap_sbpb:-}")" \
|
||||
"$(_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}' \
|
||||
"$(_json_num "${cpu_family:-}")" \
|
||||
"$(_json_num "${cpu_model:-}")" \
|
||||
"$(_json_num "${cpu_stepping:-}")" \
|
||||
"$(_json_str "$cpuid_hex")" \
|
||||
"$(_json_num "${cpu_platformid:-}")" \
|
||||
"$(_json_bool "${cpu_hybrid:-}")" \
|
||||
"$(_json_str "$codename")" \
|
||||
"$caps")
|
||||
;;
|
||||
ARM | CAVIUM | PHYTIUM)
|
||||
arch_type='arm'
|
||||
arch_sub=$(printf '{"part_list":%s,"arch_list":%s,"capabilities":{}}' \
|
||||
"$(_json_str "${cpu_part_list:-}")" \
|
||||
"$(_json_str "${cpu_arch_list:-}")")
|
||||
;;
|
||||
*)
|
||||
arch_type=''
|
||||
arch_sub=''
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -n "$arch_type" ]; then
|
||||
g_json_cpu=$(printf '{"arch":"%s","vendor":%s,"friendly_name":%s,"%s":%s}' \
|
||||
"$arch_type" \
|
||||
"$(_json_str "${cpu_vendor:-}")" \
|
||||
"$(_json_str "${cpu_friendly_name:-}")" \
|
||||
"$arch_type" \
|
||||
"$arch_sub")
|
||||
else
|
||||
g_json_cpu=$(printf '{"arch":null,"vendor":%s,"friendly_name":%s}' \
|
||||
"$(_json_str "${cpu_vendor:-}")" \
|
||||
"$(_json_str "${cpu_friendly_name:-}")")
|
||||
fi
|
||||
}
|
||||
|
||||
# Build the "cpu_microcode" section of the comprehensive JSON output
|
||||
# Sets: g_json_cpu_microcode
|
||||
# shellcheck disable=SC2034
|
||||
_build_json_cpu_microcode() {
|
||||
local ucode_uptodate ucode_hex latest_hex blacklisted
|
||||
if [ -n "${cpu_ucode:-}" ]; then
|
||||
ucode_hex=$(printf '0x%x' "$cpu_ucode")
|
||||
else
|
||||
ucode_hex=''
|
||||
fi
|
||||
is_latest_known_ucode
|
||||
case $? in
|
||||
0) ucode_uptodate='true' ;;
|
||||
1) ucode_uptodate='false' ;;
|
||||
*) ucode_uptodate='null' ;;
|
||||
esac
|
||||
if is_ucode_blacklisted; then
|
||||
blacklisted='true'
|
||||
else
|
||||
blacklisted='false'
|
||||
fi
|
||||
latest_hex="${ret_is_latest_known_ucode_version:-}"
|
||||
g_json_cpu_microcode=$(printf '{"installed_version":%s,"latest_version":%s,"microcode_up_to_date":%s,"is_blacklisted":%s,"message":%s,"db_source":%s,"db_info":%s}' \
|
||||
"$(_json_str "$ucode_hex")" \
|
||||
"$(_json_str "$latest_hex")" \
|
||||
"$ucode_uptodate" \
|
||||
"$blacklisted" \
|
||||
"$(_json_str "${ret_is_latest_known_ucode_latest:-}")" \
|
||||
"$(_json_str "${g_mcedb_source:-}")" \
|
||||
"$(_json_str "${g_mcedb_info:-}")")
|
||||
}
|
||||
|
||||
# --- Format-specific batch emitters ---
|
||||
|
||||
# Emit a single CVE result as plain text
|
||||
@@ -16,45 +287,206 @@ _emit_short() {
|
||||
g_short_output="${g_short_output}$1 "
|
||||
}
|
||||
|
||||
# Append a CVE result as a JSON object to the batch output buffer
|
||||
# Append a CVE result as a terse JSON object to the batch output buffer
|
||||
# Args: $1=cve $2=aka $3=status(UNK|VULN|OK) $4=description
|
||||
# Sets: g_json_output
|
||||
# Callers: pvulnstatus
|
||||
_emit_json() {
|
||||
_emit_json_terse() {
|
||||
local is_vuln esc_name esc_infos
|
||||
case "$3" in
|
||||
UNK) is_vuln="null" ;;
|
||||
VULN) is_vuln="true" ;;
|
||||
OK) is_vuln="false" ;;
|
||||
*)
|
||||
echo "$0: error: unknown status '$3' passed to _emit_json()" >&2
|
||||
echo "$0: error: unknown status '$3' passed to _emit_json_terse()" >&2
|
||||
exit 255
|
||||
;;
|
||||
esac
|
||||
# escape backslashes and double quotes for valid JSON strings
|
||||
esc_name=$(printf '%s' "$2" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g')
|
||||
esc_infos=$(printf '%s' "$4" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g')
|
||||
esc_name=$(_json_escape "$2")
|
||||
esc_infos=$(_json_escape "$4")
|
||||
[ -z "$g_json_output" ] && g_json_output='['
|
||||
g_json_output="${g_json_output}{\"NAME\":\"$esc_name\",\"CVE\":\"$1\",\"VULNERABLE\":$is_vuln,\"INFOS\":\"$esc_infos\"},"
|
||||
}
|
||||
|
||||
# Append vulnerable CVE IDs to the NRPE output buffer
|
||||
# Args: $1=cve $2=aka $3=status $4=description
|
||||
# Sets: g_nrpe_vuln
|
||||
# Append a CVE result as a comprehensive JSON object to the batch output buffer
|
||||
# Args: $1=cve $2=aka $3=status(UNK|VULN|OK) $4=description
|
||||
# Sets: g_json_vulns
|
||||
# Callers: pvulnstatus
|
||||
_emit_nrpe() {
|
||||
[ "$3" = VULN ] && g_nrpe_vuln="$g_nrpe_vuln $1"
|
||||
_emit_json_full() {
|
||||
local is_vuln esc_name esc_infos aliases cpu_affected sysfs_status sysfs_msg
|
||||
case "$3" in
|
||||
UNK) is_vuln="null" ;;
|
||||
VULN) is_vuln="true" ;;
|
||||
OK) is_vuln="false" ;;
|
||||
*)
|
||||
echo "$0: error: unknown status '$3' passed to _emit_json_full()" >&2
|
||||
exit 255
|
||||
;;
|
||||
esac
|
||||
esc_name=$(_json_escape "$2")
|
||||
esc_infos=$(_json_escape "$4")
|
||||
aliases=$(_cve_registry_field "$1" 4)
|
||||
|
||||
# CPU affection status (cached, cheap)
|
||||
if is_cpu_affected "$1" 2>/dev/null; then
|
||||
cpu_affected='true'
|
||||
else
|
||||
cpu_affected='false'
|
||||
fi
|
||||
|
||||
# sysfs status: use the value captured by this CVE's check function, then clear it
|
||||
# so it doesn't leak into the next CVE that might not call sys_interface_check
|
||||
sysfs_status="${g_json_cve_sysfs_status:-}"
|
||||
sysfs_msg="${g_json_cve_sysfs_msg:-}"
|
||||
|
||||
: "${g_json_vulns:=}"
|
||||
g_json_vulns="${g_json_vulns}{\"cve\":\"$1\",\"name\":\"$esc_name\",\"aliases\":$(_json_str "$aliases"),\"cpu_affected\":$cpu_affected,\"status\":\"$3\",\"vulnerable\":$is_vuln,\"info\":\"$esc_infos\",\"sysfs_status\":$(_json_str "$sysfs_status"),\"sysfs_message\":$(_json_str "$sysfs_msg")},"
|
||||
}
|
||||
|
||||
# Append a CVE result as a Prometheus metric to the batch output buffer
|
||||
# Accumulate a CVE result into the NRPE output buffers
|
||||
# Args: $1=cve $2=aka $3=status $4=description
|
||||
# Sets: g_prometheus_output
|
||||
# Sets: g_nrpe_total, g_nrpe_vuln_count, g_nrpe_unk_count, g_nrpe_vuln_ids, g_nrpe_vuln_details, g_nrpe_unk_details
|
||||
# Callers: pvulnstatus
|
||||
_emit_nrpe() {
|
||||
g_nrpe_total=$((g_nrpe_total + 1))
|
||||
case "$3" in
|
||||
VULN)
|
||||
g_nrpe_vuln_count=$((g_nrpe_vuln_count + 1))
|
||||
g_nrpe_vuln_ids="${g_nrpe_vuln_ids:+$g_nrpe_vuln_ids }$1"
|
||||
g_nrpe_vuln_details="${g_nrpe_vuln_details:+$g_nrpe_vuln_details\n}[CRITICAL] $1 ($2): $4"
|
||||
;;
|
||||
UNK)
|
||||
g_nrpe_unk_count=$((g_nrpe_unk_count + 1))
|
||||
g_nrpe_unk_details="${g_nrpe_unk_details:+$g_nrpe_unk_details\n}[UNKNOWN] $1 ($2): $4"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Append a CVE result as a Prometheus gauge to the batch output buffer
|
||||
# Status is encoded numerically: 0=not_vulnerable, 1=vulnerable, 2=unknown
|
||||
# Args: $1=cve $2=aka $3=status(UNK|VULN|OK) $4=description
|
||||
# Sets: g_smc_vuln_output, g_smc_ok_count, g_smc_vuln_count, g_smc_unk_count
|
||||
# Callers: pvulnstatus
|
||||
_emit_prometheus() {
|
||||
local esc_info
|
||||
# escape backslashes and double quotes for Prometheus label values
|
||||
esc_info=$(printf '%s' "$4" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g')
|
||||
g_prometheus_output="${g_prometheus_output:+$g_prometheus_output\n}specex_vuln_status{name=\"$2\",cve=\"$1\",status=\"$3\",info=\"$esc_info\"} 1"
|
||||
local numeric_status cpu_affected full_name esc_name
|
||||
case "$3" in
|
||||
OK)
|
||||
numeric_status=0
|
||||
g_smc_ok_count=$((g_smc_ok_count + 1))
|
||||
;;
|
||||
VULN)
|
||||
numeric_status=1
|
||||
g_smc_vuln_count=$((g_smc_vuln_count + 1))
|
||||
;;
|
||||
UNK)
|
||||
numeric_status=2
|
||||
g_smc_unk_count=$((g_smc_unk_count + 1))
|
||||
;;
|
||||
*)
|
||||
echo "$0: error: unknown status '$3' passed to _emit_prometheus()" >&2
|
||||
exit 255
|
||||
;;
|
||||
esac
|
||||
if is_cpu_affected "$1" 2>/dev/null; then
|
||||
cpu_affected='true'
|
||||
else
|
||||
cpu_affected='false'
|
||||
fi
|
||||
# use the complete CVE name (field 4) rather than the short aka key (field 2)
|
||||
full_name=$(_cve_registry_field "$1" 4)
|
||||
esc_name=$(_prom_escape "$full_name")
|
||||
g_smc_vuln_output="${g_smc_vuln_output:+$g_smc_vuln_output\n}smc_vulnerability_status{cve=\"$1\",name=\"$esc_name\",cpu_affected=\"$cpu_affected\"} $numeric_status"
|
||||
}
|
||||
|
||||
# Build the smc_system_info Prometheus metric line
|
||||
# Sets: g_smc_system_info_line
|
||||
# Callers: src/main.sh (after check_cpu / check_cpu_vulnerabilities)
|
||||
# shellcheck disable=SC2034
|
||||
_build_prometheus_system_info() {
|
||||
local kernel_release kernel_arch hypervisor_host sys_labels
|
||||
if [ "$opt_runtime" = 1 ]; then
|
||||
kernel_release=$(uname -r 2>/dev/null || true)
|
||||
kernel_arch=$(uname -m 2>/dev/null || true)
|
||||
else
|
||||
kernel_release=''
|
||||
kernel_arch=''
|
||||
fi
|
||||
case "${g_has_vmm:-}" in
|
||||
1) hypervisor_host='true' ;;
|
||||
0) hypervisor_host='false' ;;
|
||||
*) hypervisor_host='' ;;
|
||||
esac
|
||||
sys_labels=''
|
||||
[ -n "$kernel_release" ] && sys_labels="${sys_labels:+$sys_labels,}kernel_release=\"$(_prom_escape "$kernel_release")\""
|
||||
[ -n "$kernel_arch" ] && sys_labels="${sys_labels:+$sys_labels,}kernel_arch=\"$(_prom_escape "$kernel_arch")\""
|
||||
[ -n "$hypervisor_host" ] && sys_labels="${sys_labels:+$sys_labels,}hypervisor_host=\"$hypervisor_host\""
|
||||
[ -n "$sys_labels" ] && g_smc_system_info_line="smc_system_info{$sys_labels} 1"
|
||||
}
|
||||
|
||||
# Build the smc_cpu_info Prometheus metric line
|
||||
# Sets: g_smc_cpu_info_line
|
||||
# Callers: src/main.sh (after check_cpu / check_cpu_vulnerabilities)
|
||||
# shellcheck disable=SC2034
|
||||
_build_prometheus_cpu_info() {
|
||||
local cpuid_hex ucode_hex ucode_latest_hex ucode_uptodate ucode_blacklisted codename smt_val cpu_labels
|
||||
if [ -n "${cpu_cpuid:-}" ]; then
|
||||
cpuid_hex=$(printf '0x%08x' "$cpu_cpuid")
|
||||
else
|
||||
cpuid_hex=''
|
||||
fi
|
||||
if [ -n "${cpu_ucode:-}" ]; then
|
||||
ucode_hex=$(printf '0x%x' "$cpu_ucode")
|
||||
else
|
||||
ucode_hex=''
|
||||
fi
|
||||
is_latest_known_ucode
|
||||
case $? in
|
||||
0) ucode_uptodate='true' ;;
|
||||
1) ucode_uptodate='false' ;;
|
||||
*) ucode_uptodate='' ;;
|
||||
esac
|
||||
ucode_latest_hex="${ret_is_latest_known_ucode_version:-}"
|
||||
if is_ucode_blacklisted; then
|
||||
ucode_blacklisted='true'
|
||||
else
|
||||
ucode_blacklisted='false'
|
||||
fi
|
||||
codename=''
|
||||
if is_intel; then
|
||||
codename=$(get_intel_codename 2>/dev/null || true)
|
||||
fi
|
||||
is_cpu_smt_enabled
|
||||
case $? in
|
||||
0) smt_val='true' ;;
|
||||
1) smt_val='false' ;;
|
||||
*) smt_val='' ;;
|
||||
esac
|
||||
cpu_labels=''
|
||||
[ -n "${cpu_vendor:-}" ] && cpu_labels="${cpu_labels:+$cpu_labels,}vendor=\"$(_prom_escape "$cpu_vendor")\""
|
||||
[ -n "${cpu_friendly_name:-}" ] && cpu_labels="${cpu_labels:+$cpu_labels,}model=\"$(_prom_escape "$cpu_friendly_name")\""
|
||||
# arch-specific labels
|
||||
case "${cpu_vendor:-}" in
|
||||
GenuineIntel | AuthenticAMD | HygonGenuine)
|
||||
cpu_labels="${cpu_labels:+$cpu_labels,}arch=\"x86\""
|
||||
[ -n "${cpu_family:-}" ] && cpu_labels="${cpu_labels:+$cpu_labels,}family=\"$cpu_family\""
|
||||
[ -n "${cpu_model:-}" ] && cpu_labels="${cpu_labels:+$cpu_labels,}model_id=\"$cpu_model\""
|
||||
[ -n "${cpu_stepping:-}" ] && cpu_labels="${cpu_labels:+$cpu_labels,}stepping=\"$cpu_stepping\""
|
||||
[ -n "$cpuid_hex" ] && cpu_labels="${cpu_labels:+$cpu_labels,}cpuid=\"$cpuid_hex\""
|
||||
[ -n "$codename" ] && cpu_labels="${cpu_labels:+$cpu_labels,}codename=\"$(_prom_escape "$codename")\""
|
||||
;;
|
||||
ARM | CAVIUM | PHYTIUM)
|
||||
cpu_labels="${cpu_labels:+$cpu_labels,}arch=\"arm\""
|
||||
[ -n "${cpu_part_list:-}" ] && cpu_labels="${cpu_labels:+$cpu_labels,}part_list=\"$(_prom_escape "$cpu_part_list")\""
|
||||
[ -n "${cpu_arch_list:-}" ] && cpu_labels="${cpu_labels:+$cpu_labels,}arch_list=\"$(_prom_escape "$cpu_arch_list")\""
|
||||
;;
|
||||
esac
|
||||
[ -n "$smt_val" ] && cpu_labels="${cpu_labels:+$cpu_labels,}smt=\"$smt_val\""
|
||||
[ -n "$ucode_hex" ] && cpu_labels="${cpu_labels:+$cpu_labels,}microcode=\"$ucode_hex\""
|
||||
[ -n "$ucode_latest_hex" ] && cpu_labels="${cpu_labels:+$cpu_labels,}microcode_latest=\"$ucode_latest_hex\""
|
||||
[ -n "$ucode_uptodate" ] && cpu_labels="${cpu_labels:+$cpu_labels,}microcode_up_to_date=\"$ucode_uptodate\""
|
||||
# always emit microcode_blacklisted when we have microcode info (it's a boolean, never omit)
|
||||
[ -n "$ucode_hex" ] && cpu_labels="${cpu_labels:+$cpu_labels,}microcode_blacklisted=\"$ucode_blacklisted\""
|
||||
[ -n "$cpu_labels" ] && g_smc_cpu_info_line="smc_cpu_info{$cpu_labels} 1"
|
||||
}
|
||||
|
||||
# Update global state used to determine the program exit code
|
||||
@@ -85,7 +517,8 @@ pvulnstatus() {
|
||||
case "$opt_batch_format" in
|
||||
text) _emit_text "$1" "$aka" "$2" "$3" ;;
|
||||
short) _emit_short "$1" "$aka" "$2" "$3" ;;
|
||||
json) _emit_json "$1" "$aka" "$2" "$3" ;;
|
||||
json) _emit_json_full "$1" "$aka" "$2" "$3" ;;
|
||||
json-terse) _emit_json_terse "$1" "$aka" "$2" "$3" ;;
|
||||
nrpe) _emit_nrpe "$1" "$aka" "$2" "$3" ;;
|
||||
prometheus) _emit_prometheus "$1" "$aka" "$2" "$3" ;;
|
||||
*)
|
||||
@@ -93,6 +526,9 @@ pvulnstatus() {
|
||||
exit 255
|
||||
;;
|
||||
esac
|
||||
# reset per-CVE sysfs globals so they don't leak into the next CVE
|
||||
g_json_cve_sysfs_status=''
|
||||
g_json_cve_sysfs_msg=''
|
||||
fi
|
||||
|
||||
_record_result "$1" "$2"
|
||||
|
||||
@@ -52,7 +52,7 @@ write_msr_one_core() {
|
||||
core="$1"
|
||||
msr_dec=$(($2))
|
||||
msr=$(printf "0x%x" "$msr_dec")
|
||||
value_dec=$(($3))
|
||||
value_dec=$((${3:-0}))
|
||||
value=$(printf "0x%x" "$value_dec")
|
||||
|
||||
ret_write_msr_msg='unknown error'
|
||||
|
||||
@@ -26,18 +26,16 @@ read_mcedb() {
|
||||
|
||||
# Read the Intel official affected CPUs database (builtin) to stdout
|
||||
read_inteldb() {
|
||||
if [ "$opt_intel_db" = 1 ]; then
|
||||
awk '/^# %%% ENDOFINTELDB/ { exit } { if (DELIM==1) { print $2 } } /^# %%% INTELDB/ { DELIM=1 }' "$0"
|
||||
fi
|
||||
# otherwise don't output nothing, it'll be as if the database is empty
|
||||
awk '/^# %%% ENDOFINTELDB/ { exit } { if (DELIM==1) { print $2 } } /^# %%% INTELDB/ { DELIM=1 }' "$0"
|
||||
}
|
||||
|
||||
# Check whether the CPU is running the latest known microcode version
|
||||
# Sets: ret_is_latest_known_ucode_latest
|
||||
# Sets: ret_is_latest_known_ucode_latest, ret_is_latest_known_ucode_version
|
||||
# Returns: 0=latest, 1=outdated, 2=unknown
|
||||
is_latest_known_ucode() {
|
||||
local brand_prefix tuple pfmask ucode ucode_date
|
||||
parse_cpu_details
|
||||
ret_is_latest_known_ucode_version=''
|
||||
if [ "$cpu_cpuid" = 0 ]; then
|
||||
ret_is_latest_known_ucode_latest="couldn't get your cpuid"
|
||||
return 2
|
||||
@@ -64,6 +62,8 @@ is_latest_known_ucode() {
|
||||
ucode_date=$(echo "$tuple" | cut -d, -f5 | sed -E 's=(....)(..)(..)=\1/\2/\3=')
|
||||
pr_debug "is_latest_known_ucode: with cpuid $cpu_cpuid has ucode $cpu_ucode, last known is $ucode from $ucode_date"
|
||||
ret_is_latest_known_ucode_latest=$(printf "latest version is 0x%x dated $ucode_date according to $g_mcedb_info" "$ucode")
|
||||
# shellcheck disable=SC2034
|
||||
ret_is_latest_known_ucode_version=$(printf "0x%x" "$ucode")
|
||||
if [ "$cpu_ucode" -ge "$ucode" ]; then
|
||||
return 0
|
||||
else
|
||||
|
||||
@@ -89,7 +89,7 @@ if [ "$opt_cpu" != all ] && [ "$opt_cpu" -gt "$g_max_core_id" ]; then
|
||||
exit 255
|
||||
fi
|
||||
|
||||
if [ "$opt_live" = 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ]; then
|
||||
pr_info "Checking for vulnerabilities on current system"
|
||||
|
||||
# try to find the image of the current running kernel
|
||||
@@ -251,7 +251,7 @@ else
|
||||
fi
|
||||
if [ -n "$g_kernel_version" ]; then
|
||||
# in live mode, check if the img we found is the correct one
|
||||
if [ "$opt_live" = 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ]; then
|
||||
pr_verbose "Kernel image is \033[35m$g_kernel_version"
|
||||
if ! echo "$g_kernel_version" | grep -qF "$(uname -r)"; then
|
||||
pr_warn "Possible discrepancy between your running kernel '$(uname -r)' and the image '$g_kernel_version' we found ($opt_kernel), results might be incorrect"
|
||||
@@ -283,7 +283,7 @@ sys_interface_check() {
|
||||
msg=''
|
||||
ret_sys_interface_check_fullmsg=''
|
||||
|
||||
if [ "$opt_live" = 1 ] && [ "$opt_no_sysfs" = 0 ] && [ -r "$file" ]; then
|
||||
if [ "$opt_runtime" = 1 ] && [ "$opt_no_sysfs" = 0 ] && [ -r "$file" ]; then
|
||||
:
|
||||
else
|
||||
g_mockme=$(printf "%b\n%b" "$g_mockme" "SMC_MOCK_SYSFS_$(basename "$file")_RET=1")
|
||||
@@ -312,9 +312,14 @@ sys_interface_check() {
|
||||
g_mockme=$(printf "%b\n%b" "$g_mockme" "SMC_MOCK_SYSFS_$(basename "$file")='$ret_sys_interface_check_fullmsg'")
|
||||
fi
|
||||
if [ "$mode" = silent ]; then
|
||||
# capture sysfs message for JSON even in silent mode
|
||||
# shellcheck disable=SC2034
|
||||
g_json_cve_sysfs_msg="$ret_sys_interface_check_fullmsg"
|
||||
return 0
|
||||
elif [ "$mode" = quiet ]; then
|
||||
pr_info "* Information from the /sys interface: $ret_sys_interface_check_fullmsg"
|
||||
# shellcheck disable=SC2034
|
||||
g_json_cve_sysfs_msg="$ret_sys_interface_check_fullmsg"
|
||||
return 0
|
||||
fi
|
||||
pr_info_nol "* Mitigated according to the /sys interface: "
|
||||
@@ -334,6 +339,11 @@ sys_interface_check() {
|
||||
ret_sys_interface_check_status=UNK
|
||||
pstatus yellow UNKNOWN "$ret_sys_interface_check_fullmsg"
|
||||
fi
|
||||
# capture for JSON full output (read by _emit_json_full via pvulnstatus)
|
||||
# shellcheck disable=SC2034
|
||||
g_json_cve_sysfs_status="$ret_sys_interface_check_status"
|
||||
# shellcheck disable=SC2034
|
||||
g_json_cve_sysfs_msg="$ret_sys_interface_check_fullmsg"
|
||||
pr_debug "sys_interface_check: $file=$msg (re=$regex)"
|
||||
return 0
|
||||
}
|
||||
@@ -342,7 +352,7 @@ sys_interface_check() {
|
||||
check_kernel_info() {
|
||||
local config_display
|
||||
pr_info "\033[1;34mKernel information\033[0m"
|
||||
if [ "$opt_live" = 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ]; then
|
||||
pr_info "* Kernel is \033[35m$g_os $(uname -r) $(uname -v) $(uname -m)\033[0m"
|
||||
elif [ -n "$g_kernel_version" ]; then
|
||||
pr_info "* Kernel is \033[35m$g_kernel_version\033[0m"
|
||||
@@ -446,7 +456,7 @@ check_cpu() {
|
||||
ret=invalid
|
||||
pstatus yellow NO "unknown CPU"
|
||||
fi
|
||||
if [ -z "$cap_ibrs" ] && [ $ret = $READ_CPUID_RET_ERR ] && [ "$opt_live" = 1 ]; then
|
||||
if [ -z "$cap_ibrs" ] && [ $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 ibrs; then
|
||||
cap_ibrs='IBRS (cpuinfo)'
|
||||
@@ -523,7 +533,7 @@ check_cpu() {
|
||||
if [ $ret = $READ_CPUID_RET_OK ]; then
|
||||
cap_ibpb='IBPB_SUPPORT'
|
||||
pstatus green YES "IBPB_SUPPORT feature bit"
|
||||
elif [ $ret = $READ_CPUID_RET_ERR ] && [ "$opt_live" = 1 ] && grep ^flags "$g_procfs/cpuinfo" | grep -qw ibpb; then
|
||||
elif [ $ret = $READ_CPUID_RET_ERR ] && [ "$opt_runtime" = 1 ] && grep ^flags "$g_procfs/cpuinfo" | grep -qw ibpb; then
|
||||
# CPUID device unavailable (e.g. in a VM): fall back to /proc/cpuinfo
|
||||
cap_ibpb='IBPB (cpuinfo)'
|
||||
pstatus green YES "ibpb flag in $g_procfs/cpuinfo"
|
||||
@@ -594,7 +604,7 @@ check_cpu() {
|
||||
ret=invalid
|
||||
pstatus yellow UNKNOWN "unknown CPU"
|
||||
fi
|
||||
if [ -z "$cap_stibp" ] && [ $ret = $READ_CPUID_RET_ERR ] && [ "$opt_live" = 1 ]; then
|
||||
if [ -z "$cap_stibp" ] && [ $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 stibp; then
|
||||
cap_stibp='STIBP (cpuinfo)'
|
||||
@@ -666,7 +676,7 @@ check_cpu() {
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$cap_ssbd" ] && [ "$ret24" = $READ_CPUID_RET_ERR ] && [ "$ret25" = $READ_CPUID_RET_ERR ] && [ "$opt_live" = 1 ]; then
|
||||
if [ -z "$cap_ssbd" ] && [ "$ret24" = $READ_CPUID_RET_ERR ] && [ "$ret25" = $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='SSBD (cpuinfo)'
|
||||
@@ -730,7 +740,7 @@ check_cpu() {
|
||||
if [ $ret = $READ_CPUID_RET_OK ]; then
|
||||
pstatus green YES "L1D flush feature bit"
|
||||
cap_l1df=1
|
||||
elif [ $ret = $READ_CPUID_RET_ERR ] && [ "$opt_live" = 1 ] && grep ^flags "$g_procfs/cpuinfo" | grep -qw flush_l1d; then
|
||||
elif [ $ret = $READ_CPUID_RET_ERR ] && [ "$opt_runtime" = 1 ] && grep ^flags "$g_procfs/cpuinfo" | grep -qw flush_l1d; then
|
||||
# CPUID device unavailable (e.g. in a VM): fall back to /proc/cpuinfo
|
||||
pstatus green YES "flush_l1d flag in $g_procfs/cpuinfo"
|
||||
cap_l1df=1
|
||||
@@ -750,7 +760,7 @@ check_cpu() {
|
||||
if [ $ret = $READ_CPUID_RET_OK ]; then
|
||||
cap_md_clear=1
|
||||
pstatus green YES "MD_CLEAR feature bit"
|
||||
elif [ $ret = $READ_CPUID_RET_ERR ] && [ "$opt_live" = 1 ] && grep ^flags "$g_procfs/cpuinfo" | grep -qw md_clear; then
|
||||
elif [ $ret = $READ_CPUID_RET_ERR ] && [ "$opt_runtime" = 1 ] && grep ^flags "$g_procfs/cpuinfo" | grep -qw md_clear; then
|
||||
# CPUID device unavailable (e.g. in a VM): fall back to /proc/cpuinfo
|
||||
cap_md_clear=1
|
||||
pstatus green YES "md_clear flag in $g_procfs/cpuinfo"
|
||||
@@ -820,7 +830,7 @@ check_cpu() {
|
||||
if [ $ret = $READ_CPUID_RET_OK ]; then
|
||||
pstatus green YES
|
||||
cap_arch_capabilities=1
|
||||
elif [ $ret = $READ_CPUID_RET_ERR ] && [ "$opt_live" = 1 ] && grep ^flags "$g_procfs/cpuinfo" | grep -qw arch_capabilities; then
|
||||
elif [ $ret = $READ_CPUID_RET_ERR ] && [ "$opt_runtime" = 1 ] && grep ^flags "$g_procfs/cpuinfo" | grep -qw arch_capabilities; then
|
||||
# CPUID device unavailable (e.g. in a VM): fall back to /proc/cpuinfo
|
||||
pstatus green YES "arch_capabilities flag in $g_procfs/cpuinfo"
|
||||
cap_arch_capabilities=1
|
||||
|
||||
141
src/main.sh
141
src/main.sh
@@ -1,6 +1,12 @@
|
||||
# vim: set ts=4 sw=4 sts=4 et:
|
||||
|
||||
check_kernel_info
|
||||
|
||||
# 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 [ "$opt_no_hw" = 0 ] && [ -z "$opt_arch_prefix" ]; then
|
||||
@@ -10,6 +16,23 @@ if [ "$opt_no_hw" = 0 ] && [ -z "$opt_arch_prefix" ]; then
|
||||
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 [ "$opt_no_hw" = 0 ] && [ -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 [ "$opt_no_hw" = 0 ] && [ -z "$opt_arch_prefix" ]; then
|
||||
_build_prometheus_cpu_info
|
||||
fi
|
||||
fi
|
||||
|
||||
# now run the checks the user asked for
|
||||
for cve in $g_supported_cve_list; do
|
||||
if [ "$opt_cve_all" = 1 ] || echo "$opt_cve_list" | grep -qw "$cve"; then
|
||||
@@ -69,25 +92,129 @@ if [ "$g_mocked" = 1 ]; then
|
||||
fi
|
||||
|
||||
if [ "$opt_batch" = 1 ] && [ "$opt_batch_format" = "nrpe" ]; then
|
||||
if [ -n "$g_nrpe_vuln" ]; then
|
||||
echo "Vulnerable:$g_nrpe_vuln"
|
||||
_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
|
||||
echo "OK"
|
||||
_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" ]; then
|
||||
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
|
||||
echo "# TYPE specex_vuln_status untyped"
|
||||
echo "# HELP specex_vuln_status Exposure of system to speculative execution vulnerabilities"
|
||||
printf "%b\n" "$g_prometheus_output"
|
||||
prom_run_as_root='false'
|
||||
[ "$(id -u)" -eq 0 ] && prom_run_as_root='true'
|
||||
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'
|
||||
else
|
||||
prom_mode='live'
|
||||
fi
|
||||
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
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
check_mds_bsd() {
|
||||
local kernel_md_clear kernel_smt_allowed kernel_mds_enabled kernel_mds_state
|
||||
pr_info_nol "* Kernel supports using MD_CLEAR mitigation: "
|
||||
if [ "$opt_live" = 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ]; then
|
||||
if sysctl hw.mds_disable >/dev/null 2>&1; then
|
||||
pstatus green YES
|
||||
kernel_md_clear=1
|
||||
@@ -76,7 +76,7 @@ check_mds_bsd() {
|
||||
else
|
||||
if [ "$cap_md_clear" = 1 ]; then
|
||||
if [ "$kernel_md_clear" = 1 ]; then
|
||||
if [ "$opt_live" = 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ]; then
|
||||
# mitigation must also be enabled
|
||||
if [ "$kernel_mds_enabled" -ge 1 ]; then
|
||||
if [ "$opt_paranoid" != 1 ] || [ "$kernel_smt_allowed" = 0 ]; then
|
||||
@@ -95,7 +95,7 @@ check_mds_bsd() {
|
||||
pvulnstatus "$cve" VULN "Your microcode supports mitigation, but your kernel doesn't, upgrade it to mitigate the vulnerability"
|
||||
fi
|
||||
else
|
||||
if [ "$kernel_md_clear" = 1 ] && [ "$opt_live" = 1 ]; then
|
||||
if [ "$kernel_md_clear" = 1 ] && [ "$opt_runtime" = 1 ]; then
|
||||
# no MD_CLEAR in microcode, but FreeBSD may still have software-only mitigation active
|
||||
case "$kernel_mds_state" in
|
||||
software*)
|
||||
@@ -135,7 +135,7 @@ check_mds_linux() {
|
||||
pr_info_nol "* Kernel supports using MD_CLEAR mitigation: "
|
||||
kernel_md_clear=''
|
||||
kernel_md_clear_can_tell=1
|
||||
if [ "$opt_live" = 1 ] && grep ^flags "$g_procfs/cpuinfo" | grep -qw md_clear; then
|
||||
if [ "$opt_runtime" = 1 ] && grep ^flags "$g_procfs/cpuinfo" | grep -qw md_clear; then
|
||||
kernel_md_clear="md_clear found in $g_procfs/cpuinfo"
|
||||
pstatus green YES "$kernel_md_clear"
|
||||
fi
|
||||
@@ -158,7 +158,7 @@ check_mds_linux() {
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$opt_live" = 1 ] && [ "$sys_interface_available" = 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ] && [ "$sys_interface_available" = 1 ]; then
|
||||
pr_info_nol "* Kernel mitigation is enabled and active: "
|
||||
if echo "$ret_sys_interface_check_fullmsg" | grep -qi ^mitigation; then
|
||||
mds_mitigated=1
|
||||
@@ -190,7 +190,7 @@ check_mds_linux() {
|
||||
# compute mystatus and mymsg from our own logic
|
||||
if [ "$cap_md_clear" = 1 ]; then
|
||||
if [ -n "$kernel_md_clear" ]; then
|
||||
if [ "$opt_live" = 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ]; then
|
||||
# mitigation must also be enabled
|
||||
if [ "$mds_mitigated" = 1 ]; then
|
||||
if [ "$opt_paranoid" != 1 ] || [ "$mds_smt_mitigated" = 1 ]; then
|
||||
|
||||
@@ -162,7 +162,7 @@ check_mmio_linux() {
|
||||
pstatus yellow NO
|
||||
fi
|
||||
|
||||
if [ "$opt_live" = 1 ] && [ "$sys_interface_available" = 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ] && [ "$sys_interface_available" = 1 ]; then
|
||||
pr_info_nol "* Kernel mitigation is enabled and active: "
|
||||
if echo "$ret_sys_interface_check_fullmsg" | grep -qi ^mitigation; then
|
||||
mmio_mitigated=1
|
||||
@@ -194,7 +194,7 @@ check_mmio_linux() {
|
||||
# compute mystatus and mymsg from our own logic
|
||||
if [ "$cap_fb_clear" = 1 ]; then
|
||||
if [ -n "$kernel_mmio" ]; then
|
||||
if [ "$opt_live" = 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ]; then
|
||||
# mitigation must also be enabled
|
||||
if [ "$mmio_mitigated" = 1 ]; then
|
||||
if [ "$opt_paranoid" != 1 ] || [ "$mmio_smt_mitigated" = 1 ]; then
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: set ts=4 sw=4 sts=4 et:
|
||||
###############################
|
||||
# Straight-Line Speculation (SLS) — supplementary check (--extra only)
|
||||
# Straight-Line Speculation (SLS) supplementary check (--extra only)
|
||||
#
|
||||
# SLS: x86 CPUs may speculatively execute instructions past unconditional
|
||||
# control flow changes (RET, indirect JMP/CALL). Mitigated at compile time
|
||||
@@ -258,9 +258,6 @@ check_CVE_0000_0001_linux() {
|
||||
# --- verdict (x86_64) ---
|
||||
if [ "$_sls_config" = 1 ] || [ "$_sls_heuristic" = 1 ]; then
|
||||
pvulnstatus "$cve" OK "kernel compiled with SLS mitigation"
|
||||
explain "Your kernel was compiled with CONFIG_MITIGATION_SLS=y (or CONFIG_SLS=y on kernels before 6.8),\n" \
|
||||
"which enables the GCC flag -mharden-sls=all to insert INT3 instructions after unconditional\n" \
|
||||
"control flow changes, blocking straight-line speculation."
|
||||
elif [ "$_sls_config" = 0 ] || [ "$_sls_heuristic" = 0 ]; then
|
||||
pvulnstatus "$cve" VULN "kernel not compiled with SLS mitigation"
|
||||
explain "Recompile your kernel with CONFIG_MITIGATION_SLS=y (or CONFIG_SLS=y on kernels before 6.8).\n" \
|
||||
|
||||
@@ -265,7 +265,7 @@ check_CVE_2017_5715_linux() {
|
||||
g_ibpb_supported=''
|
||||
g_ibpb_enabled=''
|
||||
|
||||
if [ "$opt_live" = 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ]; then
|
||||
# in live mode, we can check for the ibrs_enabled file in debugfs
|
||||
# all versions of the patches have it (NOT the case of IBPB or KPTI)
|
||||
g_ibrs_can_tell=1
|
||||
@@ -416,7 +416,7 @@ check_CVE_2017_5715_linux() {
|
||||
fi
|
||||
|
||||
pr_info_nol " * IBRS enabled and active: "
|
||||
if [ "$opt_live" = 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ]; then
|
||||
if [ "$g_ibpb_enabled" = 2 ]; then
|
||||
# if ibpb=2, ibrs is forcefully=0
|
||||
pstatus blue NO "IBPB used instead of IBRS in all kernel entrypoints"
|
||||
@@ -447,7 +447,7 @@ check_CVE_2017_5715_linux() {
|
||||
esac
|
||||
fi
|
||||
else
|
||||
pstatus blue N/A "not testable in offline mode"
|
||||
pstatus blue N/A "not testable in no-runtime mode"
|
||||
fi
|
||||
|
||||
pr_info_nol " * Kernel is compiled with IBPB support: "
|
||||
@@ -455,8 +455,8 @@ check_CVE_2017_5715_linux() {
|
||||
if [ "$g_ibpb_can_tell" = 1 ]; then
|
||||
pstatus yellow NO
|
||||
else
|
||||
# if we're in offline mode without System.map, we can't really know
|
||||
pstatus yellow UNKNOWN "in offline mode, we need the kernel image to be able to tell"
|
||||
# if we're in no-runtime mode without System.map, we can't really know
|
||||
pstatus yellow UNKNOWN "in no-runtime mode, we need the kernel image to be able to tell"
|
||||
fi
|
||||
else
|
||||
if [ "$opt_verbose" -ge 2 ]; then
|
||||
@@ -467,7 +467,7 @@ check_CVE_2017_5715_linux() {
|
||||
fi
|
||||
|
||||
pr_info_nol " * IBPB enabled and active: "
|
||||
if [ "$opt_live" = 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ]; then
|
||||
case "$g_ibpb_enabled" in
|
||||
"")
|
||||
if [ "$g_ibrs_supported" = 1 ]; then
|
||||
@@ -484,7 +484,7 @@ check_CVE_2017_5715_linux() {
|
||||
*) pstatus yellow UNKNOWN ;;
|
||||
esac
|
||||
else
|
||||
pstatus blue N/A "not testable in offline mode"
|
||||
pstatus blue N/A "not testable in no-runtime mode"
|
||||
fi
|
||||
|
||||
pr_info "* Mitigation 2"
|
||||
@@ -544,7 +544,7 @@ check_CVE_2017_5715_linux() {
|
||||
#
|
||||
# since 5.15.28, this is now "Retpolines" as the implementation was switched to a generic one,
|
||||
# so we look for both "retpoline" and "retpolines"
|
||||
if [ "$opt_live" = 1 ] && [ -n "$ret_sys_interface_check_fullmsg" ]; then
|
||||
if [ "$opt_runtime" = 1 ] && [ -n "$ret_sys_interface_check_fullmsg" ]; then
|
||||
if echo "$ret_sys_interface_check_fullmsg" | grep -qwi -e retpoline -e retpolines; then
|
||||
if echo "$ret_sys_interface_check_fullmsg" | grep -qwi minimal; then
|
||||
retpoline_compiler=0
|
||||
@@ -595,7 +595,7 @@ check_CVE_2017_5715_linux() {
|
||||
|
||||
# only Red Hat has a tunable to disable it on runtime
|
||||
retp_enabled=-1
|
||||
if [ "$opt_live" = 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ]; then
|
||||
if [ -e "$g_specex_knob_dir/retp_enabled" ]; then
|
||||
retp_enabled=$(cat "$g_specex_knob_dir/retp_enabled" 2>/dev/null)
|
||||
pr_debug "retpoline: found $g_specex_knob_dir/retp_enabled=$retp_enabled"
|
||||
@@ -625,7 +625,7 @@ check_CVE_2017_5715_linux() {
|
||||
if is_vulnerable_to_empty_rsb || [ "$opt_verbose" -ge 2 ]; then
|
||||
pr_info_nol " * Kernel supports RSB filling: "
|
||||
rsb_filling=0
|
||||
if [ "$opt_live" = 1 ] && [ "$opt_no_sysfs" != 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ] && [ "$opt_no_sysfs" != 1 ]; then
|
||||
# if we're live and we aren't denied looking into /sys, let's do it
|
||||
if echo "$ret_sys_interface_check_fullmsg" | grep -qw RSB; then
|
||||
rsb_filling=1
|
||||
@@ -718,7 +718,7 @@ check_CVE_2017_5715_linux() {
|
||||
*", IBPB"* | *"; IBPB"*) v2_ibpb_mode=conditional ;;
|
||||
*) v2_ibpb_mode=disabled ;;
|
||||
esac
|
||||
elif [ "$opt_live" = 1 ]; then
|
||||
elif [ "$opt_runtime" = 1 ]; then
|
||||
case "$g_ibpb_enabled" in
|
||||
2) v2_ibpb_mode=always-on ;;
|
||||
1) v2_ibpb_mode=conditional ;;
|
||||
@@ -816,7 +816,7 @@ check_CVE_2017_5715_linux() {
|
||||
*"PBRSB-eIBRS: Vulnerable"*) v2_pbrsb_status=vulnerable ;;
|
||||
*) v2_pbrsb_status=unknown ;;
|
||||
esac
|
||||
elif [ "$opt_live" != 1 ] && [ -n "$g_kernel" ]; then
|
||||
elif [ "$opt_runtime" != 1 ] && [ -n "$g_kernel" ]; then
|
||||
if grep -q 'PBRSB-eIBRS' "$g_kernel" 2>/dev/null; then
|
||||
v2_pbrsb_status=sw-sequence
|
||||
else
|
||||
@@ -847,7 +847,7 @@ check_CVE_2017_5715_linux() {
|
||||
*"BHI: Vulnerable"*) v2_bhi_status=vulnerable ;;
|
||||
*) v2_bhi_status=unknown ;;
|
||||
esac
|
||||
elif [ "$opt_live" != 1 ] && [ -n "$opt_config" ] && [ -r "$opt_config" ]; then
|
||||
elif [ "$opt_runtime" != 1 ] && [ -n "$opt_config" ] && [ -r "$opt_config" ]; then
|
||||
if grep -q '^CONFIG_\(MITIGATION_\)\?SPECTRE_BHI' "$opt_config"; then
|
||||
if [ "$cap_bhi" = 1 ]; then
|
||||
v2_bhi_status=bhi_dis_s
|
||||
@@ -871,7 +871,7 @@ check_CVE_2017_5715_linux() {
|
||||
esac
|
||||
|
||||
# --- v2_vuln_module ---
|
||||
if [ "$opt_live" = 1 ] && [ -n "$ret_sys_interface_check_fullmsg" ]; then
|
||||
if [ "$opt_runtime" = 1 ] && [ -n "$ret_sys_interface_check_fullmsg" ]; then
|
||||
pr_info_nol " * Non-retpoline module loaded: "
|
||||
if echo "$ret_sys_interface_check_fullmsg" | grep -q 'vulnerable module loaded'; then
|
||||
v2_vuln_module=1
|
||||
@@ -970,7 +970,7 @@ check_CVE_2017_5715_linux() {
|
||||
if [ -n "${SMC_MOCK_UNPRIVILEGED_BPF_DISABLED:-}" ]; then
|
||||
_ebpf_disabled="$SMC_MOCK_UNPRIVILEGED_BPF_DISABLED"
|
||||
g_mocked=1
|
||||
elif [ "$opt_live" = 1 ] && [ -r "$g_procfs/sys/kernel/unprivileged_bpf_disabled" ]; then
|
||||
elif [ "$opt_runtime" = 1 ] && [ -r "$g_procfs/sys/kernel/unprivileged_bpf_disabled" ]; then
|
||||
_ebpf_disabled=$(cat "$g_procfs/sys/kernel/unprivileged_bpf_disabled" 2>/dev/null)
|
||||
g_mockme=$(printf "%b\n%b" "$g_mockme" "SMC_MOCK_UNPRIVILEGED_BPF_DISABLED='$_ebpf_disabled'")
|
||||
fi
|
||||
@@ -1157,19 +1157,19 @@ 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
|
||||
elif [ "$opt_live" != 1 ]; then
|
||||
# No-runtime mode fallback
|
||||
elif [ "$opt_runtime" != 1 ]; then
|
||||
if [ "$retpoline" = 1 ] && [ -n "$g_ibpb_supported" ]; then
|
||||
pvulnstatus "$cve" OK "offline mode: kernel supports retpoline + IBPB to mitigate the vulnerability"
|
||||
pvulnstatus "$cve" OK "no-runtime mode: kernel supports retpoline + IBPB to mitigate the vulnerability"
|
||||
elif [ -n "$g_ibrs_supported" ] && [ -n "$g_ibpb_supported" ]; then
|
||||
pvulnstatus "$cve" OK "offline mode: kernel supports IBRS + IBPB to mitigate the vulnerability"
|
||||
pvulnstatus "$cve" OK "no-runtime mode: kernel supports IBRS + IBPB to mitigate the vulnerability"
|
||||
elif [ "$cap_ibrs_all" = 1 ] || [ "$cap_autoibrs" = 1 ]; then
|
||||
pvulnstatus "$cve" OK "offline mode: CPU supports Enhanced / Automatic IBRS"
|
||||
pvulnstatus "$cve" OK "no-runtime mode: CPU supports Enhanced / Automatic IBRS"
|
||||
# CONFIG_MITIGATION_SPECTRE_V2 (v6.12+): top-level on/off for all Spectre V2 mitigations
|
||||
elif [ -n "$opt_config" ] && [ -r "$opt_config" ] && grep -q '^CONFIG_MITIGATION_SPECTRE_V2=y' "$opt_config"; then
|
||||
pvulnstatus "$cve" OK "offline mode: kernel has Spectre V2 mitigation framework enabled (CONFIG_MITIGATION_SPECTRE_V2)"
|
||||
pvulnstatus "$cve" OK "no-runtime mode: kernel has Spectre V2 mitigation framework enabled (CONFIG_MITIGATION_SPECTRE_V2)"
|
||||
elif [ "$g_ibrs_can_tell" != 1 ]; then
|
||||
pvulnstatus "$cve" UNK "offline mode: not enough information"
|
||||
pvulnstatus "$cve" UNK "no-runtime mode: not enough information"
|
||||
explain "Re-run this script with root privileges, and give it the kernel image (--kernel), the kernel configuration (--config) and the System.map file (--map) corresponding to the kernel you would like to inspect."
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -57,12 +57,12 @@ check_CVE_2017_5753_linux() {
|
||||
status=$ret_sys_interface_check_status
|
||||
fi
|
||||
if [ "$opt_sysfs_only" != 1 ]; then
|
||||
# no /sys interface (or offline mode), fallback to our own ways
|
||||
# no /sys interface (or no-runtime mode), fallback to our own ways
|
||||
|
||||
# Primary detection: grep for sysfs mitigation strings in the kernel binary.
|
||||
# The string "__user pointer sanitization" is present in all kernel versions
|
||||
# that have spectre_v1 sysfs support (x86 v4.16+, ARM64 v5.2+, ARM32 v5.17+),
|
||||
# including RHEL "Load fences" variants. This is cheap and works 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()
|
||||
|
||||
@@ -104,7 +104,7 @@ check_CVE_2017_5754_linux() {
|
||||
|
||||
mount_debugfs
|
||||
pr_info_nol " * PTI enabled and active: "
|
||||
if [ "$opt_live" = 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ]; then
|
||||
dmesg_grep="Kernel/User page tables isolation: enabled"
|
||||
dmesg_grep="$dmesg_grep|Kernel page table isolation enabled"
|
||||
dmesg_grep="$dmesg_grep|x86/pti: Unmapping kernel while in userspace"
|
||||
@@ -150,7 +150,7 @@ check_CVE_2017_5754_linux() {
|
||||
pstatus yellow NO
|
||||
fi
|
||||
else
|
||||
pstatus blue N/A "not testable in offline mode"
|
||||
pstatus blue N/A "not testable in no-runtime mode"
|
||||
fi
|
||||
|
||||
pti_performance_check
|
||||
@@ -167,7 +167,7 @@ check_CVE_2017_5754_linux() {
|
||||
is_xen_dom0 && xen_pv_domo=1
|
||||
is_xen_domU && xen_pv_domu=1
|
||||
|
||||
if [ "$opt_live" = 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ]; then
|
||||
# checking whether we're running under Xen PV 64 bits. If yes, we are affected by affected_variant3
|
||||
# (unless we are a Dom0)
|
||||
pr_info_nol "* Running as a Xen PV DomU: "
|
||||
@@ -183,7 +183,7 @@ check_CVE_2017_5754_linux() {
|
||||
pvulnstatus "$cve" OK "your CPU vendor reported your CPU model as not affected"
|
||||
elif [ -z "$msg" ]; then
|
||||
# if msg is empty, sysfs check didn't fill it, rely on our own test
|
||||
if [ "$opt_live" = 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ]; then
|
||||
if [ "$kpti_enabled" = 1 ]; then
|
||||
pvulnstatus "$cve" OK "PTI mitigates the vulnerability"
|
||||
elif [ "$xen_pv_domo" = 1 ]; then
|
||||
@@ -209,12 +209,12 @@ check_CVE_2017_5754_linux() {
|
||||
fi
|
||||
else
|
||||
if [ -n "$kpti_support" ]; then
|
||||
pvulnstatus "$cve" OK "offline mode: PTI will mitigate the vulnerability if enabled at runtime"
|
||||
pvulnstatus "$cve" OK "no-runtime mode: PTI will mitigate the vulnerability if enabled at runtime"
|
||||
elif [ "$kpti_can_tell" = 1 ]; then
|
||||
pvulnstatus "$cve" VULN "PTI is needed to mitigate the vulnerability"
|
||||
explain "If you're using a distro kernel, upgrade your distro to get the latest kernel available. Otherwise, recompile the kernel with the CONFIG_(MITIGATION_)PAGE_TABLE_ISOLATION option (named CONFIG_KAISER for some kernels), or the CONFIG_UNMAP_KERNEL_AT_EL0 option (for ARM64)"
|
||||
else
|
||||
pvulnstatus "$cve" UNK "offline mode: not enough information"
|
||||
pvulnstatus "$cve" UNK "no-runtime mode: not enough information"
|
||||
explain "Re-run this script with root privileges, and give it the kernel image (--kernel), the kernel configuration (--config) and the System.map file (--map) corresponding to the kernel you would like to inspect."
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -36,7 +36,7 @@ check_CVE_2018_12207_linux() {
|
||||
fi
|
||||
|
||||
pr_info_nol "* iTLB Multihit mitigation enabled and active: "
|
||||
if [ "$opt_live" = 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ]; then
|
||||
if [ -n "$ret_sys_interface_check_fullmsg" ]; then
|
||||
if echo "$ret_sys_interface_check_fullmsg" | grep -qF 'Mitigation'; then
|
||||
pstatus green YES "$ret_sys_interface_check_fullmsg"
|
||||
@@ -47,7 +47,7 @@ check_CVE_2018_12207_linux() {
|
||||
pstatus yellow NO "itlb_multihit not found in sysfs hierarchy"
|
||||
fi
|
||||
else
|
||||
pstatus blue N/A "not testable in offline mode"
|
||||
pstatus blue N/A "not testable in no-runtime mode"
|
||||
fi
|
||||
elif [ "$sys_interface_available" = 0 ]; then
|
||||
# we have no sysfs but were asked to use it only!
|
||||
@@ -63,7 +63,7 @@ check_CVE_2018_12207_linux() {
|
||||
elif [ -z "$msg" ]; then
|
||||
# if msg is empty, sysfs check didn't fill it, rely on our own test
|
||||
if [ "$opt_sysfs_only" != 1 ]; then
|
||||
if [ "$opt_live" = 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ]; then
|
||||
# if we're in live mode and $msg is empty, sysfs file is not there so kernel is too old
|
||||
pvulnstatus "$cve" VULN "Your kernel doesn't support iTLB Multihit mitigation, update it"
|
||||
else
|
||||
|
||||
31
src/vulns/CVE-2018-3615.sh.rej
Normal file
31
src/vulns/CVE-2018-3615.sh.rej
Normal file
@@ -0,0 +1,31 @@
|
||||
--- src/vulns/CVE-2018-3615.sh
|
||||
+++ src/vulns/CVE-2018-3615.sh
|
||||
@@ -8,15 +8,10 @@ check_CVE_2018_3615() {
|
||||
pr_info "\033[1;34m$cve aka '$(cve2name "$cve")'\033[0m"
|
||||
|
||||
pr_info_nol "* CPU microcode mitigates the vulnerability: "
|
||||
- if { [ "$cap_flush_cmd" = 1 ] || { [ "$g_msr_locked_down" = 1 ] && [ "$cap_l1df" = 1 ]; }; } && [ "$cap_sgx" = 1 ]; then
|
||||
- # no easy way to detect a fixed SGX but we know that
|
||||
- # microcodes that have the FLUSH_CMD MSR also have the
|
||||
- # fixed SGX (for CPUs that support it), because Intel
|
||||
- # delivered fixed microcodes for both issues at the same time
|
||||
- #
|
||||
- # if the system we're running on is locked down (no way to write MSRs),
|
||||
- # make the assumption that if the L1D flush CPUID bit is set, probably
|
||||
- # that FLUSH_CMD MSR is here too
|
||||
+ if [ "$cap_l1df" = 1 ] && [ "$cap_sgx" = 1 ]; then
|
||||
+ # the L1D flush CPUID bit indicates that the microcode supports L1D flushing,
|
||||
+ # and microcodes that have this also have the fixed SGX (for CPUs that support it),
|
||||
+ # because Intel delivered fixed microcodes for both issues at the same time
|
||||
pstatus green YES
|
||||
elif [ "$cap_sgx" = 1 ]; then
|
||||
pstatus red NO
|
||||
@@ -27,7 +22,7 @@ check_CVE_2018_3615() {
|
||||
if ! is_cpu_affected "$cve"; then
|
||||
# override status & msg in case CPU is not vulnerable after all
|
||||
pvulnstatus "$cve" OK "your CPU vendor reported your CPU model as not affected"
|
||||
- elif [ "$cap_flush_cmd" = 1 ] || { [ "$g_msr_locked_down" = 1 ] && [ "$cap_l1df" = 1 ]; }; then
|
||||
+ elif [ "$cap_l1df" = 1 ]; then
|
||||
pvulnstatus "$cve" OK "your CPU microcode mitigates the vulnerability"
|
||||
else
|
||||
pvulnstatus "$cve" VULN "your CPU supports SGX and the microcode is not up to date"
|
||||
@@ -37,7 +37,7 @@ check_CVE_2018_3620_linux() {
|
||||
fi
|
||||
|
||||
pr_info_nol "* PTE inversion enabled and active: "
|
||||
if [ "$opt_live" = 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ]; then
|
||||
if [ -n "$ret_sys_interface_check_fullmsg" ]; then
|
||||
if echo "$ret_sys_interface_check_fullmsg" | grep -q 'Mitigation: PTE Inversion'; then
|
||||
pstatus green YES
|
||||
@@ -51,7 +51,7 @@ check_CVE_2018_3620_linux() {
|
||||
pteinv_active=-1
|
||||
fi
|
||||
else
|
||||
pstatus blue N/A "not testable in offline mode"
|
||||
pstatus blue N/A "not testable in no-runtime mode"
|
||||
fi
|
||||
elif [ "$sys_interface_available" = 0 ]; then
|
||||
# we have no sysfs but were asked to use it only!
|
||||
@@ -66,7 +66,7 @@ check_CVE_2018_3620_linux() {
|
||||
# if msg is empty, sysfs check didn't fill it, rely on our own test
|
||||
if [ "$opt_sysfs_only" != 1 ]; then
|
||||
if [ "$pteinv_supported" = 1 ]; then
|
||||
if [ "$pteinv_active" = 1 ] || [ "$opt_live" != 1 ]; then
|
||||
if [ "$pteinv_active" = 1 ] || [ "$opt_runtime" != 1 ]; then
|
||||
pvulnstatus "$cve" OK "PTE inversion mitigates the vulnerability"
|
||||
else
|
||||
pvulnstatus "$cve" VULN "Your kernel supports PTE inversion but it doesn't seem to be enabled"
|
||||
|
||||
@@ -18,7 +18,7 @@ check_CVE_2018_3639_linux() {
|
||||
fi
|
||||
if [ "$opt_sysfs_only" != 1 ]; then
|
||||
pr_info_nol "* Kernel supports disabling speculative store bypass (SSB): "
|
||||
if [ "$opt_live" = 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ]; then
|
||||
if grep -Eq 'Speculation.?Store.?Bypass:' "$g_procfs/self/status" 2>/dev/null; then
|
||||
kernel_ssb="found in $g_procfs/self/status"
|
||||
pr_debug "found Speculation.Store.Bypass: in $g_procfs/self/status"
|
||||
@@ -57,7 +57,7 @@ check_CVE_2018_3639_linux() {
|
||||
fi
|
||||
|
||||
kernel_ssbd_enabled=-1
|
||||
if [ "$opt_live" = 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ]; then
|
||||
# https://elixir.bootlin.com/linux/v5.0/source/fs/proc/array.c#L340
|
||||
pr_info_nol "* SSB mitigation is enabled and active: "
|
||||
if grep -Eq 'Speculation.?Store.?Bypass:[[:space:]]+thread' "$g_procfs/self/status" 2>/dev/null; then
|
||||
@@ -106,7 +106,7 @@ check_CVE_2018_3639_linux() {
|
||||
# if msg is empty, sysfs check didn't fill it, rely on our own test
|
||||
if [ -n "$cap_ssbd" ]; then
|
||||
if [ -n "$kernel_ssb" ]; then
|
||||
if [ "$opt_live" = 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ]; then
|
||||
if [ "$kernel_ssbd_enabled" -gt 0 ]; then
|
||||
pvulnstatus "$cve" OK "your CPU and kernel both support SSBD and mitigation is enabled"
|
||||
else
|
||||
@@ -121,11 +121,21 @@ check_CVE_2018_3639_linux() {
|
||||
fi
|
||||
else
|
||||
if [ -n "$kernel_ssb" ]; then
|
||||
pvulnstatus "$cve" VULN "Your CPU doesn't support SSBD"
|
||||
explain "Your kernel is recent enough to use the CPU microcode features for mitigation, but your CPU microcode doesn't actually provide the necessary features for the kernel to use. The microcode of your CPU hence needs to be upgraded. This is usually done at boot time by your kernel (the upgrade is not persistent across reboots which is why it's done at each boot). If you're using a distro, make sure you are up to date, as microcode updates are usually shipped alongside with the distro kernel. Availability of a microcode update for you CPU model depends on your CPU vendor. You can usually find out online if a microcode update is available for your CPU by searching for your CPUID (indicated in the Hardware Check section)."
|
||||
if [ "$cpu_vendor" = ARM ] || [ "$cpu_vendor" = CAVIUM ] || [ "$cpu_vendor" = PHYTIUM ]; then
|
||||
pvulnstatus "$cve" VULN "no SSB mitigation is active on your system"
|
||||
explain "ARM CPUs mitigate SSB either through a hardware SSBS bit (ARMv8.5+ CPUs) or through firmware support for SMCCC ARCH_WORKAROUND_2. Your kernel reports SSB status but neither mechanism appears to be active. For CPUs predating ARMv8.5 (such as Cortex-A57 or Cortex-A72), check with your board or SoC vendor for a firmware update that provides SMCCC ARCH_WORKAROUND_2 support."
|
||||
else
|
||||
pvulnstatus "$cve" VULN "Your CPU doesn't support SSBD"
|
||||
explain "Your kernel is recent enough to use the CPU microcode features for mitigation, but your CPU microcode doesn't actually provide the necessary features for the kernel to use. The microcode of your CPU hence needs to be upgraded. This is usually done at boot time by your kernel (the upgrade is not persistent across reboots which is why it's done at each boot). If you're using a distro, make sure you are up to date, as microcode updates are usually shipped alongside with the distro kernel. Availability of a microcode update for you CPU model depends on your CPU vendor. You can usually find out online if a microcode update is available for your CPU by searching for your CPUID (indicated in the Hardware Check section)."
|
||||
fi
|
||||
else
|
||||
pvulnstatus "$cve" VULN "Neither your CPU nor your kernel support SSBD"
|
||||
explain "Both your CPU microcode and your kernel are lacking support for mitigation. If you're using a distro kernel, upgrade your distro to get the latest kernel available. Otherwise, recompile the kernel from recent-enough sources. The microcode of your CPU also needs to be upgraded. This is usually done at boot time by your kernel (the upgrade is not persistent across reboots which is why it's done at each boot). If you're using a distro, make sure you are up to date, as microcode updates are usually shipped alongside with the distro kernel. Availability of a microcode update for you CPU model depends on your CPU vendor. You can usually find out online if a microcode update is available for your CPU by searching for your CPUID (indicated in the Hardware Check section)."
|
||||
if [ "$cpu_vendor" = ARM ] || [ "$cpu_vendor" = CAVIUM ] || [ "$cpu_vendor" = PHYTIUM ]; then
|
||||
pvulnstatus "$cve" VULN "your kernel and firmware do not support SSB mitigation"
|
||||
explain "ARM SSB mitigation requires kernel support (CONFIG_ARM64_SSBD) combined with either a hardware SSBS bit (ARMv8.5+ CPUs) or firmware support for SMCCC ARCH_WORKAROUND_2. Ensure you are running a recent kernel compiled with CONFIG_ARM64_SSBD. For CPUs predating ARMv8.5, also check with your board or SoC vendor for a firmware update providing SMCCC ARCH_WORKAROUND_2 support."
|
||||
else
|
||||
pvulnstatus "$cve" VULN "Neither your CPU nor your kernel support SSBD"
|
||||
explain "Both your CPU microcode and your kernel are lacking support for mitigation. If you're using a distro kernel, upgrade your distro to get the latest kernel available. Otherwise, recompile the kernel from recent-enough sources. The microcode of your CPU also needs to be upgraded. This is usually done at boot time by your kernel (the upgrade is not persistent across reboots which is why it's done at each boot). If you're using a distro, make sure you are up to date, as microcode updates are usually shipped alongside with the distro kernel. Availability of a microcode update for you CPU model depends on your CPU vendor. You can usually find out online if a microcode update is available for your CPU by searching for your CPUID (indicated in the Hardware Check section)."
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
else
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# CVE-2018-3640, Variant 3a, Rogue System Register Read
|
||||
|
||||
check_CVE_2018_3640() {
|
||||
local status sys_interface_available msg cve
|
||||
local status sys_interface_available msg cve is_arm64_kernel arm_v3a_mitigation
|
||||
cve='CVE-2018-3640'
|
||||
pr_info "\033[1;34m$cve aka '$(cve2name "$cve")'\033[0m"
|
||||
|
||||
@@ -11,22 +11,66 @@ check_CVE_2018_3640() {
|
||||
sys_interface_available=0
|
||||
msg=''
|
||||
|
||||
pr_info_nol "* CPU microcode mitigates the vulnerability: "
|
||||
if [ -n "$cap_ssbd" ]; then
|
||||
# microcodes that ship with SSBD are known to also fix affected_variant3a
|
||||
# there is no specific cpuid bit as far as we know
|
||||
pstatus green YES
|
||||
else
|
||||
pstatus yellow NO
|
||||
# Detect whether the target kernel is ARM64, for both live and no-runtime modes.
|
||||
# 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
|
||||
is_arm64_kernel=1
|
||||
elif [ -n "$opt_map" ] && grep -q 'arm64_sys_' "$opt_map" 2>/dev/null; then
|
||||
is_arm64_kernel=1
|
||||
elif [ -n "$g_kernel" ] && grep -q 'arm64_sys_' "$g_kernel" 2>/dev/null; then
|
||||
is_arm64_kernel=1
|
||||
elif [ -n "$opt_config" ] && grep -qw 'CONFIG_ARM64=y' "$opt_config" 2>/dev/null; then
|
||||
is_arm64_kernel=1
|
||||
fi
|
||||
|
||||
if ! is_cpu_affected "$cve"; then
|
||||
# override status & msg in case CPU is not vulnerable after all
|
||||
pvulnstatus "$cve" OK "your CPU vendor reported your CPU model as not affected"
|
||||
elif [ -n "$cap_ssbd" ]; then
|
||||
pvulnstatus "$cve" OK "your CPU microcode mitigates the vulnerability"
|
||||
if [ "$is_arm64_kernel" = 1 ]; then
|
||||
# ARM64: mitigation is via an EL2 indirect trampoline (spectre_v3a_enable_mitigation),
|
||||
# applied automatically at boot for affected CPUs (Cortex-A57, Cortex-A72).
|
||||
# No microcode update is involved.
|
||||
arm_v3a_mitigation=''
|
||||
if [ -n "$opt_map" ] && grep -qw spectre_v3a_enable_mitigation "$opt_map" 2>/dev/null; then
|
||||
arm_v3a_mitigation="found spectre_v3a_enable_mitigation in System.map"
|
||||
fi
|
||||
if [ -z "$arm_v3a_mitigation" ] && [ -n "$g_kernel" ]; then
|
||||
if "${opt_arch_prefix}strings" "$g_kernel" 2>/dev/null | grep -qw spectre_v3a_enable_mitigation; then
|
||||
arm_v3a_mitigation="found spectre_v3a_enable_mitigation in kernel image"
|
||||
fi
|
||||
fi
|
||||
|
||||
pr_info_nol "* Kernel mitigates the vulnerability via EL2 hardening: "
|
||||
if [ -n "$arm_v3a_mitigation" ]; then
|
||||
pstatus green YES "$arm_v3a_mitigation"
|
||||
else
|
||||
pstatus yellow NO
|
||||
fi
|
||||
|
||||
if ! is_cpu_affected "$cve"; then
|
||||
pvulnstatus "$cve" OK "your CPU vendor reported your CPU model as not affected"
|
||||
elif [ -n "$arm_v3a_mitigation" ]; then
|
||||
pvulnstatus "$cve" OK "your kernel mitigates the vulnerability via EL2 vector hardening"
|
||||
else
|
||||
pvulnstatus "$cve" VULN "your kernel does not include the EL2 vector hardening mitigation"
|
||||
explain "ARM64 Spectre v3a mitigation is provided by the kernel using an indirect trampoline for EL2 (hypervisor) vectors (spectre_v3a_enable_mitigation). Ensure you are running a recent kernel. If you're using a distro kernel, upgrading your distro should provide a kernel with this mitigation included."
|
||||
fi
|
||||
else
|
||||
pvulnstatus "$cve" VULN "an up-to-date CPU microcode is needed to mitigate this vulnerability"
|
||||
explain "The microcode of your CPU needs to be upgraded to mitigate this vulnerability. This is usually done at boot time by your kernel (the upgrade is not persistent across reboots which is why it's done at each boot). If you're using a distro, make sure you are up to date, as microcode updates are usually shipped alongside with the distro kernel. Availability of a microcode update for you CPU model depends on your CPU vendor. You can usually find out online if a microcode update is available for your CPU by searching for your CPUID (indicated in the Hardware Check section). The microcode update is enough, there is no additional OS, kernel or software change needed."
|
||||
# x86: microcodes that ship with SSBD are known to also fix variant 3a;
|
||||
# there is no specific CPUID bit for variant 3a as far as we know.
|
||||
pr_info_nol "* CPU microcode mitigates the vulnerability: "
|
||||
if [ -n "$cap_ssbd" ]; then
|
||||
pstatus green YES
|
||||
else
|
||||
pstatus yellow NO
|
||||
fi
|
||||
|
||||
if ! is_cpu_affected "$cve"; then
|
||||
pvulnstatus "$cve" OK "your CPU vendor reported your CPU model as not affected"
|
||||
elif [ -n "$cap_ssbd" ]; then
|
||||
pvulnstatus "$cve" OK "your CPU microcode mitigates the vulnerability"
|
||||
else
|
||||
pvulnstatus "$cve" VULN "an up-to-date CPU microcode is needed to mitigate this vulnerability"
|
||||
explain "The microcode of your CPU needs to be upgraded to mitigate this vulnerability. This is usually done at boot time by your kernel (the upgrade is not persistent across reboots which is why it's done at each boot). If you're using a distro, make sure you are up to date, as microcode updates are usually shipped alongside with the distro kernel. Availability of a microcode update for you CPU model depends on your CPU vendor. You can usually find out online if a microcode update is available for your CPU by searching for your CPUID (indicated in the Hardware Check section). The microcode update is enough, there is no additional OS, kernel or software change needed."
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ check_CVE_2018_3646_linux() {
|
||||
pr_info "* Mitigation 1 (KVM)"
|
||||
pr_info_nol " * EPT is disabled: "
|
||||
ept_disabled=-1
|
||||
if [ "$opt_live" = 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ]; then
|
||||
if ! [ -r "$SYS_MODULE_BASE/kvm_intel/parameters/ept" ]; then
|
||||
pstatus blue N/A "the kvm_intel module is not loaded"
|
||||
elif [ "$(cat "$SYS_MODULE_BASE/kvm_intel/parameters/ept")" = N ]; then
|
||||
@@ -79,12 +79,12 @@ check_CVE_2018_3646_linux() {
|
||||
pstatus yellow NO
|
||||
fi
|
||||
else
|
||||
pstatus blue N/A "not testable in offline mode"
|
||||
pstatus blue N/A "not testable in no-runtime mode"
|
||||
fi
|
||||
|
||||
pr_info "* Mitigation 2"
|
||||
pr_info_nol " * L1D flush is supported by kernel: "
|
||||
if [ "$opt_live" = 1 ] && grep -qw flush_l1d "$g_procfs/cpuinfo"; then
|
||||
if [ "$opt_runtime" = 1 ] && grep -qw flush_l1d "$g_procfs/cpuinfo"; then
|
||||
l1d_kernel="found flush_l1d in $g_procfs/cpuinfo"
|
||||
fi
|
||||
if [ -z "$l1d_kernel" ]; then
|
||||
@@ -106,7 +106,7 @@ check_CVE_2018_3646_linux() {
|
||||
fi
|
||||
|
||||
pr_info_nol " * L1D flush enabled: "
|
||||
if [ "$opt_live" = 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ]; then
|
||||
if [ -n "$ret_sys_interface_check_fullmsg" ]; then
|
||||
# vanilla: VMX: $l1dstatus, SMT $smtstatus
|
||||
# Red Hat: VMX: SMT $smtstatus, L1D $l1dstatus
|
||||
@@ -152,18 +152,18 @@ check_CVE_2018_3646_linux() {
|
||||
fi
|
||||
else
|
||||
l1d_mode=-1
|
||||
pstatus blue N/A "not testable in offline mode"
|
||||
pstatus blue N/A "not testable in no-runtime mode"
|
||||
fi
|
||||
|
||||
pr_info_nol " * Hardware-backed L1D flush supported: "
|
||||
if [ "$opt_live" = 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ]; then
|
||||
if grep -qw flush_l1d "$g_procfs/cpuinfo" || [ -n "$l1d_xen_hardware" ]; then
|
||||
pstatus green YES "performance impact of the mitigation will be greatly reduced"
|
||||
else
|
||||
pstatus blue NO "flush will be done in software, this is slower"
|
||||
fi
|
||||
else
|
||||
pstatus blue N/A "not testable in offline mode"
|
||||
pstatus blue N/A "not testable in no-runtime mode"
|
||||
fi
|
||||
|
||||
pr_info_nol " * Hyper-Threading (SMT) is enabled: "
|
||||
|
||||
@@ -33,7 +33,7 @@ check_CVE_2019_11135_linux() {
|
||||
fi
|
||||
|
||||
pr_info_nol "* TAA mitigation enabled and active: "
|
||||
if [ "$opt_live" = 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ]; then
|
||||
if [ -n "$ret_sys_interface_check_fullmsg" ]; then
|
||||
if echo "$ret_sys_interface_check_fullmsg" | grep -qE '^Mitigation'; then
|
||||
pstatus green YES "$ret_sys_interface_check_fullmsg"
|
||||
@@ -44,7 +44,7 @@ check_CVE_2019_11135_linux() {
|
||||
pstatus yellow NO "tsx_async_abort not found in sysfs hierarchy"
|
||||
fi
|
||||
else
|
||||
pstatus blue N/A "not testable in offline mode"
|
||||
pstatus blue N/A "not testable in no-runtime mode"
|
||||
fi
|
||||
elif [ "$sys_interface_available" = 0 ]; then
|
||||
# we have no sysfs but were asked to use it only!
|
||||
@@ -57,7 +57,7 @@ check_CVE_2019_11135_linux() {
|
||||
pvulnstatus "$cve" OK "your CPU vendor reported your CPU model as not affected"
|
||||
elif [ -z "$msg" ]; then
|
||||
# if msg is empty, sysfs check didn't fill it, rely on our own test
|
||||
if [ "$opt_live" = 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ]; then
|
||||
# if we're in live mode and $msg is empty, sysfs file is not there so kernel is too old
|
||||
pvulnstatus "$cve" VULN "Your kernel doesn't support TAA mitigation, update it"
|
||||
else
|
||||
|
||||
@@ -32,7 +32,7 @@ check_CVE_2020_0543_linux() {
|
||||
pstatus yellow NO
|
||||
fi
|
||||
pr_info_nol "* SRBDS mitigation control is enabled and active: "
|
||||
if [ "$opt_live" = 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ]; then
|
||||
if [ -n "$ret_sys_interface_check_fullmsg" ]; then
|
||||
if echo "$ret_sys_interface_check_fullmsg" | grep -qE '^Mitigation'; then
|
||||
pstatus green YES "$ret_sys_interface_check_fullmsg"
|
||||
@@ -43,7 +43,7 @@ check_CVE_2020_0543_linux() {
|
||||
pstatus yellow NO "SRBDS not found in sysfs hierarchy"
|
||||
fi
|
||||
else
|
||||
pstatus blue N/A "not testable in offline mode"
|
||||
pstatus blue N/A "not testable in no-runtime mode"
|
||||
fi
|
||||
elif [ "$sys_interface_available" = 0 ]; then
|
||||
# we have no sysfs but were asked to use it only!
|
||||
@@ -61,7 +61,7 @@ check_CVE_2020_0543_linux() {
|
||||
# SRBDS mitigation control is enabled
|
||||
if [ -z "$msg" ]; then
|
||||
# if msg is empty, sysfs check didn't fill it, rely on our own test
|
||||
if [ "$opt_live" = 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ]; then
|
||||
# if we're in live mode and $msg is empty, sysfs file is not there so kernel is too old
|
||||
pvulnstatus "$cve" OK "Your microcode is up to date for SRBDS mitigation control. The kernel needs to be updated"
|
||||
fi
|
||||
@@ -75,7 +75,7 @@ check_CVE_2020_0543_linux() {
|
||||
elif [ "$cap_srbds_on" = 0 ]; then
|
||||
# SRBDS mitigation control is disabled
|
||||
if [ -z "$msg" ]; then
|
||||
if [ "$opt_live" = 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ]; then
|
||||
# if we're in live mode and $msg is empty, sysfs file is not there so kernel is too old
|
||||
pvulnstatus "$cve" VULN "Your microcode is up to date for SRBDS mitigation control. The kernel needs to be updated. Mitigation is disabled"
|
||||
fi
|
||||
|
||||
@@ -174,14 +174,14 @@ check_CVE_2022_29900_linux() {
|
||||
# Zen/Zen+/Zen2: check IBPB microcode support and SMT
|
||||
if [ "$cpu_family" = $((0x17)) ]; then
|
||||
pr_info_nol "* CPU supports IBPB: "
|
||||
if [ "$opt_live" = 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ]; then
|
||||
if [ -n "$cap_ibpb" ]; then
|
||||
pstatus green YES "$cap_ibpb"
|
||||
else
|
||||
pstatus yellow NO
|
||||
fi
|
||||
else
|
||||
pstatus blue N/A "not testable in offline mode"
|
||||
pstatus blue N/A "not testable in no-runtime mode"
|
||||
fi
|
||||
|
||||
pr_info_nol "* Hyper-Threading (SMT) is enabled: "
|
||||
@@ -217,7 +217,7 @@ check_CVE_2022_29900_linux() {
|
||||
"doesn't fully protect cross-thread speculation."
|
||||
elif [ -z "$kernel_unret" ] && [ -z "$kernel_ibpb_entry" ]; then
|
||||
pvulnstatus "$cve" VULN "Your kernel doesn't have either UNRET_ENTRY or IBPB_ENTRY compiled-in"
|
||||
elif [ "$smt_enabled" = 0 ] && [ -z "$cap_ibpb" ] && [ "$opt_live" = 1 ]; then
|
||||
elif [ "$smt_enabled" = 0 ] && [ -z "$cap_ibpb" ] && [ "$opt_runtime" = 1 ]; then
|
||||
pvulnstatus "$cve" VULN "SMT is enabled and your microcode doesn't support IBPB"
|
||||
explain "Update your CPU microcode to get IBPB support, or disable SMT by adding\n" \
|
||||
"\`nosmt\` to your kernel command line."
|
||||
|
||||
@@ -84,7 +84,7 @@ check_CVE_2022_29901_linux() {
|
||||
fi
|
||||
|
||||
pr_info_nol "* CPU supports Enhanced IBRS (IBRS_ALL): "
|
||||
if [ "$opt_live" = 1 ] || [ "$cap_ibrs_all" != -1 ]; then
|
||||
if [ "$opt_runtime" = 1 ] || [ "$cap_ibrs_all" != -1 ]; then
|
||||
if [ "$cap_ibrs_all" = 1 ]; then
|
||||
pstatus green YES
|
||||
elif [ "$cap_ibrs_all" = 0 ]; then
|
||||
@@ -93,11 +93,11 @@ check_CVE_2022_29901_linux() {
|
||||
pstatus yellow UNKNOWN
|
||||
fi
|
||||
else
|
||||
pstatus blue N/A "not testable in offline mode"
|
||||
pstatus blue N/A "not testable in no-runtime mode"
|
||||
fi
|
||||
|
||||
pr_info_nol "* CPU has RSB Alternate Behavior (RSBA): "
|
||||
if [ "$opt_live" = 1 ] || [ "$cap_rsba" != -1 ]; then
|
||||
if [ "$opt_runtime" = 1 ] || [ "$cap_rsba" != -1 ]; then
|
||||
if [ "$cap_rsba" = 1 ]; then
|
||||
pstatus yellow YES "this CPU is affected by RSB underflow"
|
||||
elif [ "$cap_rsba" = 0 ]; then
|
||||
@@ -106,7 +106,7 @@ check_CVE_2022_29901_linux() {
|
||||
pstatus yellow UNKNOWN
|
||||
fi
|
||||
else
|
||||
pstatus blue N/A "not testable in offline mode"
|
||||
pstatus blue N/A "not testable in no-runtime mode"
|
||||
fi
|
||||
|
||||
elif [ "$sys_interface_available" = 0 ]; then
|
||||
|
||||
@@ -145,7 +145,7 @@ check_CVE_2022_40982_linux() {
|
||||
if [ -n "$kernel_gds" ]; then
|
||||
pr_info_nol "* Kernel has disabled AVX as a mitigation: "
|
||||
|
||||
if [ "$opt_live" = 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ]; then
|
||||
# Check dmesg message to see whether AVX has been disabled
|
||||
dmesg_grep 'Microcode update needed! Disabling AVX as mitigation'
|
||||
dmesgret=$?
|
||||
@@ -172,7 +172,7 @@ check_CVE_2022_40982_linux() {
|
||||
pstatus yellow NO "AVX support is enabled"
|
||||
fi
|
||||
else
|
||||
pstatus blue N/A "not testable in offline mode"
|
||||
pstatus blue N/A "not testable in no-runtime mode"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ check_CVE_2023_20588_linux() {
|
||||
pr_info_nol "* DIV0 mitigation enabled and active: "
|
||||
cpuinfo_div0=''
|
||||
dmesg_div0=''
|
||||
if [ "$opt_live" = 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ]; then
|
||||
if [ -e "$g_procfs/cpuinfo" ] && grep -qw 'div0' "$g_procfs/cpuinfo" 2>/dev/null; then
|
||||
cpuinfo_div0=1
|
||||
pstatus green YES "div0 found in $g_procfs/cpuinfo bug flags"
|
||||
@@ -119,11 +119,19 @@ check_CVE_2023_20588_linux() {
|
||||
fi
|
||||
fi
|
||||
else
|
||||
pstatus blue N/A "not testable in offline mode"
|
||||
pstatus blue N/A "not testable in no-runtime mode"
|
||||
fi
|
||||
|
||||
pr_info_nol "* SMT (Simultaneous Multi-Threading) status: "
|
||||
pr_info_nol "* SMT (Simultaneous Multi-Threading) is enabled: "
|
||||
is_cpu_smt_enabled
|
||||
smt_ret=$?
|
||||
if [ "$smt_ret" = 0 ]; then
|
||||
pstatus yellow YES
|
||||
elif [ "$smt_ret" = 2 ]; then
|
||||
pstatus yellow UNKNOWN
|
||||
else
|
||||
pstatus green NO
|
||||
fi
|
||||
elif [ "$sys_interface_available" = 0 ]; then
|
||||
msg="/sys vulnerability interface use forced, but it's not available!"
|
||||
status=UNK
|
||||
@@ -133,7 +141,7 @@ check_CVE_2023_20588_linux() {
|
||||
pvulnstatus "$cve" OK "your CPU vendor reported your CPU model as not affected"
|
||||
elif [ -z "$msg" ]; then
|
||||
if [ "$opt_sysfs_only" != 1 ]; then
|
||||
if [ "$opt_live" = 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ]; then
|
||||
# live mode: cpuinfo div0 flag is the strongest proof the mitigation is active
|
||||
if [ "$cpuinfo_div0" = 1 ] || [ "$dmesg_div0" = 1 ]; then
|
||||
_cve_2023_20588_pvulnstatus_smt
|
||||
@@ -145,7 +153,7 @@ check_CVE_2023_20588_linux() {
|
||||
_cve_2023_20588_pvulnstatus_no_kernel
|
||||
fi
|
||||
else
|
||||
# offline mode: only kernel image / System.map evidence is available
|
||||
# no-runtime mode: only kernel image / System.map evidence is available
|
||||
if [ -n "$kernel_mitigated" ]; then
|
||||
pvulnstatus "$cve" OK "Mitigation: amd_clear_divider found in kernel image"
|
||||
else
|
||||
|
||||
@@ -28,7 +28,7 @@ check_CVE_2023_20593_linux() {
|
||||
pstatus yellow NO
|
||||
fi
|
||||
pr_info_nol "* Zenbleed kernel mitigation enabled and active: "
|
||||
if [ "$opt_live" = 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ]; then
|
||||
# read the DE_CFG MSR, we want to check the 9th bit
|
||||
# don't do it on non-Zen2 AMD CPUs or later, aka Family 17h,
|
||||
# as the behavior could be unknown on others
|
||||
@@ -53,7 +53,7 @@ check_CVE_2023_20593_linux() {
|
||||
pstatus blue N/A "CPU is incompatible"
|
||||
fi
|
||||
else
|
||||
pstatus blue N/A "not testable in offline mode"
|
||||
pstatus blue N/A "not testable in no-runtime mode"
|
||||
fi
|
||||
|
||||
pr_info_nol "* Zenbleed mitigation is supported by CPU microcode: "
|
||||
@@ -82,7 +82,7 @@ check_CVE_2023_20593_linux() {
|
||||
elif [ -z "$msg" ]; then
|
||||
# if msg is empty, sysfs check didn't fill it, rely on our own test
|
||||
zenbleed_print_vuln=0
|
||||
if [ "$opt_live" = 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ]; then
|
||||
if [ "$fp_backup_fix" = 1 ] && [ "$ucode_zenbleed" = 1 ]; then
|
||||
# this should never happen, but if it does, it's interesting to know
|
||||
pvulnstatus "$cve" OK "Both your CPU microcode and kernel are mitigating Zenbleed"
|
||||
|
||||
@@ -106,7 +106,7 @@ check_CVE_2023_28746_linux() {
|
||||
pstatus yellow NO
|
||||
fi
|
||||
|
||||
if [ "$opt_live" = 1 ] && [ "$sys_interface_available" = 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ] && [ "$sys_interface_available" = 1 ]; then
|
||||
pr_info_nol "* RFDS mitigation is enabled and active: "
|
||||
if echo "$ret_sys_interface_check_fullmsg" | grep -qi '^Mitigation'; then
|
||||
rfds_mitigated=1
|
||||
@@ -129,7 +129,7 @@ check_CVE_2023_28746_linux() {
|
||||
if [ "$opt_sysfs_only" != 1 ]; then
|
||||
if [ "$cap_rfds_clear" = 1 ]; then
|
||||
if [ -n "$kernel_rfds" ]; then
|
||||
if [ "$opt_live" = 1 ]; then
|
||||
if [ "$opt_runtime" = 1 ]; then
|
||||
if [ "$rfds_mitigated" = 1 ]; then
|
||||
pvulnstatus "$cve" OK "Your microcode and kernel are both up to date for this mitigation, and mitigation is enabled"
|
||||
else
|
||||
|
||||
Reference in New Issue
Block a user