Daily statistics from the linux-cve-announce archive — publication volume, stable-branch backport gaps, and how CVSS scoring is actually applied. Every number on this page comes from the JSON files linked at the bottom.
Read these counts as a disclosure policy, not a deteriorating kernel. In February 2024 the Linux kernel became its own CVE Numbering Authority. It now assigns an identifier to each stable-tree bugfix its team judges security-relevant — a minority of stable commits, but enough to move annual totals from a few hundred before 2024 to roughly 5,800. The volume changed because the counting changed.
The kernel does not claim these are exploitable. Its own documentation says applicability "is up to the user of Linux to determine," and that "large numbers of assigned CVEs are not relevant for their systems." Comparing this total to another project's compares counting rules, not security.
That framing is the kernel team's, and it is contested. It is quoted here because it explains the volume, not because this page endorses it. Downstream maintainers who have to triage the output have argued the opposite — that assigning identifiers to thousands of bugs without severity or exploitability data shifts an unfunded burden onto distributions and dilutes the signal CVEs are supposed to carry. Distribution and systemd maintainers made that case directly when the policy landed, in A turning point for CVE numbers and the CNA announcement discussion. Both readings are defensible and this page does not settle between them. What the data supports is narrower: the step change in 2024 is a counting change, and growth since then is separate and modest — see the rate below.
Kernel CVEs are not published continuously — they arrive in batches, usually tied to a round of stable releases. Long quiet stretches followed by a several-hundred-CVE day are the normal shape of this data, not an anomaly. The whole archive is shown by week, because a weekly bar is close to one real batch: only twice in two and a half years has a run of publication days straddled a week boundary.
This is why a headline of the form "N vulnerabilities this week" carries almost no information about the kernel — it reports which side of a batch the calendar happened to fall on. The panel beneath the bars is the figure that does not move with the batch schedule.
| Date | CVEs |
|---|
For CVEs published in the last 30 days: a branch is affected when the kernel's own CVE team lists it as affected, and fixed when the announcement lists a fix for it. The difference is the backport gap at announcement time — a snapshot of what one batch left open, not a verdict, since gaps close as backports land.
The orange band is the gap, and it counts only branches the CVE team names. Where the team publishes no record for a branch, that branch can still be inferred as affected from version ordering — it forked after the bug went in and before the fix came out — and those are drawn separately, in grey. They are shown because they are real candidates, and kept apart because they behave differently: an inferred branch can only ever produce a gap, never a fix, since the announcement's fix list is drawn from the named set. Adding the two together produces a clean staircase down this chart that reads as older branches are patched worse — but that pattern tracks how completely each branch gets enumerated, not how well it gets patched. Measured on named branches alone, the gap is close to flat.
| Branch | Named affected | Fixed | Gap | Gap % | Inferred only |
|---|
Mostly, yes — and this is the figure most likely to be misread. Measured across the branches the CVE team names, the large majority of gaps are filled, usually within weeks. The count that looks alarming — tens of thousands of branch-CVE pairs with no fix on record — is dominated by pairs the team never published a record for in the first place. Those are excluded here rather than counted as refusals, because a fix on such a branch could not be observed even if it landed, so including them would measure the CVE team's bookkeeping and report it as the stable team's patching.
The announcement is frozen at publication and never amended, so the archive alone can't say whether a missing backport later landed. The kernel security team's vulns.git can: every backport that lands rewrites that CVE's record, so walking the full commit history recovers the exact date each fix was recorded — and with it, a real distribution of how long backports take.
Every branch the archive touches, including retired ones, each measured only across its real maintenance window from kernel.org's release index — a branch that ended in 2021 is judged on its years, not on the ones since.
Two closure columns, because one cannot do both jobs. Closed by 90d is read at the same horizon for every branch, which is what makes it comparable down the column. Longest view is the deepest reading each branch individually supports, with its horizon printed beside it — do not read one branch's across to another's. Neither is taken at a year: by then 4.19 has two gaps still under observation and 6.12 has six, and a rate off a handful of survivors is noise wearing a percent sign. A branch with fewer than 50 resolved gaps gets a dash instead: two of two closing is not 100%.
Older branches close somewhat less and take longer, and a branch winding down takes fewer backports. Be wary of reading a steep decline across this column, though: older branches are also the ones the CVE team enumerates least completely, and a rate built across both kinds of branch measures that enumeration rather than what the stable maintainers did. The inferred only column is that quantity, published so the effect stays visible instead of being buried inside a rate.
| Branch | Named gaps | Backported later | No fix recorded yet | Too recent to judge | Closed by 90d | Closed, longest view | Median days | Inferred only |
|---|
The announcements themselves never carry a severity — the Linux CNA publishes CVSS only in the CVE record at cve.org, and often days after the announcement. Of the CVEs published in the last 30 days and already looked up, — have a score at all.
The kernel's CVE team rejects the premise of these scores. Greg Kroah-Hartman: "we have no way of judging the severity of any CVE fix for a user, only the integrator of Linux into their system can properly determine that. So any group that attempts to give a 'severity score' to a Linux CVE is lying to you, UNLESS they know exactly your use case." This section is therefore a description of how scoring is applied to kernel CVEs — which is itself revealing — and not a severity assessment. Nothing below ranks risk.
When the fix identifies the commit that introduced the bug, that commit's kernel version says how long the flaw sat in the tree. The bulk sits in the 5.x era, with a long tail reaching back to 2.6 — code that has been shipping for two decades still yields CVEs.
Computed over the whole archive, not a recent window. Nothing about the age of an introducing commit depends on when its CVE happened to be announced, and a single batch is small enough and skewed enough to reverse the ranking outright — one 30-day window puts the newest era on top, which is the opposite of what the archive shows.
These buckets cover very different spans of time: 2.6.x is eight years wide, 6.10+ is two. Bar heights are raw counts and are not comparable across bars for that reason — the caption gives the per-year rate, which is the comparison that means something. Even that is right-censored: recent eras have had fewer years in which their bugs could be found, so their rates will keep rising for years yet and are floors, not final values.
Share of announcements whose text credits a discovery method. This counts only what the commit message says out loud, so it is a floor, not a census — most announcements say nothing about provenance.
| Quarter | Total | syzbot | static | AI |
|---|
Subsystem is the prefix of the fix's subject line. It reflects where fixes are being published, which tracks maintainer activity and audit attention at least as much as it tracks where bugs actually are.
| Subsystem | CVEs (last 365d) | Share |
|---|
Ranked over a year, not over the recent batch. CVEs arrive in batches of several
hundred, so a 30-day ranking is largely a ranking of whatever the last batch happened to contain:
a driver subsystem with nine CVEs on one day and none in the preceding two years would place
well inside the top 25. Prefixes differing only in capitalisation are counted as one subsystem,
since usb and USB are the same thing and would otherwise split their
count across two rows.
The same set of subsystems carried back across the archive, one point per complete month. Toggle any line on or off; the top five are on by default. Other is every subsystem outside the ranked 25, and Total is all CVEs published that month.
These are the exact files the charts above are drawn from. No login, no rate limit, no attribution required — though a link back is appreciated if you use them.
Two sources, both mirrored with git and parsed directly — no scraping, no HTML, no third-party feed.
git clone --mirror https://lore.kernel.org/linux-cve-announce/0
Every message is one CVE announcement. Messages are classified as advisories or
withdrawals (body contains "has now been rejected"); list-administrative traffic is
dropped. Affected and fixed versions are read only from the
Affected and fixed versions block, so a version number mentioned in a commit
message can't contaminate the record. Both and fixed in X and the bare
Fixed in X forms are matched — the second is used when there's no known
introducing commit, and missing it silently undercounts fixes.
git clone --bare https://git.kernel.org/pub/scm/linux/security/vulns.git
The announcement is frozen at publication and never amended, so it can't say whether a
missing backport later landed. vulns.git can: every backport that lands rewrites that CVE's
.dyad file. Walking the full commit history in order and recording the first
appearance of each (CVE, branch) pair recovers the exact date every fix was registered —
retroactively, across the whole archive.
CVSS comes from the CVE Program record store at cveawg.mitre.org, cached and
re-checked for 120 days because scores are assigned after publication. Records published before
2025 are not requested at all: the Linux CNA assigned no CVSS during its first year, so those
lookups could only ever return nothing.
A branch is treated as live when it appears in at least half of the recent publication batches and kernel.org has not flagged it end-of-life. The batch heuristic alone is not sufficient: 7.0 shipped its final release on 2026-06-27, yet later batches still referenced 7.0.14, which is enough to keep a dead branch looking live. A newly-opened branch is shown on the gap chart in grey, labelled reference only, and excluded from every summary figure.
Each CVE's .dyad file in vulns.git lists, per branch, the version
that introduced the bug and the version that fixed it — or no fix version, meaning affected and
still open. A branch the file names is attested. A branch it does not name can still be
inferred as affected from version ordering, if it forked at or after the introducing version and
before the mainline fix; that is inferred, and the two are never added together.
The reason is that closure is detected by the CVE team publishing a record. A branch is in the inferred set precisely because no such record exists — so the condition that puts a pair in that population is the negation of the condition that would close it, and its closure rate is pinned near zero however the stable team behaves. It is 6 closures in 5,954 across the archive, and the 6 are edits to old records rather than backports. Inferred pairs are just over half of all candidates, so a rate computed across both is an average of a real measurement and a tautology — and its complement reads as more than half of kernel backport gaps are never filled, which is a statement about the CVE team's bookkeeping wearing the costume of a statement about patching. The same blending turns the by-branch table into a readout of enumeration density, landing every branch within a few points of one minus its inferred share.
Inferred pairs are still counted and published, in their own column, because they are real candidates and a reader is entitled to see how much of the picture the records do not cover. No rate is derived from them.
Over attested gaps only. Each carries its maintained exposure: days the branch stayed under maintenance after the CVE was announced, bounded by the branch's real end-of-life. Gaps are split three ways — closed (the backport landed), declined (at least 180 days of maintained exposure and still no fix), and censored (too recent, or the branch ended first).
Every published rate is Kaplan-Meier: one risk set that only shrinks, with each gap contributing every day it was genuinely at risk and then dropping out at its branch's EOL. The tempting alternative — asking, at each horizon, what share of gaps old enough to judge had closed — gives every point a different denominator drawn from a different slice of the archive. That lets cumulative closure fall as the horizon grows, which is impossible.
A simple closed ÷ (closed + declined) ratio is also not used, and cannot be repaired by moving the 180-day threshold. It is biased upward: a gap that closed in five days counts in the numerator whatever its exposure, while an unresolved gap of the same age is dropped from the denominator, so fast backports are always fully counted and slow ones only partly. The bias is large here because backports have a long tail: the 90th percentile is 203 days, and a threshold short enough to keep a usable denominator books a great many real backports as refusals before they land. The threshold only labels the declined/censored split, and no rate comes from it.
KM has its own trap at the other end. As the risk set empties, one closure among the last few observations multiplies the estimate straight to 100%, so a branch with a couple of hundred gaps can show perfect closure off two survivors. No estimate is read past the point where fewer than 50 gaps remain under observation, every published KM figure names the risk set behind it, and branches with fewer than 50 resolved gaps get no rate at all.
Known limits.
net and ipv6) are not, so the ranking is finer-grained than a real
subsystem map and the counts are floors.The pipeline ships invariant checks (scripts/selftest.py) that gate the daily
run: silent degradation is its real failure mode, since a regex that stops matching produces
smaller numbers rather than an error.