# Linux Kernel CVE Records: Read Stable-Branch Affected Ranges

> Every Linux kernel CVE record carries two product entries: git commit ranges and kernel version ranges. We read all 7,987 kernel CNA records published in 2026 to show how the two fit together, where reading only one goes wrong (1,056 records), why the CPE block over-matches fixed kernels, and a tested Python checker for a kernel.org version.

- URL: https://firewallsync.com/posts/linux-kernel-cve-records-affected-versions-stable-branches/
- Site: FirewallSync (https://firewallsync.com/)
- Topic: AppSec & APIs
- Tags: linux kernel, cve, cve json 5, vulnerability management, affected versions, kernel cna
- Published: 2026-11-07
- Author: Craig Leslie

## Key takeaways

- All 7,987 Linux kernel CNA records published in 2026 (to November 6) have exactly two product entries. In 7,923 of them the first holds git commit ranges with defaultStatus unaffected and the second holds kernel version ranges with defaultStatus affected.
- The version entry reads top to bottom and the first match wins: the release that introduced the bug, everything before it unaffected, one 'X.Y.N to X.Y.*' unaffected range per fixed stable branch, then the mainline fix release onward. Anything not listed falls back to affected.
- In 1,056 records the git entry also holds kernel version ranges marking a stable release as affected where the version entry alone says unaffected, usually because the bug was backported into that stable branch. Evaluate both entries.
- The records' OR'd cpeApplicability ranges matched 35,340 of the 36,296 fixed stable releases they name (97.4%) as vulnerable. Use the affected block, not CPE, for kernel.org versions.
- None of this applies directly to distribution kernels: kernel.org says anything after the dash in a version means a distribution kernel. Use the distribution's own tracker or VEX data for those.



A Linux kernel CVE record answers "is my kernel affected?" in two places at once: a list of **git commit ranges** and a list of **kernel version ranges**, each in its own product entry with a different `defaultStatus`. Read the version entry from top to bottom, stop at the first match, and fall back to `affected` for anything unlisted. Then check the git entry too, because in about one record in eight it carries version ranges the other entry does not.

We read every record the Linux kernel CNA published in 2026 (7,987 records, January 21 to November 6) from the CVE Program's [cvelistV5](https://github.com/CVEProject/cvelistV5) bulk export of November 7, 2026, and counted how they are built. This article covers the kernel-specific layout. For the general rules of `version`, `lessThan`, `lessThanOrEqual` and `defaultStatus` in any CVE record, see our [guide to CVE affected versions](/posts/cve-affected-versions-lessthan-defaultstatus-versiontype/).

## What a kernel CVE record contains

The kernel's [CVE process document](https://docs.kernel.org/process/cve.html) sets the ground rules. CVEs are assigned "after a fix is available and applied to a stable kernel tree", and are tracked "by the git commit id of the original fix". No CVEs are assigned for kernel versions that are not "currently being actively supported by the Stable/LTS kernel team". And applicability is yours to decide: "the applicability of any specific CVE is up to the user of Linux to determine, it is not up to the CVE assignment team."

That produces a very regular structure. Across the 7,987 records:

| Feature | Records |
| --- | --- |
| Exactly two entries in `affected`, both product "Linux", vendor "Linux" | 7,987 |
| `repo` set to the stable tree (`git.kernel.org/.../stable/linux.git`) | 7,987 |
| `programFiles` listing the changed source files | 7,987 |
| A `cpeApplicability` block | 7,987 |
| Version entry starts with the introducing release, then `0` to it unaffected | 7,911 |
| Version entry ends with an `original_commit_for_fix` range to `*` | 7,917 |
| CNA-supplied CVSS v3.1 metrics | 3,154 |

Every record has the two entries. In 7,923 of them the order is the same: the git entry (`defaultStatus` unaffected) first, the version entry (`defaultStatus` affected) second. In the remaining 64, both entries have `defaultStatus` unaffected, so only explicitly listed versions are affected. The checker below handles both shapes because it evaluates every entry.

## Entry 1: git commit ranges

The first product entry has `"defaultStatus": "unaffected"` and a list of commit ranges. Here is CVE-2026-99190 (gpiolib, published October 28, 2026), trimmed to the version list:

```json
{"version": "d73d2a569e6212f58d89efc6e66ffbb2386a47b1", "lessThan": "68a2cd5381b8d89dc80bd16dd9cc9ca2d5c25e7f", "status": "affected", "versionType": "git"},
{"version": "9b407312755fd5db012413ca005f0f3a661db8dd", "lessThan": "78189c0e35abbcdab3b8ab0b764d578c02900734", "status": "affected", "versionType": "git"},
{"version": "9b407312755fd5db012413ca005f0f3a661db8dd", "lessThan": "890c3bf08e0fc09eff788a6cfeded7d21b4d3e9d", "status": "affected", "versionType": "git"},
{"version": "9b407312755fd5db012413ca005f0f3a661db8dd", "lessThan": "c28846ca1ac6134881114d272314623c8433e372", "status": "affected", "versionType": "git"},
{"version": "6.12.114", "lessThan": "6.12.115", "status": "affected", "versionType": "semver"}
```

Each git range reads "from the commit that introduced the bug, up to but not including a commit that fixed it". The fix commits are the same ones listed in the record's `references` as `git.kernel.org/stable/c/<hash>` links, one per branch that received the fix. The CVE schema says `repo` exists "to resolve git hash version ranges", which is the point: comparing commits is not a string comparison. You need a kernel git checkout to ask whether your build contains the introducing commit and not the fix.

If you build kernels from source, this is the most precise data in the record. Most teams run a released version instead, which is what the second entry is for. But note the last line above: a kernel version range inside the git entry. We come back to it below.

## Entry 2: kernel version ranges, first match wins

The second entry has `"defaultStatus": "affected"`. For CVE-2026-99190 it is:

```json
{"version": "6.15", "status": "affected"},
{"version": "0", "lessThan": "6.15", "status": "unaffected", "versionType": "semver"},
{"version": "6.12.115", "lessThanOrEqual": "6.12.*", "status": "unaffected", "versionType": "semver"},
{"version": "6.18.59", "lessThanOrEqual": "6.18.*", "status": "unaffected", "versionType": "semver"},
{"version": "7.2.13", "lessThanOrEqual": "7.2.*", "status": "unaffected", "versionType": "semver"},
{"version": "7.3", "lessThanOrEqual": "*", "status": "unaffected", "versionType": "original_commit_for_fix"}
```

The [CVE schema](https://github.com/CVEProject/cve-schema/blob/main/schema/CVE_Record_Format.json) gives the rule: walk the list, return the status of the first entry that matches, and return `defaultStatus` if none do. Applied here:

1. **6.15 is affected.** A bare `version` with no range is an exact match. This is the release that introduced the bug.
2. **Anything before 6.15 is unaffected.** `0` is the schema's convention for "the earliest possible version".
3. **One unaffected range per fixed stable branch.** `6.12.115` to `6.12.*` means 6.12.115 and every later 6.12 release. The schema says a trailing `*` means "an arbitrarily large number in the version ordering", so `6.12.*` closes the range at the end of the 6.12 branch without touching 6.13.
4. **The mainline fix onward is unaffected.** The `7.3 ... *` entry covers 7.3 and every later release.
5. **Everything else is affected.** 6.18.58, any 6.19 kernel, or 7.2.12 matches no entry and takes `defaultStatus`.

Point 5 is the reason the order and the default both matter. A branch that does not appear at all, such as 6.19 here, has no fix listed and falls through to affected. The kernel's [releases page](https://kernel.org/releases.html) currently lists 6.18, 6.12, 6.6, 6.1, 5.15 and 5.10 as longterm branches. If your kernel sits on a branch that a record does not list, the record gives you no fixed version on that branch; the way out is moving to a branch that has one.

### The `original_commit_for_fix` type is not defined

The last entry's `versionType` is `original_commit_for_fix`. It is not among the schema's `versionType` examples ("custom", "git", "maven", "python", "rpm", "semver"), and the kernel's CVE process document does not mention it. From the records, the value looks like the first mainline release containing the original fix, and every one we saw was a release number such as `7.3` or `7.4-rc1`. That is our reading of the data, not a published definition.

The practical effect: a parser that refuses unknown version types stops at this entry for every version not matched earlier, which in practice means "unknown" for any kernel above the newest fixed stable branch. Our [generic affected-versions guide](/posts/cve-affected-versions-lessthan-defaultstatus-versiontype/) shows a strict evaluator doing exactly that on a kernel record. If you choose to compare it as a normal kernel version, which is what the checker below does, make that a documented decision.

## Why you must read both entries

The `6.12.114 lessThan 6.12.115` line in the git entry is the trap. In CVE-2026-99190, the bug arrived in mainline at 6.15, so the version entry says every 6.12 kernel is "before 6.15" and unaffected. But the git entry marks 6.12.114 as affected and the version entry lists a 6.12.115 fix. The commit ranges hint at why: the first range starts from a different commit (`d73d2a5...`) than the other three (`9b40731...`), consistent with the bug reaching 6.12 as a stable backport. Read only the version entry and 6.12.114 comes out clean.

This is not rare. In 1,084 of the 7,987 records, the first (git) entry also contains `semver` ranges. In **1,056 records** (3,136 ranges), at least one of those ranges marks a stable release affected that the version entry, evaluated alone, calls unaffected.

Running the checker below against the record:

```text
$ python3 kernel_affected.py CVE-2026-99190.json 6.12.113 6.12.114 6.18.58 6.18.59 6.19.10 7.3 6.12.114-1-amd64
CVE-2026-99190 6.12.113: unaffected
CVE-2026-99190 6.12.114: affected
CVE-2026-99190 6.18.58: affected
CVE-2026-99190 6.18.59: unaffected
CVE-2026-99190 6.19.10: affected
CVE-2026-99190 7.3: unaffected
CVE-2026-99190 6.12.114-1-amd64: distribution kernel suffix; use your distro's tracker
```

(Per-entry lines trimmed; for 6.12.114 the checker reports entry[0] affected via the `6.12.114`-`6.12.115` range and entry[1] unaffected via `0`-`6.15`.)

## Why CPE matching over-flags fixed kernels

Every 2026 kernel record also has a `cpeApplicability` block. For CVE-2026-100075, it is one OR node of eight ranges on `cpe:2.3:o:linux:linux_kernel`, each starting at 4.7 and ending before one fixed version: 5.10.270, 5.15.221, 6.1.188, 6.6.157, 6.12.110, 6.18.52, 7.2.6, and 7.3. OR'd together, those ranges collapse to "4.7 up to 7.3", so 5.10.300, a fixed 5.10 kernel, still matches the 7.3 range.

We checked this across the corpus. The version entries name 36,296 fixed stable releases (the start of each `X.Y.N` to `X.Y.*` unaffected range). The OR'd CPE ranges in the same records match **35,340 of them (97.4%) as vulnerable**, in 7,917 records. CPE ranges have no way to say "fixed on the 6.12 branch at 6.12.110"; the affected block does. We covered this limit with one example in our [cpeApplicability guide](/posts/cve-cpeapplicability-cpe-match-nodes-operators-version-ranges/); the corpus count shows it is the norm for kernel records, not an edge case.

If your scanner or SBOM tool matches kernel CVEs by CPE and reports patched LTS kernels as vulnerable, this is the first thing to check.

## Distribution kernels: stop here

kernel.org is direct about this: "If you see anything at all after the dash, you are running a distribution kernel", and "kernel developers can provide no support for them". A RHEL, Ubuntu or Debian kernel version reflects the distribution's base plus its own backports, so `5.14.0-...` or `6.8.0-...` cannot be compared to the kernel.org ranges in a CVE record. The kernel's CVE document also says issues only present in a distribution's kernel get CVEs from that distribution, not from the kernel team.

For those kernels, use the distribution's security tracker or its VEX/CSAF data. Our [VEX explainer](/posts/vex-explained-what-a-not-affected-statement-means-and-how-to-read-one/) covers how to read a "not affected" statement from a vendor.

## A tested checker for kernel.org versions

The script below implements the schema's first-match algorithm over **both** product entries, skips git commit ranges (they need a kernel checkout), and reports affected if either entry says so. It refuses versions with a distribution suffix. We ran it with Python 3.12 against CVE-2026-99190 (output above) and across all 7,987 2026 records to produce the counts in this article.

```python
#!/usr/bin/env python3
"""Evaluate a kernel.org version against a Linux kernel CNA record (CVE JSON 5).
usage: kernel_affected.py CVE-record.json VERSION [VERSION ...]"""
import json, re, sys

def key(v):
    """Kernel version -> comparable tuple. '*' sorts above any number; -rcN sorts before final."""
    if v == "*":
        return ((float("inf"),),)
    m = re.fullmatch(r"(\d+(?:\.\d+)*)(?:\.(\*))?(?:-rc(\d+))?", v)
    if not m:
        raise ValueError(f"not a kernel.org version: {v}")
    nums = tuple(int(p) for p in m.group(1).split("."))
    if m.group(2):                                   # "6.12.*" -> 6.12.<infinity>
        nums += (float("inf"),)
    rc = int(m.group(3)) if m.group(3) else float("inf")  # 7.4-rc1 < 7.4
    return (nums + (0,) * (4 - len(nums)), rc)

def status(product, v):
    k = key(v)
    for e in product.get("versions", []):
        if e.get("versionType") == "git":
            continue                                 # commit ranges need a kernel git checkout
        if "lessThan" not in e and "lessThanOrEqual" not in e:
            if key(e["version"]) == k:
                return e["status"], e
            continue
        lo = key(e["version"])
        if "lessThan" in e and lo <= k < key(e["lessThan"]):
            return e["status"], e
        if "lessThanOrEqual" in e and lo <= k <= key(e["lessThanOrEqual"]):
            return e["status"], e
    return product.get("defaultStatus", "unknown"), None

rec = json.load(open(sys.argv[1]))
cve = rec["cveMetadata"]["cveId"]
prods = rec["containers"]["cna"]["affected"]       # kernel records carry two product entries
for v in sys.argv[2:]:
    if "-" in v and not re.search(r"-rc\d+$", v):
        print(f"{cve} {v}: distribution kernel suffix; use your distro's tracker")
        continue
    verdicts = []
    for i, p in enumerate(prods):
        s, e = status(p, v)
        why = "defaultStatus" if e is None else json.dumps({k: e[k] for k in e if k != "status"})
        verdicts.append(s)
        print(f"  entry[{i}] defaultStatus={p.get('defaultStatus')}: {s} (matched {why})")
    final = "affected" if "affected" in verdicts else "unaffected" if all(x == "unaffected" for x in verdicts) else "unknown"
    print(f"{cve} {v}: {final}")
```

Limits: it treats `original_commit_for_fix` as a kernel version comparison (see above), ignores git ranges, and only understands kernel.org version strings. It is a reference for reading the data, not a vulnerability scanner.

## Turning the answer into a decision

"Affected" in a kernel record means the code is in your build, not that it is reachable. The kernel team says plainly that "large numbers of assigned CVEs are not relevant for their systems", and the `programFiles` list (here `drivers/gpio/gpiolib.c`) is a quick first filter against your kernel config. After that, the usual signals apply: is it in KEV, what does EPSS say, what is exposed. Our [vulnerability prioritization workflow](/posts/vulnerability-prioritization-workflow-cve-record-to-patch-ticket/) walks through that decision from the CVE record to a ticket.

The kernel team's own advice is to take whole stable releases rather than cherry-pick: "it is best to take all released kernel changes, as they are tested together in a unified whole". For most teams, staying on a current LTS point release answers more of these records than evaluating them one by one.

## Limits of this article

The counts cover Linux kernel CNA records with IDs CVE-2026-* in the PUBLISHED state, from the cvelistV5 export of November 7, 2026. The CNA updates records, so counts will drift. We did not check git ranges against a kernel tree, so the backport explanation for CVE-2026-99190 is an inference from the record. The meaning of `original_commit_for_fix` is our reading of the data, not a published definition.

## Sources and verification

| Important claim | Source | Status |
| --- | --- | --- |
| Record counts, entry structure, 1,056 records / 3,136 ranges, 36,296 fixed releases and 35,340 CPE matches | [CVEProject/cvelistV5](https://github.com/CVEProject/cvelistV5) bulk export 2026-11-07, computed with the script above | Verified (computed) |
| CVE-2026-99190 and CVE-2026-100075 affected and cpeApplicability data | [cvelistV5 records](https://github.com/CVEProject/cvelistV5) | Verified |
| First-match algorithm, `defaultStatus` fallback, `*` wildcard, `repo` for git ranges, `versionType` examples | [CVE JSON schema](https://github.com/CVEProject/cve-schema/blob/main/schema/CVE_Record_Format.json) | Verified |
| CVEs assigned after a fix reaches stable, tracked by original fix commit; only supported branches; applicability is the user's; take all stable changes | [Linux kernel CVE process](https://docs.kernel.org/process/cve.html) | Verified |
| Longterm branches; text after the dash means a distribution kernel | [kernel.org releases](https://kernel.org/releases.html) | Verified, Nov 7, 2026 |
| Meaning of `original_commit_for_fix` | Record data only; no definition found | Qualified |

## Frequently asked questions

### How do I check if my Linux kernel is affected by a CVE?

For a kernel.org kernel, take the version (for example 6.12.114), and evaluate it against both product entries in the CVE record's affected block, using the CVE schema rule that the first matching entry wins and unlisted versions take defaultStatus. If either entry says affected, treat it as affected. For a distribution kernel (anything after a dash, such as 6.12.114-1-amd64), use your distribution's security tracker instead.

### What does versionType original_commit_for_fix mean in a kernel CVE record?

It appears on the last version entry of nearly every 2026 kernel record, as a range from the mainline release that contains the fix up to '*', marked unaffected. Neither the CVE schema's examples nor the kernel's CVE process document defines it, so a strict parser will not know how to compare it. Treating it as a plain kernel version comparison is an assumption you should make explicitly.

### Why does a scanner flag my patched stable kernel?

One common cause is matching on the record's cpeApplicability block. In the 2026 kernel records, those CPE ranges are OR'd and all start at the release that introduced the bug, so the widest range covers every fixed stable release below the mainline fix. In our check, 97.4% of the fixed stable releases named in the records matched as vulnerable that way.

### Why are there so many Linux kernel CVEs?

The kernel's CVE process document says that, because almost any kernel bug might be exploitable, the assignment team is 'overly cautious and assign CVE numbers to any bugfix that they identify'. It also says applicability is up to the user, and that most systems use only a small part of the source tree.


Image credit: Photo by [Carlos 312](https://unsplash.com/photos/bare-trees-with-intricate-branches-in-a-field-vUVKKdixrpE) on Unsplash
