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.

A grove of leafless trees with dark, twisting trunks that split into many crossing branches above a pale yellow lawn
Photo by Carlos 312 on Unsplash

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 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.

What a kernel CVE record contains

The kernel’s CVE process document 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:

FeatureRecords
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 files7,987
A cpeApplicability block7,987
Version entry starts with the introducing release, then 0 to it unaffected7,911
Version entry ends with an original_commit_for_fix range to *7,917
CNA-supplied CVSS v3.1 metrics3,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:

{"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:

{"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 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 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 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:

$ 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; 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 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.

#!/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 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 claimSourceStatus
Record counts, entry structure, 1,056 records / 3,136 ranges, 36,296 fixed releases and 35,340 CPE matchesCVEProject/cvelistV5 bulk export 2026-11-07, computed with the script aboveVerified (computed)
CVE-2026-99190 and CVE-2026-100075 affected and cpeApplicability datacvelistV5 recordsVerified
First-match algorithm, defaultStatus fallback, * wildcard, repo for git ranges, versionType examplesCVE JSON schemaVerified
CVEs assigned after a fix reaches stable, tracked by original fix commit; only supported branches; applicability is the user’s; take all stable changesLinux kernel CVE processVerified
Longterm branches; text after the dash means a distribution kernelkernel.org releasesVerified, Nov 7, 2026
Meaning of original_commit_for_fixRecord data only; no definition foundQualified

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.

Linux KernelCveCve Json 5Vulnerability ManagementAffected VersionsKernel Cna