spectre-meltdown-checker: Auditing CPU Vulnerability Mitigations on Linux
Why this tool still matters in 2026
Spectre and Meltdown landed in January 2018, and the steady drip of related transient-execution and side-channel disclosures since then — Foreshadow, ZombieLoad, MDS, SRBDS, RIDL, Fallout, Retbleed, Downfall, Inception, GDS, the long Hertzbleed family, and more — has turned CPU vulnerability management into a permanent operational concern rather than a one-off patch event. Every kernel release adds new entries to /sys/devices/system/cpu/vulnerabilities/, every microcode update changes which mitigations are available, and every firmware update on the BIOS side changes whether those mitigations are actually engaged.
Knowing that your kernel reports Mitigation: PTI for Meltdown is not the same as knowing whether your CPU is genuinely safe. The kernel reports what it did. It does not report whether the underlying microcode supports the feature it relied on, whether the BIOS exposed the relevant CPUID bits, whether the mitigation chosen is the best one available for that part, or whether a vulnerability disclosed last week is even being checked. spectre-meltdown-checker exists to bridge that gap: it inspects the CPU, the firmware, the kernel, and the running configuration together and gives a unified picture per CVE.
This post is for engineers who run Linux in production and want to understand what the tool is actually doing, where its blind spots are, what the alternatives look like, and when each one is the right answer.
What the tool actually is
spectre-meltdown-checker is a single Bash script — spectre-meltdown-checker.sh — maintained by Stéphane Lesimple at github.com/speed47/spectre-meltdown-checker. It is shipped in the official repos of every mainstream distribution (apt install spectre-meltdown-checker on Debian/Ubuntu, dnf install spectre-meltdown-checker on Fedora/RHEL derivatives via EPEL, pacman -S spectre-meltdown-checker on Arch). The script is GPLv3 and is around 8000 lines of Bash with no compiled components.
The packaged version on most distributions lags upstream by weeks or months. For the latest CVE coverage you want the upstream version:
git clone https://github.com/speed47/spectre-meltdown-checker.git
cd spectre-meltdown-checker
sudo ./spectre-meltdown-checker.sh
The script needs root to read /dev/cpu/*/msr for the CPUID and MSR introspection, to access kernel images on disk, and on some distributions to load the cpuid and msr modules. You can run it without root, but its output then includes a long list of “this check is not possible without root” lines and the result is significantly less authoritative.
How it actually works
The script’s logic for each CVE follows a consistent pattern: check whether the hardware is vulnerable, check whether a mitigation is supported by the kernel and the microcode, and check whether the mitigation is actually engaged. It does this through four data sources in roughly this order.
First, the CPU itself. The script reads /proc/cpuinfo to get vendor, family, model, stepping, and microcode revision. It then loads cpuid and msr if they are not already present, and reads specific MSRs that report architectural features and mitigations — IA32_ARCH_CAPABILITIES (MSR 0x10A) for Intel, MSR 0xC0011029 and friends for AMD. These MSRs are how a CPU tells the operating system “I am not vulnerable to this CVE” or “I support this mitigation in microcode, here is the bit you can set”. The script knows which bits matter for which CVE and reads them directly. This is the layer that catches the case where a CPU has been silently fixed in hardware on a newer stepping, even though the kernel still reports a software mitigation as engaged because it does not yet know better.
Second, the kernel sysfs interface. From kernel 4.15 onward, the kernel exposes /sys/devices/system/cpu/vulnerabilities/ with one file per vulnerability — meltdown, spectre_v1, spectre_v2, spec_store_bypass, l1tf, mds, tsx_async_abort, srbds, mmio_stale_data, retbleed, gather_data_sampling, reg_file_data_sampling, spec_rstack_overflow, etc. Each file contains a one-line status: Not affected, Vulnerable, or Mitigation: ... describing what is engaged. The script reads these and uses them as the kernel’s own statement of what it is doing.
Third, the kernel image on disk and in memory. This is what makes the tool more interesting than a one-line cat. For each mitigation, the script tries to determine whether the kernel was compiled with the relevant CONFIG_* option, whether the relevant code paths exist in the running kernel, and in many cases whether the mitigation is in the on-position via boot parameters or runtime sysfs writes. It does this by:
- Parsing
/boot/config-$(uname -r)if available, falling back to/proc/config.gz. - Pattern-matching against the running kernel image (
/boot/vmlinuz-*or extracted from/proc/kallsyms) for the presence of mitigation symbols likespectre_v2_select_mitigation,mds_user_clear,__x86_indirect_thunk_*,srbds_mitigation. - Reading
/proc/cmdlineto see what mitigation flags were passed at boot —mitigations=off,nopti,spectre_v2=off, and so on. - On some kernels, reading specific debugfs files for retpoline state and IBPB engagement.
Fourth, microcode revision tables. The script ships with embedded tables of “minimum microcode revision required to mitigate this CVE” per Intel and AMD CPU model, drawn from vendor advisories. After it identifies the running CPU and its loaded microcode revision, it compares against these tables and warns explicitly when a host is running below the required level — a check that none of the kernel-supplied interfaces perform. This is the single most valuable thing the tool does that nothing else does as cleanly.
The output for each CVE then summarises: is this hardware vulnerable, is microcode at the right level, has the kernel engaged the right mitigation, and is the mitigation actually doing something at runtime. The colours (red/orange/green) are a fast visual; the prose under each finding is where the real information is.
Reading the output
A representative run on a recent Intel Xeon looks something like:
CVE-2017-5754 aka 'Meltdown, Variant 3, rogue data cache load'
* Mitigated according to the /sys interface: YES (Mitigation: PTI)
* Kernel supports Page Table Isolation (PTI): YES
* PTI enabled and active: YES
* Reduced performance impact of PTI: YES (CPU supports PCID, performance impact will be reduced)
> STATUS: NOT VULNERABLE (Mitigation: PTI)
CVE-2018-3640 aka 'Variant 3a, rogue system register read'
* CPU microcode mitigates the vulnerability: YES
> STATUS: NOT VULNERABLE (your CPU microcode mitigates the vulnerability)
CVE-2022-29900 aka 'RETBleed, arbitrary speculative code execution with return instructions'
* Mitigation 1
* Kernel supports IBRS: YES
* IBRS enabled and active: YES (for kernel space)
* Mitigation 2
* Kernel supports RETBleed mitigation: YES (Mitigation: Enhanced IBRS)
> STATUS: NOT VULNERABLE (Mitigation: Enhanced IBRS)
Worth noticing:
Mitigated according to the /sys interfaceis just the script restating what the kernel reported. That alone is not enough to call something safe — it is one of four signals.Kernel supports Xis from the on-disk config and the kernel image probe. This is what tells you whether the running kernel even has the code for the mitigation.Microcode mitigatesis the MSR read. This is the layer that is invisible to/sys.STATUSis the script’s overall verdict, after considering all four data sources.
You will sometimes see a VULNERABLE verdict alongside Mitigated according to the /sys interface: YES. That is exactly the case the tool is designed to surface: the kernel believes it is safe, but the hardware-level checks say otherwise. A common cause is a CPU that needs a microcode update for a mitigation to be effective, with the kernel having engaged the software fallback but at a performance cost the operator did not expect.
The non-zero exit codes are also useful. The script returns 0 when nothing is vulnerable, 2 when at least one CVE is VULNERABLE, and 3 when at least one is UNKNOWN. That makes it easy to wire into Nagios, Prometheus textfile collectors, Ansible reporting, or a simple CI check on a golden image.
Useful invocations
The defaults are sensible. A few flags worth knowing:
# JSON output, suitable for parsing
sudo ./spectre-meltdown-checker.sh --batch json > /tmp/cpu-vulns.json
# Nagios output, a single line plus performance data
sudo ./spectre-meltdown-checker.sh --batch nrpe
# Short output, just per-CVE verdicts, no explanatory prose
sudo ./spectre-meltdown-checker.sh --no-explanations
# Check a non-running kernel — useful for golden-image validation
sudo ./spectre-meltdown-checker.sh --kernel /boot/vmlinuz-6.5.0-15-generic --config /boot/config-6.5.0-15-generic
# Hardware-only — what does this CPU support, ignoring kernel state
sudo ./spectre-meltdown-checker.sh --hw-only
# Skip the upstream version check (helpful in air-gapped environments)
sudo ./spectre-meltdown-checker.sh --no-version-check
# Specific CVE only
sudo ./spectre-meltdown-checker.sh --variant retbleed
The --batch json output is what you want for any kind of scale. The structure is per-CVE and stable across versions:
{
"CVE-2017-5754": {
"name": "Meltdown",
"vulnerable": false,
"status": "OK",
"mitigation": "PTI"
},
...
}
A practical Ansible playbook that runs this across an estate is short:
- name: Run spectre-meltdown-checker
ansible.builtin.command: /usr/local/sbin/spectre-meltdown-checker.sh --batch json --no-version-check
register: smc
changed_when: false
failed_when: false
- name: Parse results
ansible.builtin.set_fact:
smc_findings: "{{ smc.stdout | from_json }}"
- name: Fail if any vulnerable
ansible.builtin.fail:
msg: "Host is vulnerable to {{ item.key }}"
loop: "{{ smc_findings | dict2items }}"
when: item.value.vulnerable | default(false)
The failed_when: false matters because the script returns 2 on vulnerable hosts and Ansible would otherwise abort before you got to inspect the JSON.
The alternatives, and where they fall short
There are at least four other ways to get at this information. Each has a place.
The first is the kernel sysfs interface itself.
grep . /sys/devices/system/cpu/vulnerabilities/*
This is the lightest possible check and it requires nothing installed. It is correct as far as it goes — what the kernel reports here is the kernel’s authoritative statement on what mitigation it engaged. The blind spots are that it does not tell you whether your microcode is at the required level, it does not tell you whether the CPU is silently fixed in hardware on a stepping the kernel does not recognise, it does not check whether your kernel was compiled with the relevant config options on a kernel where they are tunable, and it does not cover CVEs whose mitigations are not yet exposed via sysfs (recent disclosures sometimes lag here by a few kernel versions). For a fast scan or a one-line health probe, sysfs is fine. For an audit, it is incomplete.
The second is lscpu. Modern util-linux has had a “Vulnerabilities” section in the lscpu output since around 2018:
$ lscpu | grep -A30 Vulnerab
Vulnerabilities:
Gather data sampling: Mitigation; Microcode
Itlb multihit: Not affected
L1tf: Not affected
Mds: Not affected
Meltdown: Not affected
Mmio stale data: Not affected
Reg file data sampling: Not affected
Retbleed: Not affected
Spec rstack overflow: Not affected
Spec store bypass: Mitigation; Speculative Store Bypass disabled via prctl
Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer sanitization
Spectre v2: Mitigation; Enhanced / Automatic IBRS, IBPB conditional, RSB filling, PBRSB-eIBRS SW sequence
Srbds: Not affected
Tsx async abort: Not affected
This is lscpu formatting the same /sys/devices/system/cpu/vulnerabilities/ data the kernel exposes. It is a strict subset of what sysfs gives you, but in a more readable form. It is great as a quick visual on an unfamiliar box. It carries the same blind spots as raw sysfs because it is the same data.
The third is dmesg | grep -i -e spectre -e meltdown -e mitigation. The kernel logs the mitigation it picked at boot, and rummaging in the ring buffer is sometimes the fastest way to see what the kernel decided to do and why. It is especially useful when investigating a non-obvious mitigation choice — the boot log will say something like Spectre V2: Enhanced IBRS + Retpolines and explain whether it fell back from a preferred mitigation and why. The downside is that this is unstructured prose, the messages change between kernel versions, and dmesg may have rolled over on a long-running box.
The fourth is the vendor tools. Intel ships intel-cpu-tools and mcedaemon, AMD has its own collateral, and large fleets often deploy osquery — which exposes kernel_info and system_info tables but does not natively join CPU model to CVE applicability. None of these is a like-for-like replacement for spectre-meltdown-checker. The closest thing in spirit is the per-vendor microcode advisory PDFs published by Intel and AMD, which list “minimum microcode revision required to mitigate CVE-X on family/model Y”. spectre-meltdown-checker essentially encodes those advisories into checked code, which is why it is more useful than any of the vendor tools individually.
A fifth alternative worth mentioning is paid platforms — Wiz, Tenable, Qualys, Rapid7 all have CPU-vulnerability checks in their fleet scanners — but these typically use exactly the same data sources spectre-meltdown-checker uses, plus a CMDB join for fleet-wide visibility. If you already pay for one of them, you have the same check at fleet scale; if you are running open-source tooling, this script is the closest free equivalent and the upstream check it is doing is broadly the same.
When to reach for which
The decision is essentially “how authoritative does the answer need to be, and how widely does it need to scale?”
For a quick “is this host probably patched?” check on an unfamiliar box during incident response, lscpu or grep . /sys/devices/system/cpu/vulnerabilities/* is the fastest answer and gives you the kernel’s view in two seconds. Don’t use spectre-meltdown-checker for this — it is slow to run and produces far more output than you need.
For a real audit — confirming that a host is genuinely safe before signing off compliance, or before declaring a fleet patched after a CVE disclosure — spectre-meltdown-checker is the right tool, because it cross-references microcode levels and hardware fix flags against what the kernel reports. The kernel-only checks lie by omission for any case where microcode is the gap.
For continuous monitoring of an estate, run spectre-meltdown-checker --batch json on a cron and ship the output to your monitoring backend. Prometheus’s node_exporter does not currently parse this natively, but the textfile collector pattern works fine: a small script writes a .prom file under /var/lib/node_exporter/textfile_collector/ with one metric per CVE. Alert on any metric flipping to 1.
For pre-production validation of a golden image, use spectre-meltdown-checker --kernel ... --config ... to evaluate a kernel and config without booting them. This catches CVE coverage regressions before they ship — for example, a config change that disables CONFIG_RETPOLINE in pursuit of throughput will be flagged against every CVE that mitigation covers.
For a quick “what mitigation did the kernel pick at boot?” investigation, dmesg is the right tool, because it carries the kernel’s reasoning in narrative form and tells you which mitigations got rejected.
For “is this CPU hardware-fixed against CVE-X?” specifically — without caring about what the kernel did — use spectre-meltdown-checker --hw-only. This is the cleanest way to evaluate a part for a procurement decision or a fleet-refresh plan.
For air-gapped or kiosk-grade hosts where you cannot install new packages, the sysfs files plus a manual microcode-revision check against vendor advisories is your only option. Be aware that you are deliberately giving up the cross-check the script provides.
Gotchas worth knowing
A few things that caught me out the first time I leaned on this tool in anger.
The script’s microcode-revision tables ship with the script. If you are running a packaged version that is two years old, the tables are two years old, and you may get false-positive vulnerable verdicts on hosts that were silently fixed by microcode updates whose revision the bundled table does not recognise. Check the version against upstream when results look surprising.
mitigations=off on the kernel command line disables every mitigation the kernel knows about, and the script will dutifully report every CVE as VULNERABLE even though the kernel and microcode are perfectly capable of mitigating them. This is correct behaviour — the host genuinely is vulnerable in that configuration — but I have seen it cause mild panic during audits before the operator remembered the boot flag was there for a benchmark and forgot to remove it.
On VMs and containers, the script’s view of the hardware is the hypervisor’s view of the hardware. A VM running on a pre-patched hypervisor with a guest microcode of “0” — because most hypervisors do not pass microcode revisions through — will get an “unknown microcode revision” warning on every check. The right level to run the tool is the host. Running it inside a guest gives you the guest kernel’s view, which is useful but not the whole story.
The --variant flag knows symbolic names for the older CVEs (meltdown, spectre_v1, spectre_v2, l1tf, mds) but newer ones use the CVE number. If you are scripting against specific CVEs, use the CVE numbers — the symbolic name list lags upstream additions.
The script reads /dev/cpu/*/msr. On some hardened distributions, the msr module is not loaded by default, and on systems with lockdown=integrity or lockdown=confidentiality you cannot load it. The tool will skip those checks and tell you so, but the output is then strictly less informative than on a system where MSRs are accessible.
What you should actually do this quarter
Run spectre-meltdown-checker --batch json on every Linux host you own, archive the output, and write a short check that fails CI on any new VULNERABLE finding. Patch microcode and kernels until the output is clean. Keep the script up to date with upstream — the packaged version is a starting point, not a maintenance plan. And when a new transient-execution CVE drops, use the --variant CVE-YYYY-NNNNN flag to confirm coverage on a representative host before assuming your kernel and microcode have caught up.
The reason this remains a permanent operational concern, not a one-off cleanup, is that the next CVE in this family is not a question of if. The tool exists because the answer to “are we safe?” needs to be a fresh check, not a stale assumption.