#!/usr/bin/env python3 """Render SM-023 from the summary. python3 -I make_page.py summary.json OUT.html""" import json, sys, os, html S = json.load(open(sys.argv[1])); OUT = sys.argv[2] HERE = os.path.dirname(os.path.abspath(__file__)); P = os.path.join(os.path.dirname(HERE), "ghsa", "page_parts") esc = html.escape d = lambda x: f"{x:g} days" if x is not None else "—" HD, CV, Y = S["hand_rated"], S["cvss_rated"], S["y2026"] CP, CK, CA = S["cvss_rated_from_publication"], S["cvss_rated_from_known"], S["cvss_rated_alt_release_ships"] MONTHS = {"2026-06": "June", "2026-07": "July", "2026-08": "August", "2026-09": "September", "2026-10": "October"} notfixed = len(S["no_fix_open_pro_days_since_raise"]); missed = CV["measured"] + notfixed SP, SL, KM = S["fixed_kernel_still_open_pro"], S["fixed_kernel_still_open_legacy"], S["known_minus_published"] NAMES = {"stonking": "26.10", "resolute": "26.04 LTS", "noble": "24.04 LTS", "jammy": "22.04 LTS", "focal": "20.04 LTS", "bionic": "18.04 LTS", "xenial": "16.04 LTS", "trusty": "14.04 LTS"} rn = lambda c: f"{NAMES.get(c, c)} ({c})" _dep = os.path.expanduser("~/second-measurements/zenodo/sm-023/deposit.json") DOI = json.load(open(_dep))["metadata"]["prereserve_doi"]["doi"] if os.path.exists(_dep) else "pending" EX = "sm-023/exhibits/" title = (f"Canonical says “critical vulnerabilities are released within 24 hours” in Ubuntu. Since June its security team has raised {CV['n']} kernel vulnerabilities " f"to critical from a CVSS score; of the {CV['measured']} fixed after being raised, none was fixed within a week, and the median wait was {CV['median_d']:g} days") desc = (f"Ubuntu’s security team sets each vulnerability’s priority, and Canonical says critical ones are released within 24 hours, across every supported release. " f"We read all {S['total_critical']} vulnerabilities Ubuntu rated critical on {S['read_at'][:10]}, the commit in the team’s public tracker that made each one critical, and the day its first fix reached users. " f"{CV['n']}, all Linux kernel, were recorded as medium and raised to critical from a CVSS score between June and October 2026. {CV['fixed_before_start']} were already fixed when raised, and one affects no Ubuntu release. None of the other {missed} was fixed within a day: {CV['measured']} were fixed a median {CV['median_d']:g} days later, none within a week, and {notfixed} were still waiting.") hand_rows = "".join( f"{esc(h['cve'])}{h['published']}{esc(h['package'])}{(' and ' + str(h['n_packages'] - 1) + ' more') if h['n_packages'] > 1 else ''}" f"{esc(h['reason']) or '—'}" + (("no release affected" if h["cve"] in S["no_fix_other"] else "not fixed") if h["known_to_fix_d"] is None else ("fixed before marked critical" if h["known_to_fix_d"] < 0 else f"{round(h['known_to_fix_d'], 1):g} days")) + "" for h in S["hand_rated_list"]) rel_rows = "".join(f"{rn(r)}{v}" for r, v in SP["by_release"].items() if v) since = S["since_2026_09_23"] style = open(os.path.join(P, "style.html")).read() top = open(os.path.join(P, "top.html")).read().replace("SECOND MEASUREMENT SM-017", "SECOND MEASUREMENT SM-023") foot = open(os.path.join(P, "foot.html")).read() url = "https://markovianprotocol.com/measurements/sm-023.html" head = f""" SM-023 {title} — Markovian Protocol {style} """ body = f"""

{title}

Markovian ProtocolMeasured {S['read_at'][:10]}Status: draft, not publishedDOI: {DOI}

{desc}

The short version

Disclosure: Markovian Protocol holds no financial position in Canonical, was paid by no one for this work, and showed it to no one before publication. How we work.

Critical by judgement and critical by CVSS

How the priority was setCriticalFixedFixed before rated criticalTimedWithin a dayWithin a weekMedian wait
By the team’s judgement{HD['n']}{HD['fixed']}{HD['fixed_before_start']}{HD['measured']}{HD['within_1d']}{HD['within_7d']}{d(HD['median_d'])}
Raised from medium on a CVSS score{CV['n']}{CV['fixed']}{CV['fixed_before_start']}{CV['measured']}{CV['within_1d']}{CV['within_7d']}{d(CV['median_d'])}

