What we counted, on 7 July 2026
We ran a simple count. For every actively-maintained Linux LTS line — 5.4, 5.10, 5.15, 6.1, 6.6, 6.12 — we took the latest point release, assumed a kernel with every feature compiled in (the most generous possible reading), and asked one question: of all the kernel CVEs an NVD-based scanner would flag against it, how many actually affect it?
| Active LTS (latest, 7 Jul 2026) | Scanner flags | Actually affected | Don’t apply | Share |
|---|---|---|---|---|
| 5.4.302 | 3,065 | 2,590 | 957 | 31% |
| 5.10.260 | 2,195 | 1,526 | 955 | 44% |
| 5.15.211 | 1,872 | 1,345 | 799 | 43% |
| 6.1.177 | 1,206 | 809 | 554 | 46% |
| 6.6.144 | 691 | 514 | 287 | 42% |
| 6.12.95 | 262 | 262 | 91 | 35% |
Read the 6.1 row. A current, fully-patched 6.1.177 kernel draws 1,206 kernel-CVE findings from NVD-based matching — and 554 of them, 46%, do not affect that kernel. Not “low severity,” not “mitigated”: the vulnerable code was never in it, or the fix already shipped to you. Across the six lines, that’s 1,555 distinct CVEs producing findings on kernels they can’t touch. And because we assumed every feature compiled in, these figures are a floor — a real device with a lean config clears even more.
Numbers like these usually come with an accusation attached: the scanner is broken, or the database is sloppy. Neither is true here — and that is what makes this worth understanding. The scanner matches exactly what it’s given. NVD publishes exactly what its format allows. The gap opens between them, at a specific, structural seam.
The best way to see that seam is to follow one kernel CVE all the way from publication to the finding on your dashboard. Everything below is drawn from three public sources — NVD’s API, the CVE record at cve.org, and the kernel’s own git history — so you can re-run every step yourself. We’ll do it with two real CVEs: a netfilter bug your patched kernel already fixed, and a NIC-driver bug your kernel never contained.
Step 1 — a kernel CVE is born as a set of git commits
The Linux kernel is its own CVE Numbering Authority. When it publishes a CVE, it does not say “affects versions 5.7 through 6.6.” It says, in the machine-readable record at cve.org — here for CVE-2026-43114, a bug in netfilter’s packet-classification engine, the code behind nftables firewalls:
"versions": [
{ "version": "7400b063…", "lessThan": "1c43f0dd…", "versionType": "git", "status": "affected" },
{ "version": "7400b063…", "lessThan": "f8c39983…", "versionType": "git", "status": "affected" },
…one entry per maintained stable branch…
]
Read it literally: affected from commit 7400b063, up to each
branch’s own fix commit. The kernel maintains many stable branches
in parallel — 5.10.x, 6.1.x, 6.6.x and so on — and each branch
receives its fix at its own point release. So the kernel publishes one commit
range per branch. This is exact, because a git commit is an
exact thing.
And it’s exact in a way you can resolve, on any kernel checkout:
$ git tag --contains 1c43f0dd8691… # the 6.1-branch fix commit
v6.1.175 …
$ git tag --contains 7400b063969b… # the commit that introduced the bug
v5.7 …
Introduced in v5.7. Fixed on the 6.1 branch at v6.1.175. If you run 6.1.177, that fix is in your kernel. This is the ground truth, published by the people who wrote the patch.
Step 2 — your scanner speaks a different language
Scanners don’t traverse git history. They match components against CPE — a flat product identifier plus numeric version bounds. That’s not a shortcoming; it’s the industry’s interchange format, and it works well for “product X, versions 1.2 to 1.4.”
But it means somebody has to translate: from the kernel’s per-branch commit ranges into flat version windows. NVD performs that translation. Here is its actual output for the same netfilter CVE, verbatim from the NVD API:
vulnerable, from 5.7 before 6.6.136
vulnerable, from 6.7 before 6.12.83
vulnerable, from 6.13 before 6.18.24
vulnerable, from 6.19 before 6.19.14
Now compare the two documents. The kernel published separate fix commits for the 5.10, 5.15, 6.1 and 6.6 branches. The CPE translation collapsed all of them into one window — “from 5.7 before 6.6.136” — capped at the 6.6 branch’s fix.
Note what this is not: it is not a one-window-per-CVE limitation. CPE happily expresses several windows per CVE — you can see them listed on any finding in Dependency-Track, and right here the newer branches each got their own. The granularity just stops before it reaches the older LTS lines: four branches with four different fix points share one window, capped at whichever fix the translation picked — here, 6.6’s.
Your 6.1 kernel was fixed earlier, at 6.1.175. But 6.1.175 lies inside “from 5.7 before 6.6.136,” so the scanner matches your patched kernel and raises a finding — correctly, against the range it was given. And this is the rule, not a quirk of one CVE: of the 554 phantom findings on 6.1.177, 95% are matched by a window whose cap belongs to a different branch — 250 of them, like this one, capped at 6.6’s fix. Even the few 6.1-specific windows in the phantom set are drawn to the end of the branch instead of to its fix point. The translation didn’t make an error; it stopped short of per-branch resolution.
The pattern is strikingly consistent: in every CVE we checked, the newer branches’ windows are correct — each cap is that branch’s real fix release. It is always the oldest window that misbehaves: it lumps the remaining LTS lines under one foreign cap, and it usually drops its lower bound to zero even when the kernel’s record names the exact introducing version. Both phantom directions live in that one window.
That is how a CRITICAL-rated firewall bug you already patched stays red on your dashboard.
The same seam, failing the other way
CVE-2025-21751 is a use-after-free in the mlx5 driver
— the NVIDIA/Mellanox ConnectX NICs common in datacenters. The kernel
record says the bug is affected from commit
472dd792. Resolve that commit:
$ git tag --contains 472dd792348f…
v6.12 … # present in no 6.1.x tag at all
The vulnerable code was born in v6.12, years after the 6.1 branch split off. It cannot exist in any 6.1 kernel. NVD’s CPE range for it:
vulnerable, before 6.13.3 (no lower bound)
When the commit range’s starting point doesn’t map cleanly to a version number, the translation defaults to “from the beginning.” The result: a scanner flags this 2024-born bug against 6.1 — and against a 5.4 kernel released in 2019.
Two failures, opposite directions, one cause: the round-trip from git commits to version ranges is lossy. Everything downstream inherits the loss.
Why nobody in the chain can fix it alone
It’s tempting to file a bug against someone. But walk the chain:
- The kernel CNA publishes the most precise affectedness data of any CVE source — exact commits, per branch. Nothing to fix there.
- NVD must express that in CPE, a format designed long before a CNA existed that mints thousands of CVEs a year across dozens of parallel stable trees. A fully faithful translation would need one version window per branch, per CVE — sometimes it’s there, often it’s rounded. Given the volume, that’s not sloppiness; it’s the format straining.
- Your scanner consumes CPE because CPE is what exists. It matches faithfully.
The imprecision isn’t in any link. It’s in the joint — and it will persist as long as kernel affectedness is squeezed through flat version ranges. Which means the fix isn’t a better matcher. It’s better input: matching your kernel against the per-branch commit data directly, the way the kernel itself publishes it.
What this costs you — and what to do about it
Every phantom finding has a real price: an engineer opens the CVE, reads the description, checks the changelog, realizes it doesn’t apply, closes it — twenty minutes, hundreds of times per kernel, every scan cycle. Worse, the 554 findings that don’t apply are visual noise burying the 809 that do. Alert fatigue isn’t an inconvenience; it’s how the real one gets missed.
This is the problem KernelScan was built for. We track every kernel CVE at the same granularity the kernel publishes it — introduced-in commit, per-branch fix commit, per point release — and turn it into verdicts for your kernel:
- Version-precise: each of those 554 phantoms becomes an explicit “Not affected” with the reason spelled out — “fixed on the 6.1 branch at 6.1.175”, “introduced in 6.12; 6.1 predates it.”
- Scanner-native: the verdicts export as standard VEX, which Dependency-Track and friends apply automatically — your existing dashboard, minus the phantoms, no rip-and-replace.
-
And that’s the floor: the numbers in this article
assumed every feature compiled in. Upload your actual
.configand KernelScan also clears the CVEs in code your build never compiled — on real devices, that’s most of what remains.
The result isn’t a shorter report for its own sake. It’s a patch queue where every entry is actually about your kernel — and an audit trail (per-branch, commit-level, the same public data you verified above) behind every “not affected.”
Try it on your kernel →
Upload a .config, get the full CycloneDX VEX for your exact
version and build. The free tier covers recent kernels —
you’ll know within minutes how much of your current findings list is
phantom.
Methodology: counts measured 7 July 2026 against the latest point release of
each actively-maintained LTS line, assuming all features compiled in.
“Scanner flags” = CVEs whose NVD CPE version ranges include that
release; “actually affected” = CVEs whose kernel-CNA per-branch
commit ranges include it. NVD CPE ranges and kernel CVE records quoted
verbatim from the NVD API and cve.org; commit-to-tag resolution via
git tag --contains on kernel.org trees.