Wait = first fix reaching users minus the first commit that set the vulnerability’s Priority: line to critical in ubuntu-cve-tracker. First fix = the earlier of the first USN and the first publication, in the Security, Updates or Release pocket and on or after that release’s own release date, of the fixed version the tracker lists for any release: the main linux package for kernel vulnerabilities, the vulnerability’s own package otherwise (Launchpad publishing history; versions kept in Ubuntu Pro’s private archive cannot be dated and are left out). “Fixed before rated critical” = a negative wait, left out of the timed columns. Livepatch notices (LSNs) exist for {S['lsn_cves']} of these vulnerabilities; none came before the first fix, so counting them changes nothing. Read at {S['read_at'][11:16]} UTC on {S['read_at'][:10]}.

The team-rated ones, all {HD['n']}:

{hand_rows}
CVEPublishedPackagePriority reasonRated critical to fix

Other start times

For the {CV['n']} raised on a CVSS score, other starts give longer waits, because they include the time each spent at medium, for which the FAQ’s figure is 60 days:

Clock starts whenTimedWithin a dayMedian
the vulnerability was published{CP['measured']}{CP['within_1d']}{d(CP['median_d'])}
the team first recorded it (at medium){CK['measured']}{CK['within_1d']}{d(CK['median_d'])}
the team raised it to critical (used above){CV['measured']}{CV['within_1d']}{d(CV['median_d'])}

Counting a new release that came out after a vulnerability was raised, already carrying the fix, as a fix on its release date changes nothing: {CA['timed']} timed, {CA['within_1d']} within a day, median {d(CA['median_d'])}. {S['cvss_never_affected'][0]} is left out of every count of waits, since no Ubuntu release carried it.

Every supported release

The FAQ applies the 24 hours “across every Ubuntu release actively supported”. On {S['read_at'][:10]}, {len(S['standard_releases'])} releases are in standard support and {len(S['pro_releases']) - len(S['standard_releases'])} more in Ubuntu Pro (ESM): {', '.join(rn(r) for r in S['pro_releases'])}. Of the {S['fixed_kernel']} critical kernel vulnerabilities with a fix somewhere, {SP['n']} still show the main linux package as needed or pending on at least one of them:

{rel_rows}
ReleaseCritical kernel fixes still needed or pending

Only the main linux source package is counted, not the dozens of kernel variants. “Pending” means a fix is prepared but not yet released. {', '.join(rn(r) for r in S['legacy_releases'])} are now covered only by the paid Legacy add-on, not Ubuntu Pro, and are left out above; on them {SL['n']} of the fixed critical kernel vulnerabilities are still open.5

Another {notfixed} critical kernel vulnerabilities had no fix released for the LTS releases that still need them on {S['read_at'][:10]}, {min(S['no_fix_open_pro_days_since_raise']):g} to {max(S['no_fix_open_pro_days_since_raise']):g} days after they were raised to critical. One of them, CVE-2023-53867, is already fixed in the kernels 24.04 and 26.04 LTS shipped with; it is still needed on 18.04, 20.04 and 22.04 LTS.

What Canonical announced in September

Canonical’s post of 23 September 2026 outlined “a transition from its current 4-week regular and 2-week security kernel Stable Release Update (SRU) cycles to a unified, rapid 2-week SRU cycle, published weekly”, with no date given for the change, and said that while a patch is prepared “Canonical aims to provide safe workarounds where applicable”.4 It went on: “Where no safe workaround exists, Canonical will say so clearly and point users toward general hardening steps instead.” Since that post, two vulnerabilities now rated critical were published; both were recorded as medium and raised to critical on 18 October 2026, two days before our read. On {S['read_at'][:10]}, CVE-2026-93221 had a fix pending for 26.04 LTS and still needed for 20.04, 22.04 and 24.04 LTS; CVE-2026-98365 had fixes pending for 24.04 and 26.04 LTS and still needed for 18.04, 20.04 and 22.04 LTS. 26.10 already ships a kernel containing both fixes. Neither had a USN, the mitigation field on each CVE page was empty, and searching Canonical’s blog, Ubuntu’s security notices and Ubuntu’s Discourse forum for each CVE found neither a workaround nor a statement that none exists (workaround_search_2026-10-20.json). On {S['read_at'][:10]}, Ubuntu’s web pages for both, and for CVE-2026-89530, still showed “Ubuntu priority Medium” (last updated 8 October, 25 September and 6 October) while Ubuntu’s CVE API and tracker said critical (saved pages: 98365, 93221, 89530).

How this ends

Canonical can close it three ways: ship CVSS-rated critical kernel fixes within 24 hours; stop rating kernel vulnerabilities critical from a CVSS score alone, so that “critical” means what the team judges critical; or change the FAQ to say what kernel users should expect. We recheck monthly and print the table here.

Our questions to the Ubuntu Security Team

  1. Does the FAQ’s “critical vulnerabilities are released within 24 hours” cover kernel vulnerabilities rated critical from the Ubuntu CVSS score? If not, will the FAQ or the label change?
  2. Under the two-week kernel cycle announced on 23 September, what should users expect between a CVSS-rated critical kernel vulnerability and its released fix?
  3. For the {SP['n']} critical kernel vulnerabilities fixed in some releases but still needed or pending in others under standard or Pro support, is there a date for the rest?

Their reply, scored

Not yet sent.

What we told whom, and when.

{S['read_at'][:10]}Measured. Nobody has seen the page.

What we can’t be sure of

Run it yourself

git clone --filter=blob:none --no-checkout https://git.launchpad.net/ubuntu-cve-tracker uct
python3 known_dates.py exhibits/rows_2026-10-20.json uct known.json
python3 crit_dates.py exhibits/ubuntu_critical_cves_raw_2026-10-20.json uct crit.json
python3 fix_dates.py exhibits/ubuntu_critical_cves_raw_2026-10-20.json exhibits/ubuntu_releases_2026-10-20.json fixes.json
python3 analyze.py exhibits/ubuntu_critical_cves_raw_2026-10-20.json known.json crit.json exhibits/ubuntu_releases_2026-10-20.json uct fixes.json summary.json
# a fresh read of the live API instead of the saved one:
python3 census.py raw_new.json rows_new.json

Needs Python 3 and git; Ubuntu’s CVE API and Launchpad’s are anonymous. The tracker clone takes a few minutes; known_dates.py about two, crit_dates.py about 35, since it fetches each file’s history. Scripts and exhibits: sm-023/.

References

  1. Ubuntu Discourse, Ubuntu Pro FAQ, reply 23 by Maria Emilia Torino (Canonical), written 29 March 2023. discourse.ubuntu.com, saved copies ubuntu_pro_faq_raw_2026-10-20.md, discourse_ubuntu_pro_faq_post23_2026-10-20.json and its edit history.
  2. Ubuntu blog, Ubuntu Pro enters general availability, 26 January 2023. ubuntu.com, saved copy ubuntu-pro-enters-ga_2026-10-20.html. Also Alex Murray, Securing open source through CVE prioritisation, 26 March 2020: “the most vital updates are prepared, tested and released within 24 hours”, saved copy securing-open-source-through-cve-prioritisation_2026-10-20.html and the Internet Archive copy of 28 March 2020, wayback_cve_prioritisation_20200328.html; the live page now carries a 20 July 2023 date.
  3. Ubuntu, About CVEs: priority levels. ubuntu.com, saved copy security_cves_about_2026-10-20.html.
  4. Canonical blog, Accelerating delivery of CVE fixes with a new Kernel release strategy, 23 September 2026. canonical.com, saved copy canonical_blog_kernel_release_strategy_2026-10-20.html.
  5. Ubuntu, Expanded Security Maintenance (support schedule). ubuntu.com, saved copy security_esm_2026-10-20.html.
  6. Ubuntu CVE API, ubuntu.com/security/cves.json?priority=critical (saved: ubuntu_critical_cves_raw_2026-10-20.json); ubuntu.com/security/releases.json; Launchpad publishing history (api.launchpad.net, saved as fixes_2026-10-20.json); ubuntu-cve-tracker git history (priority changes saved as crit_2026-10-20.json).
""" if os.path.exists(OUT) and "published 2026-10-21" in open(OUT, encoding="utf-8").read() and "--force" not in sys.argv: sys.exit(f"{OUT} is the published page with publish-day edits; this render would drop them. Edit the page, or pass --force to a different OUT.") open(OUT, "w").write(head + top + body + foot) print("wrote", OUT, len(head + top + body + foot))