OpenAI’s “A Thousand Fewer Paths to Root” Turns $1.2 Million of API Credit Into 948 Candidate Patches, but Mainline Linux Shows 24 OpenAI-Linked Commits Since Mid-September and the Bounty Math Assumes Payouts the Rules Do Not Offer
OpenAI says its pipeline validated and generated patches for 948 Linux privilege escalations for $1.2 million; the body counts 1,169 exploits, mainline shows 24 OpenAI-linked commits since...
On November 4, OpenAI published A thousand fewer paths to root, a report on a pipeline that scans the Linux kernel for local privilege escalation (LPE) bugs, builds exploits for them and drafts patches. The page’s summary line says that in one run with its cyber model the pipeline “validated and generated patches for 948 local privilege escalation vulnerabilities in Linux at an API cost of $1.2 million.” The post then prices the work against bug bounty programs and concludes that the pipeline “was net profitable at the time of this research.”
Table Of Content
- What we found in short
- What OpenAI published
- What the headline counts
- 948 is a patch count, not an exploit count
- The $1.2 million leaves out patching
- Three smaller ambiguities
- How much of the 948 was already public
- What mainline Linux shows
- Method
- Results
- Who posted what
- Credit lines and models
- How old the bugs are
- The bounty arithmetic
- Pwn2Own Berlin 2026
- TyphoonPWN 2026
- kernelCTF
- What the valuation can carry
- What holds up
- What we would ask OpenAI
- What to do with this if you run Linux
- Run the checks yourself
- Script 1: the post’s own arithmetic
- Script 2: OpenAI-linked commits in mainline and the age of the bugs
- Script 3: the kernelCTF sheet
- Sources
We read the post against three outside records: the arithmetic of its own tables, the Linux mainline history through November 3 (shortly after v7.4-rc1), and the payout rules of the three contests it uses for pricing, Pwn2Own Berlin 2026, TyphoonPWN 2026 and Google’s kernelCTF. The cost figures recompute, the pipeline numbers hang together, and the post is open about several weak spots. The headline is looser than the body, though: it counts patches drafted rather than paths closed, and the public record of what has been closed is still short. Our earlier posts on Trail of Bits’ Codex backporting and on the Linux TCP bug behind Anthropic’s MPTCP exploit cover the patch side of the same shift.
What we found in short
- The headline number is a patch count. The body reports 1,169 working LPE exploits and then uses 948 distinct patches “to determine the overall number of unique LPEs.” The summary’s “$1,279 per LPE exploit” is the whole run divided by 948 unique bugs; per exploit it is $1,038.
- The five itemized stage costs add to $1,213,428 against a stated total of $1,213,430, so patch generation, the stage the title is about, has no line of its own.
- When the 10-day run ended, 61 of the 948 were patched upstream, 43 had staging patches and 299 were under public discussion, by the post’s own count. That is 403, or 42.5 percent, already known to someone else.
- In Linux mainline commits from September 15 through November 3 we found 24 linked to openai.com addresses, 19 of them authored from one. Twelve are tagged for stable. Their
Fixes:tags name 22 distinct bug-introducing commits, 19 of them in v6.12 or earlier, so the claim that stronger models reach older code holds up in this sample. - The $18.9 million to $66.3 million valuation multiplies a Pwn2Own prize that each registrant can win once, and a TyphoonPWN ceiling (“Up to $70,000 USD”), by 948. kernelCTF pays one winner per slot and requires SLAB_VIRTUAL, which OpenAI dropped from its run.
- What holds up: the cost arithmetic, the funnel percentages, and the post’s own disclosures of an early run in which 20 of 200 exploits used the wrong vulnerability and of its modified kernelCTF target.
What OpenAI published
The pipeline has five stages and a human at the end. Scout agents choose audit targets in the kernel, analyst agents scan them for candidate findings, and triage agents try to reproduce each candidate in a virtual machine running a kernel with the Kernel Address Sanitizer (KASAN). A screening agent then separates information leaks from privilege escalations, exploit-development agents try to turn each into a working exploit on VMs “configured with the protections enabled by a standard Linux distribution,” and variant analysis produces patches. “Human researchers reviewed the outputs and reported found vulnerabilities alongside patches,” the post says. Table 1 sets the printed counts and costs side by side.
| Stage | Output as printed in the post | Cost | Share of the total |
|---|---|---|---|
| Scout agents | 5,034 audit targets, about 2.8 million lines, roughly 7 percent of the kernel | $3,426 | 0.3 percent |
| Analyst agents | 2,308 candidate findings after deduplication, in 12 hours | $316,549 | 26.1 percent |
| Validation under KASAN | 1,724 reproduced (74.7 percent), in 30 hours | $73,330 | 6.0 percent |
| Exploitability screening | 313 information-leak and 1,238 LPE candidates (90.0 percent of the reproduced) | $44,194 | 3.6 percent |
| Exploit development | 291 information-leak proofs of concept and 1,169 working LPE exploits | $775,929 | 63.9 percent |
| Variant analysis and patching | 948 distinct patches, used as the count of unique LPEs | no line | not stated |
| Total | The post states $1,213,430; the five lines above sum to $1,213,428 | $1,213,430 | 100 percent |
Three more figures drive the conclusions. In a second campaign on “an adapted kernelCTF environment” the pipeline made 25 information-leak proofs of concept and 21 LPE exploits in 112 hours for about $32,936. By the time the main run ended, after 10 days, “61 LPEs had already been patched upstream, 43 had staging patches, and 299 were being reported and discussed publicly.” And the bounty section applies historical Pwn2Own and TyphoonPWN prizes ($20,000 and $70,000) to the 948 for “a hypothetical valuation” of $18.9 million to $66.3 million (the post truncates where we round: $18.96 million and $66.36 million), and a kernelCTF reward to the 21 for “around a $1.5M bounty award.”
What the headline counts
948 is a patch count, not an exploit count
The summary says the prototype “produced 948 local privilege escalation (LPE) exploits” and prices them at “$1,279 per LPE exploit.” The body is more careful. Its exploit stage “produced 291 proofs of concept for information leaks and 1,169 working local privilege escalation exploits,” and then: “We performed variant analysis and developed 948 distinct patches for the vulnerabilities behind those exploits. We use that number to determine the overall number of unique LPEs.” So 948 is the number of unique vulnerabilities inferred from the number of distinct patches, 81.1 percent of the exploit count. Divided by 1,169 exploits, the $1,213,430 run costs $1,038 each; divided by 948 unique bugs it costs $1,279.99, which the post prints as $1,279. Both are fair figures if you know which one you are reading, and the summary uses the word exploit for the second.
The $1.2 million leaves out patching
The five itemized lines (scout $3,426, analyst $316,549, validation $73,330, screening $44,194, exploitation $775,929) add to $1,213,428, and the stated total is $1,213,430. There is no line for patch generation or variant analysis. Either that work sits inside one of the five, which the post does not say, or it is outside the total. The title and summary are about patches, so the price of the patches is the one number a reader cannot get. The post does say its costs “are more an upper bound” and that it “focused on methodology and measuring model capability in this research, not cost.”
Three smaller ambiguities
The 3.1 percent. “Only 3.1% reflected incorrect analysis by the model,” the validation section says, without a denominator. Of the 2,308 candidates that is 72 findings; of the 584 that did not reproduce it is 18.
Unique is not novel. The summary says the October model found “>5x more novel LPEs” than a July run, while the body counts “unique” LPEs (948 against 166, or 5.7 times; the June run in the post had 24). Since 403 of the 948 were already patched, staged or under public discussion when the run ended, unique and novel are different sets.
The target. The exploits ran on “a stock desktop operating system, not a weakened target,” on “a commonly used Linux distribution,” and the post never names the distribution or the kernel version. It also says “For both campaigns, we scanned the latest Linux kernel master branch at the time,” and describes the exploit VMs as “configured with the protections enabled by a standard Linux distribution.” That can mean a distribution’s shipped kernel or a mainline build with distribution-style settings. The post does not say, and it matters below, because contest targets are specific kernels.
How much of the 948 was already public
The post is candid here: “When the autonomous scan and manual analysis finished after 10 days, 61 LPEs had already been patched upstream, 43 had staging patches, and 299 were being reported and discussed publicly.” That is 403 of 948, or 42.5 percent, and 104 of them (11.0 percent) were already fixed or staged by others. The post’s takeaway is practical: “Revalidating findings against the latest code” keeps duplicate reports from burdening maintainers.
The kernel’s own documentation explains why. The security-bugs guide in the tree we scanned has a section titled “Responsible use of AI to find bugs.” It says “A significant fraction of bug reports submitted to the security team are actually the result of code reviews assisted by AI tools,” that this “causes an overload on maintainers, who are sometimes forced to ignore such reports,” and that anyone who used AI assistance “must treat it as public” because “bugs discovered this way systematically surface simultaneously across multiple researchers, often on the same day.”
The commit record shows it happening. The mptcp fix c9c90cf437a9, committed October 16, credits three reporters: Kyle Zeng (writing from an openai.com address), a Trail of Bits engineer and “Vega” at nebusec.ai. Its Closes: link points to a Trail of Bits message on lore.kernel.org whose message ID is stamped August 4. The hfs/hfsplus fix dce0e0248205 credits Zeng and closes five syzbot reports. The scheduler fix d6f81fd6abe2, committed October 28, links a Trail of Bits posting whose ID is stamped August 5.
What mainline Linux shows
Method
We cloned the kernel’s commit history without file contents (--filter=tree:0, shallow since September 15, 11 MiB), pinned it at commit 05441d697f7f, the November 3 merge of cocci-7.4-rc1, and read about 13,000 non-merge commits (the exact count moves by a few with the shallow boundary the server picks). A commit counts as OpenAI-linked if its author address ends in @openai.com, or if an openai.com address appears in a Reported-by, Co-developed-by, Suggested-by, Tested-by, Reviewed-by, Acked-by, Signed-off-by or Cc line. Commits that credit a researcher by a personal address, or that fix an OpenAI report without crediting anyone, are invisible to this test, so the count is a floor.
Results
Twenty-four commits match. Nineteen are authored from an openai.com address (Kyle Zeng 17, Jeff Jo 2). Five more are by other authors, including two Trail of Bits engineers, and credit Zeng as reporter or carry his sign-off. A third address, [email protected], appears as the reporter on a KVM fix. Author dates run from September 17 to October 28, and ten fall on or after October 14. Thirteen of the commits touch networking, three memory management, two io_uring and six other areas (Table 2). Twelve carry Cc: stable, which makes them candidates for the stable trees that distributions track.
| Area | Commits | Subsystems |
|---|---|---|
| Networking | 13 | tcp (4, plus one selftest), netlink (2), netfilter, sctp, mptcp, net/sched, gro, net/packet |
| Memory management | 3 | mm/rmap, mm/userfaultfd, mm/page_vma_mapped |
| io_uring | 2 | io-wq worker type, foreign io-wq enqueues |
| Other | 6 | fsnotify, pipe, hfs/hfsplus, ALSA sequencer, KVM x86, keys |
The post says OpenAI has “already sent out hundreds of reports and patches for critical vulnerabilities.” Reports and patches sent are not commits merged, and many reports travel through the private security list or are fixed by maintainers in their own trees, so mainline is only one end of the pipe. It is the end that is public and checkable, though, and it shows two dozen fixes where the title says a thousand paths. The title is a forecast of what the pipeline can find, not a count of what has been closed, and the post gives no number of patches merged or assigned a CVE.
Who posted what
The last batch is telling. Five networking fixes authored on October 28 (three tcp, one gro, one net/packet) all link to postings by Eric Dumazet, and all five were committed the next day. Two earlier fixes have Trail of Bits authors and carry Zeng’s sign-off. So the path to mainline runs through maintainers and partners as well as through the pipeline, which fits the post’s thanks to “the Linux maintainers who have been working urgently to patch identified issues.” The history does not show which patch texts the pipeline wrote.
Credit lines and models
Ten of the 19 OpenAI-authored commits carry an Assisted-by trailer. Five name Codex models (gpt-6-astra, gpt-5.6-sol and gpt-5.5-cyber all appear) and five say only LLM; none names the unreleased cyber model that the post says drove the October run. The nine commits without a trailer include all seven authored on or after October 25. The kernel dropped Assisted-by from its documentation in the 7.4 window, as we reported, and these commits show that disclosure was uneven even before that.
How old the bugs are
Twenty-two of the 24 commits carry a Fixes: tag naming the commit that introduced the bug. We asked GitHub’s compare API whether each of those 22 targets is inside v6.12. Nineteen are, among them the 2005 initial import (the hfs/hfsplus fix), and ten date from 2017 or earlier. Three came later, in October 2025, March 2026 and June 2026. The median is early 2018 (Table 3). That supports the post’s statement that stronger models find vulnerabilities “often including ones that have been around longer,” at least in this sample of what has merged.
| Bug introduced | Fix targets |
|---|---|
| 2005 to 2009 | 4 |
| 2013 to 2017 | 6 |
| 2018 to 2022 | 5 |
| 2023 to v6.12 (November 2024) | 4 |
| After v6.12 | 3 |
It also matters for pricing. kernelCTF requires that “the underlying vulnerability must be triggerable on the latest LTS 6.12,” and Red Hat lists 6.12.0-124.8.1.el10_1 as the kernel of RHEL 10.1, the release Pwn2Own’s rules name for their virtualization targets. A bug introduced after v6.12 cannot be in those kernels unless it was backported, and three of these 22 fix targets were introduced after v6.12.
The bounty arithmetic
This is where the post leans furthest. It converts counts into dollars with three programs, and each program’s rules decide what can actually be won. Table 4 puts OpenAI’s multiplications next to the published rules.
| Program | What OpenAI multiplied | What the published rules say |
|---|---|---|
| Pwn2Own Berlin 2026 | 948 times $20,000, or $18.96 million | Linux LPE prize of $20,000; “The contestant can only register once per target”; “Each company is limited to one registration” |
| TyphoonPWN 2026 | 948 times $70,000, or $66.36 million | “Up to $70,000 USD” for Linux privilege escalation, in an event whose competitors share “hundreds of thousands of dollars in prizes” |
| kernelCTF | 21 times $71,337, or $1,498,077 | $71,337 per eligible exploit; one winner per slot; first submission per vulnerability only; hardened 7.2-rc5 target with SLAB_VIRTUAL=y |
Pwn2Own Berlin 2026
The Local Escalation of Privilege category pays $20,000 for Red Hat Enterprise Linux for Workstations, and an entry “must leverage a kernel vulnerability to execute arbitrary code with elevated privileges” from a non-admin, non-root account. The same rules say “Every entry must be a separate and unique exploit chain,” so one registrant can collect the Linux prize once, and 948 times $20,000 is not a payout the contest offers. For scale, ZDI’s announcement says the 2025 Berlin event “awarded $1,078,750 for 28 unique 0-days” and that 2026 had “more than $1,000,000” on offer across 31 targets. The low end of OpenAI’s range ($18.96 million) is 17.6 times the whole 2025 Berlin payout, and the high end ($66.36 million) is 61.5 times.
TyphoonPWN 2026
The post says Pwn2Own and TyphoonPWN “offered a $20,000 and $70,000 bug bounty.” The TyphoonPWN page lists “Linux Privilege Escalation” with the words “Up to $70,000 USD,” which is a ceiling rather than a price. Multiplying a maximum by 948 gives the $66.3 million figure.
kernelCTF
kernelCTF is Google’s reward program for 0-day Linux kernel exploits. Under the rules in force since September 4 the reward is a single static $71,337 per eligible exploit, and 21 times that is $1,498,077, the post’s “$1.5M.” The rules narrow “eligible” in ways that the post’s one-line multiplication does not reflect:
- One winner per slot. “Only one submission per researcher is eligible per slot,” the window closes “at which point the winning submission will be selected,” and “Only one submission is eligible per kernel version.”
- First come only. “Only the first submission for a vulnerability is eligible for a reward.”
- 0-days only. “Submissions can only target 0-day vulnerabilities,” meaning “There is no patch commit in the mainline tree” and the bug is “not disclosed in any form.” By the post’s own count 403 of the 948 were already patched, staged or publicly discussed when the run ended, so many would not meet that test.
- A fixed, hardened target. Linux 7.2-rc5 with kCFI, IBT, SLAB_VIRTUAL=y and a reduced attack surface (CONFIG_IO_URING=n, CONFIG_USER_NS=n, CONFIG_TLS=n and more). OpenAI ran “the latest version of the Linux kernel (incompatible with the SLAB_VIRTUAL mitigation in kernelCTF so we dropped it from the config)” and calls the result “an adapted kernelCTF environment.”
- A bug that reaches LTS 6.12, reliably. The vulnerability must trigger on LTS 6.12, and a submission needs “at least 70% stability based on 20 exploit runs,” each within 5 minutes.
The public submission sheet shows how that plays out. Its last row is dated August 26, before the rule change, and it holds 235 0-day flag submissions with 2026 dates (Table 5). Of those, 206 are marked (dupe) and 25 are neither duplicates nor revoked, close to the post’s count: “26 confirmed 0-day LPE exploits have been accepted by kernelCTF in 2026 at the time of writing.” Each row is a flag submission, so one binary can fill many rows (the most repeated duplicate-only binary fills 81). Counted by exploit hash, 93 distinct exploits were submitted, 23 won and 66 were only ever duplicates. In August all 163 rows were duplicates.
| 0-day rows with a 2026 flag time, through August 26 | Rows | Share |
|---|---|---|
| Marked (dupe) | 206 | 87.7 percent |
| Revoked | 4 | 1.7 percent |
| Won a named slot | 22 | 9.4 percent |
| Won, no slot name (two mitigation-v4 flags, one invalid-signature flag) | 3 | 1.3 percent |
| Total | 235 | 100 percent |
What the valuation can carry
Read as a statement about cost, the post is strong: about $1,280 per unique LPE, down from about $3,095 in July, for a pipeline that found 5.7 times as many. Read as revenue, it is not supported. Pwn2Own pays one entry per registrant, TyphoonPWN publishes a ceiling, and kernelCTF pays one winner per slot and only for the first submission of a bug, on a hardened kernel that OpenAI did not use as published. The post calls its figure hypothetical and then rests its takeaway on it: “Based on public pay-out amounts” the pipeline “was net profitable at the time of this research.” A fairer reading of the same data, and this part is our inference, is that finding and exploiting kernel LPEs now costs on the order of a thousand dollars each, and that programs built around one prize per registrant or per slot cannot price a pipeline of this volume.
What holds up
- The arithmetic. The percentages that come with a denominator recompute (74.7 percent reproduced, about 7 percent of the kernel audited, 76.2 and 88.9 percent of 63 candidates), the stage lines reproduce the total within $2, and the per-case exploit averages rebuild the $691,000 LPE line to within $458.
- Disclosed failures. In an early run, “20 of these had achieved root privileges through a different 0-day vulnerability than the one assigned,” and the team added “an independent check confirming that each final exploit used its assigned vulnerability.” The kernelCTF target change is stated in the text, and so is the point that costs are “more an upper bound.”
- Overlap with others. The 61, 43 and 299 are in the post, not buried.
- Old bugs. The bugs behind the merged fixes mostly predate v6.12.
- Review by other developers. The merged commits carry Reviewed-by and Acked-by lines from other kernel developers, and the kernel’s guidance for AI-found bugs (a tested fix with a
Fixes:tag, a plain-text report, a reproducer on request) matches the bundle the post says each finding ships with: “a root cause analysis, a minimal crashing proof-of-concept, a candidate patch, and a full exploit.”
What we would ask OpenAI
- Which distribution, kernel version and build did the 948 exploits run on, and how many of the 948 exist in v6.12, 6.6 and 6.1 and in that distribution’s shipped kernel?
- How many reports and patches have been sent, how many merged, and how many have CVE IDs? Would OpenAI publish the list with commit IDs as they land?
- Is patch generation inside the $1,213,430, and what did it cost?
- How was “publicly discussed” decided for the 299, and how many of the other 545 had been reported by OpenAI before anyone else posted?
- After the independent check was added, how many final exploits were rejected as using the wrong vulnerability?
- How many of the 21 kernelCTF exploits work on the unmodified target, with SLAB_VIRTUAL enabled, and on LTS 6.12?
What to do with this if you run Linux
The post’s own advice survives every check above: “having access to a low-privileged account on an operating system, that isn’t up-to-date on its latest available patches should be considered functionally equivalent to root access for a well-resourced threat actor.” Three practical consequences follow.
- Stable updates are the delivery vehicle. Twelve of the 24 mainline commits are tagged
Cc: stable. Check your distribution’s kernel changelog for these subjects (the second script prints them) rather than waiting for CVE numbers, since distributions can lag, as in our Infinity Core report. - Treat shared local access as the exposed surface. CI runners, shared shells, build hosts and multi-tenant containers on one kernel all give an attacker the low-privileged account the post worries about. The post names Firecracker and gVisor as ways to limit the blast radius.
- Remove surface you do not use. The kernelCTF hardened target compiles out io_uring, user namespaces and in-kernel TLS, and two of the 24 commits fix io_uring. The kernel’s sysctl guide documents
kernel.io_uring_disabled: “Enabling this shrinks the kernel’s attack surface,” and with a value of 2io_uring_setup()“always fails with -EPERM,” so test software that uses io_uring before you set it.
Run the checks yourself
Three short scripts reproduce every count above that is not simply quoted. The first uses only figures printed in the post and needs no network. The second reads the pinned commit history (setup below, about five minutes) and asks GitHub’s compare API whether each Fixes: target is inside v6.12. It makes about 25 anonymous calls, so it keeps the answers in ages.json. The third downloads kernelCTF’s public sheet and cuts it at August 26 so the numbers stay put.
git clone --filter=tree:0 --no-checkout --shallow-since=2026-09-15 --single-branch --branch master https://github.com/torvalds/linux.git linux
python -I paths_arithmetic.py
python -I openai_commits.py linux
python -I kernelctf_sheet.py
Script 1: the post’s own arithmetic
# paths_arithmetic.py
"""paths_arithmetic.py: recompute the figures in OpenAI's "A thousand fewer paths to root" (2026-11-04).
Every input below is a number printed in the post. Standard library only; no network."""
# Funnel counts
targets, lines_audited, kernel_lines = 5034, 2_800_000, 40_000_000
candidates, reproduced = 2308, 1724
leak_cand, lpe_cand = 313, 1238
leak_pocs, lpe_exploits = 291, 1169
patches = 948 # "948 distinct patches ... We use that number to determine the overall number of unique LPEs"
# Stage costs in US dollars, as printed
cost = {"scout": 3426, "analyst": 316_549, "validation": 73_330, "screening": 44_194, "exploitation": 775_929}
total_stated = 1_213_430
lpe_stage, leak_stage = 691_000, 84_929
ok_cases, ok_avg, bad_cases, bad_avg = 1169, 360, 69, 3922
print("== Funnel")
print(f"audit targets: {targets:,} cover {lines_audited / 1e6:.1f}M of {kernel_lines / 1e6:.0f}M lines = {lines_audited / kernel_lines:.1%}")
print(f"reproduced: {reproduced:,} of {candidates:,} candidates = {reproduced / candidates:.1%}")
print(f"screened for exploitation: {leak_cand + lpe_cand:,} of {reproduced:,} = {(leak_cand + lpe_cand) / reproduced:.1%}")
print(f"leak PoCs: {leak_pocs} of {leak_cand} = {leak_pocs / leak_cand:.1%}; LPE exploits: {lpe_exploits:,} of {lpe_cand:,} = {lpe_exploits / lpe_cand:.1%}")
print(f"unique LPEs (distinct patches): {patches} of {lpe_exploits:,} exploits = {patches / lpe_exploits:.1%}")
print(f"3.1% 'incorrect analysis': {0.031 * candidates:.0f} of {candidates:,} candidates, or {0.031 * (candidates - reproduced):.0f} of the {candidates - reproduced} not reproduced")
print("\n== Cost")
s = sum(cost.values())
print(f"five stage lines add to ${s:,}; the post states ${total_stated:,} (difference ${total_stated - s}); no patch-generation line")
print(f"exploitation stage = leak ${leak_stage:,} + LPE ${lpe_stage:,} = ${leak_stage + lpe_stage:,} (stage line ${cost['exploitation']:,})")
print(f"LPE stage rebuilt from per-case averages: {ok_cases:,} x ${ok_avg} + {bad_cases} x ${bad_avg:,} = ${ok_cases * ok_avg + bad_cases * bad_avg:,}")
print("share of total: " + ", ".join(f"{k} {v / total_stated:.1%}" for k, v in cost.items()) + f"; discovery (scout + analyst) {(cost['scout'] + cost['analyst']) / total_stated:.1%}")
print(f"per working LPE exploit: ${total_stated / lpe_exploits:,.0f}; per unique LPE: ${total_stated / patches:,.2f} (post prints $1,279)")
print("\n== Already public when the run ended (day 10)")
fixed, staged, discussed = 61, 43, 299
pub = fixed + staged + discussed
print(f"{fixed} patched + {staged} staged + {discussed} discussed = {pub} of {patches} = {pub / patches:.1%}; remainder {patches - pub}")
print(f"patched or staged only: {fixed + staged} = {(fixed + staged) / patches:.1%}")
print("\n== Bounty valuation")
print(f"948 x $20,000 = ${patches * 20_000:,}; 948 x $70,000 = ${patches * 70_000:,} (post prints $18.9M and $66.3M)")
print(f"as multiples of the run cost: {patches * 20_000 / total_stated:.1f}x to {patches * 70_000 / total_stated:.1f}x (post: 16x to 55x)")
print(f"Pwn2Own Berlin 2025 paid $1,078,750 for 28 unique 0-days: low end is {patches * 20_000 / 1_078_750:.1f}x that, high end {patches * 70_000 / 1_078_750:.1f}x")
print(f"kernelCTF: 21 x $71,337 = ${21 * 71_337:,}; over $32,936 = {21 * 71_337 / 32_936:.1f}x (post: about $1.5M, about 45x)")
print("\n== July versus October")
jul_n, jul_cost = 166, 513_690
print(f"unique LPEs {jul_n} -> {patches} = {patches / jul_n:.1f}x; run cost ${jul_cost:,} -> ${total_stated:,} = {total_stated / jul_cost:.1f}x")
print(f"cost per unique LPE ${jul_cost / jul_n:,.0f} -> ${total_stated / patches:,.0f} = {jul_cost / jul_n / (total_stated / patches):.1f}x cheaper")
print(f"orchestration test: 76.2% of 63 = {0.762 * 63:.1f}; 88.9% of 63 = {0.889 * 63:.1f}")
print(f"wrong-0-day check: 20 of 200 early-run exploits = {20 / 200:.0%}")
== Funnel
audit targets: 5,034 cover 2.8M of 40M lines = 7.0%
reproduced: 1,724 of 2,308 candidates = 74.7%
screened for exploitation: 1,551 of 1,724 = 90.0%
leak PoCs: 291 of 313 = 93.0%; LPE exploits: 1,169 of 1,238 = 94.4%
unique LPEs (distinct patches): 948 of 1,169 exploits = 81.1%
3.1% 'incorrect analysis': 72 of 2,308 candidates, or 18 of the 584 not reproduced
== Cost
five stage lines add to $1,213,428; the post states $1,213,430 (difference $2); no patch-generation line
exploitation stage = leak $84,929 + LPE $691,000 = $775,929 (stage line $775,929)
LPE stage rebuilt from per-case averages: 1,169 x $360 + 69 x $3,922 = $691,458
share of total: scout 0.3%, analyst 26.1%, validation 6.0%, screening 3.6%, exploitation 63.9%; discovery (scout + analyst) 26.4%
per working LPE exploit: $1,038; per unique LPE: $1,279.99 (post prints $1,279)
== Already public when the run ended (day 10)
61 patched + 43 staged + 299 discussed = 403 of 948 = 42.5%; remainder 545
patched or staged only: 104 = 11.0%
== Bounty valuation
948 x $20,000 = $18,960,000; 948 x $70,000 = $66,360,000 (post prints $18.9M and $66.3M)
as multiples of the run cost: 15.6x to 54.7x (post: 16x to 55x)
Pwn2Own Berlin 2025 paid $1,078,750 for 28 unique 0-days: low end is 17.6x that, high end 61.5x
kernelCTF: 21 x $71,337 = $1,498,077; over $32,936 = 45.5x (post: about $1.5M, about 45x)
== July versus October
unique LPEs 166 -> 948 = 5.7x; run cost $513,690 -> $1,213,430 = 2.4x
cost per unique LPE $3,095 -> $1,280 = 2.4x cheaper
orchestration test: 76.2% of 63 = 48.0; 88.9% of 63 = 56.0
wrong-0-day check: 20 of 200 early-run exploits = 10%
Script 2: OpenAI-linked commits in mainline and the age of the bugs
# openai_commits.py
"""openai_commits.py: which commits in Linux mainline link to an openai.com address, and how old are the bugs they fix?
Setup (about 5 minutes, 11 MiB, commit objects only, no files checked out):
git clone --filter=tree:0 --no-checkout --shallow-since=2026-09-15 --single-branch --branch master https://github.com/torvalds/linux.git linux
Run: python -I openai_commits.py linux
The age step asks the GitHub compare API (60 anonymous calls an hour) whether each Fixes: target is inside v6.12 and caches
the answers in ages.json next to this file."""
import json
import os
import re
import subprocess
import sys
import urllib.error
import urllib.request
from collections import Counter
REPO = sys.argv[1] if len(sys.argv) > 1 else "linux"
PIN = "05441d697f7f9a927264f4058cbbc6a03dfcac9d" # merge of cocci-7.4-rc1, 2026-11-03
SINCE = "2026-09-15"
CACHE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ages.json")
CREDIT = re.compile(r"(?im)^(reported-by|co-developed-by|suggested-by|tested-by|reviewed-by|acked-by|signed-off-by|cc):.*openai")
RS, FS = "\x1e", "\x1f"
raw = subprocess.run(
["git", "-C", REPO, "log", PIN, "--no-merges", f"--since={SINCE}", "--format=%H%x1f%aI%x1f%cI%x1f%an%x1f%ae%x1f%s%x1f%B%x1e"],
capture_output=True, text=True, encoding="utf-8", errors="replace", check=True).stdout
rows = []
for rec in raw.split(RS):
p = rec.strip("\n").split(FS)
if len(p) >= 7:
rows.append(dict(h=p[0], ad=p[1][:10], cd=p[2][:10], an=p[3], ae=p[4], s=p[5], b=FS.join(p[6:])))
# the exact count moves by a few commits with the shallow boundary the server picks, so print it rounded
print(f"pinned at {PIN[:12]}; non-merge commits since {SINCE}: about {round(len(rows), -2):,} (committer dates {min(r['cd'] for r in rows)} to {max(r['cd'] for r in rows)})")
hits = []
for r in rows:
own = r["ae"].lower().endswith("@openai.com")
if own or CREDIT.search(r["b"]):
r["own"] = own
r["stable"] = bool(re.search(r"(?im)^cc:.*stable@", r["b"]))
r["assist"] = re.findall(r"(?im)^assisted-by:\s*(.+)$", r["b"])
r["fixes"] = re.findall(r"(?im)^fixes:\s*([0-9a-f]{8,40})\s*(?:\((.*)\))?", r["b"])
hits.append(r)
hits.sort(key=lambda r: (r["ad"], r["h"]))
own = [r for r in hits if r["own"]]
print(f"OpenAI-linked commits: {len(hits)}")
print(f" authored from an openai.com address: {len(own)} ({', '.join(f'{a} {n}' for a, n in Counter(r['ae'] for r in own).most_common())})")
print(f" other authors, openai.com address in a credit or sign-off line: {len(hits) - len(own)}")
print(f" openai.com addresses seen: {', '.join(sorted(set(a.lower() for r in hits for a in re.findall(r'<([\w.+-]+@openai\.com)>', r['b'], re.I))))}")
print(f" Cc: stable: {sum(r['stable'] for r in hits)}; Fixes: tag: {sum(bool(r['fixes']) for r in hits)}; Assisted-by trailer: {sum(bool(r['assist']) for r in hits)} (own: {sum(bool(r['assist']) for r in own)})")
print(f" author dates {hits[0]['ad']} to {hits[-1]['ad']}; on or after 2026-10-14: {sum(r['ad'] >= '2026-10-14' for r in hits)}")
print()
print(f"{'commit':<13}{'author date':<13}{'author':<26}{'stable':<7}subject")
for r in hits:
print(f"{r['h'][:12]:<13}{r['ad']:<13}{r['an'][:24]:<26}{'yes' if r['stable'] else '-':<7}{r['s'][:78]}")
ages = json.load(open(CACHE)) if os.path.exists(CACHE) else {}
HDR = {"User-Agent": "sxz.io editorial research tool (+https://sxz.io)", "Accept": "application/vnd.github+json"}
def api(path):
try:
with urllib.request.urlopen(urllib.request.Request("https://api.github.com/repos/torvalds/linux/" + path, headers=HDR), timeout=40) as resp:
return json.load(resp)
except urllib.error.HTTPError as e:
return {"_http": e.code}
FULL = {"1da177e4c3f4": "1da177e4c3f41524e886b7f1b8a0c1fc7321cac2"} # GitHub cannot resolve this short SHA (the 2005 root import)
def age(sha):
if sha in ages:
return ages[sha]
cmp_ = api(f"compare/v6.12...{sha}?per_page=1")
if "_http" in cmp_: # a root commit has no merge base with v6.12
c = api(f"commits/{FULL.get(sha, sha)}")
ages[sha] = dict(inside=not c["parents"], date=c["commit"]["author"]["date"][:10], note="root commit")
elif cmp_["status"] in ("behind", "identical"):
ages[sha] = dict(inside=True, date=cmp_["merge_base_commit"]["commit"]["author"]["date"][:10], note="")
else:
ages[sha] = dict(inside=False, date=api(f"commits/{sha}")["commit"]["author"]["date"][:10], note="")
json.dump(ages, open(CACHE, "w"), indent=1)
return ages[sha]
targets = {}
for r in hits:
for sha, title in r["fixes"]:
targets.setdefault(sha, title)
print(f"\ndistinct Fixes: targets: {len(targets)}")
info = {sha: age(sha) for sha in targets}
inside = sum(v["inside"] for v in info.values())
print(f" already inside v6.12: {inside}; introduced after v6.12: {len(info) - inside}")
dates = sorted(v["date"] for v in info.values())
print(f" introduced {dates[0]} to {dates[-1]}; before 2018: {sum(d < '2018' for d in dates)}; median {dates[len(dates) // 2 - 1]} / {dates[len(dates) // 2]}")
for sha, v in sorted(info.items(), key=lambda kv: kv[1]["date"]):
print(f" {v['date']} {'v6.12 or older' if v['inside'] else 'after v6.12 '} {sha[:12]} {targets[sha][:62]}")
pinned at 05441d697f7f; non-merge commits since 2026-09-15: about 13,000 (committer dates 2026-09-15 to 2026-11-03)
OpenAI-linked commits: 24
authored from an openai.com address: 19 ([email protected] 17, [email protected] 2)
other authors, openai.com address in a credit or sign-off line: 5
openai.com addresses seen: [email protected], [email protected], [email protected]
Cc: stable: 12; Fixes: tag: 22; Assisted-by trailer: 12 (own: 10)
author dates 2026-09-17 to 2026-10-28; on or after 2026-10-14: 10
commit author date author stable subject
95b2e0361c88 2026-09-17 Kyle Zeng yes ALSA: seq: Serialize compat port-info ioctls
e70f812ccdd6 2026-09-17 Kyle Zeng - KVM: x86: Restrict saved GPA writes to hardware write faults
dce0e0248205 2026-09-21 Bruno Produit - hfs/hfsplus: serialize B-tree close against folio release
1b92780ae4fc 2026-09-24 Jeff Jo - tcp: refresh TS.Recent for accepted old ACKs
81fe707ae58e 2026-09-24 Jeff Jo - selftests: net: check timestamp echo after an old ACK
36ebddca56b9 2026-10-02 Kyle Zeng yes fsnotify: prevent mark additions during connector teardown
40bb03d3ea6f 2026-10-02 Kyle Zeng - pipe: preserve existing fasync readers on writer setup failure
8bf659bd54e1 2026-10-06 Kyle Zeng - netlink: reset skb headers before unicast and multicast filtering
73a73d442f99 2026-10-07 Kyle Zeng yes netfilter: nf_reject: initialize IPCB at inet ingress
f88bb3606e4b 2026-10-07 Kyle Zeng - netlink: avoid hashing the network namespace pointer
ad1320865b18 2026-10-09 David Lee - sctp: hold shkey across socket migration
984c0700854e 2026-10-12 Zi Yan yes mm/rmap: don't discard lazyfree THPs at PMD level
c9c90cf437a9 2026-10-13 Ruide Cao yes mptcp: hold MP_JOIN msk ref when cloning reqsk
de891c73c1cf 2026-10-13 Kyle Zeng yes keys: make keyring indexes independent of kernel addresses
514c9247db30 2026-10-14 David Hildenbrand (Arm) yes mm/userfaultfd: only allocate anon_vma in CoW mappings
1a2993227e8a 2026-10-15 Kyle Zeng yes mm/page_vma_mapped: continue past nonmatching huge PMDs
d6f81fd6abe2 2026-10-23 Kyle Zeng yes net: sched: prevent scheduling deactivated qdiscs
37e425460b7c 2026-10-25 Kyle Zeng yes io_uring/io-wq: save worker type before dropping its reference
3de16279b43d 2026-10-25 Kyle Zeng - io_uring: defer foreign io-wq enqueues to task_work
b7ff0e7a9ae3 2026-10-28 Kyle Zeng - tcp: initialize mapped children before route lookup
c1df9c0c0536 2026-10-28 Kyle Zeng yes tcp: fix reordering addition overflow in MTU probing
dbd62ad55a4c 2026-10-28 Kyle Zeng - tcp: avoid double destroy in tcp_abort() of an unaccepted child
dc4dd12f80e4 2026-10-28 Kyle Zeng yes net/packet: check TX_RING fragments before memory accounting
de788d8a7032 2026-10-28 Kyle Zeng - gro: do not GRO GSO packets with inconsistent payload
distinct Fixes: targets: 22
already inside v6.12: 19; introduced after v6.12: 3
introduced 2005-04-16 to 2026-06-15; before 2018: 10; median 2018-01-09 / 2018-03-14
2005-04-16 v6.12 or older 1da177e4c3f4 "Linux-2.6.12-rc2"
2008-03-21 v6.12 or older b1153f29ee07 "netlink: make socket filters work on netlink"
2009-02-01 v6.12 or older 76398425bb06 "Move FASYNC bit handling to f_op->fasync()"
2009-05-19 v6.12 or older 69e3c75f4d54 "net: TX_RING and packet mmap"
2013-04-19 v6.12 or older 12fb3dd9dc3c "tcp: call tcp_replace_ts_recent() from tcp_ack()"
2013-09-24 v6.12 or older b2a4df200d57 "KEYS: Expand the capacity of a keyring"
2015-03-20 v6.12 or older c428ecd1a21f "netlink: Move namespace into hash key"
2016-12-14 v6.12 or older 0f89b207b04a "kvm: svm: Use the hardware provided GPA instead of page walk"
2016-12-21 v6.12 or older 6b3f05d24d35 "fsnotify: Detach mark from object list when last reference is
2017-02-24 v6.12 or older ace71a19cec5 "mm: introduce page_vma_mapped_walk()"
2018-01-09 v6.12 or older b3defb791b26 "ALSA: seq: Make ioctls race-free"
2018-03-14 v6.12 or older 1b1e0bc99474 "sctp: add refcnt support for sh_key"
2020-10-31 v6.12 or older 117ca1f8920c "netfilter: nft_reject_inet: allow to use reject from inet ing
2021-06-12 v6.12 or older c905dee62232 "tcp: Migrate TCP_NEW_SYN_RECV requests at retransmitting SYN+
2022-09-30 v6.12 or older 5eddb24901ee "gro: add support of (hw)gro packets to gro stack"
2023-01-08 v6.12 or older e6db6f9398da "io_uring/io-wq: only free worker if it was allocated for crea
2024-02-15 v6.12 or older 867a43a34ff8 "userfaultfd: use per-vma locks in userfaultfd operations"
2024-06-14 v6.12 or older 735ecdfaf4e8 "mm/vmscan: avoid split lazyfree THP during shrink_folio_list(
2024-08-26 v6.12 or older bac76cf89816 "tcp: fix forever orphan socket caused by tcp_abort"
2025-10-14 after v6.12 1c17f4373d4d "ipv6: Move ipv6_fl_list from ipv6_pinfo to inet_sock."
2026-03-07 after v6.12 47e8dbb6e763 "net/sched: do not reset queues in graft operations"
2026-06-15 after v6.12 efb8763d7bbb "net: ipv4: bound TCP reordering sysctl writes and MTU probe s
Script 3: the kernelCTF sheet
# kernelctf_sheet.py
"""kernelctf_sheet.py: how many 2026 kernelCTF 0-day submissions were wins, and how many were duplicates?
Reads Google's public submission sheet (the one linked from the kernelCTF rules page) as CSV. Standard library only.
The sheet's last row was dated 2026-08-26 when this was written, so rows are cut at that date to keep the numbers stable."""
import csv
import io
import urllib.request
from collections import Counter
URL = ("https://docs.google.com/spreadsheets/d/e/2PACX-1vS1REdTA29OJftst8xN5B5x8iIUcxuK6bXdzF8G1UXCmRtoNsoQ9MbebdRdFnj6qZ0Yd7LwQfvYC2oF/pub"
"?gid=2095368189&single=true&output=csv")
CUT = "2026-08-27"
req = urllib.request.Request(URL, headers={"User-Agent": "Mozilla/5.0"})
text = urllib.request.urlopen(req, timeout=60).read().decode("utf-8")
rows = list(csv.DictReader(io.StringIO(text)))
y26 = [r for r in rows if r["Flag submission time"].startswith("2026") and r["Flag submission time"] < CUT]
zero = [r for r in y26 if r["0-day / 1-day"] == "0-day"]
print(f"sheet rows: {len(rows)}; newest flag time {max(r['Flag submission time'] for r in rows)[:10]}")
print(f"2026 rows: {len(y26)}; of them 0-day: {len(zero)}")
def kind(r):
slot = (r["LTS slot"] or r["COS slot"]).strip()
return "dupe" if slot == "(dupe)" else "revoked" if slot == "(revoked)" else "named slot" if slot else "no slot name"
k = Counter(kind(r) for r in zero)
for name in ("dupe", "revoked", "named slot", "no slot name"):
print(f" {name:<13}{k[name]:>4} {k[name] / len(zero):.1%}")
wins = k["named slot"] + k["no slot name"]
print(f"neither dupe nor revoked: {wins}")
print("no slot name rows:", ", ".join(f"{r['ID']} ({r['Flags'][10:34]})" for r in zero if kind(r) == "no slot name"))
# One exploit binary can appear in many rows (every captured flag is a row), so also count distinct exploit hashes.
status = {}
for r in zero:
status.setdefault(r["Exploit hash"], set()).add(kind(r))
won = [h for h, v in status.items() if v & {"named slot", "no slot name"}]
dupe_only = [h for h, v in status.items() if v == {"dupe"}]
print(f"distinct exploit hashes: {len(status)}; with a win: {len(won)}; only ever (dupe): {len(dupe_only)}; revoked only: {len(status) - len(won) - len(dupe_only)}")
top = Counter(r["Exploit hash"] for r in zero if r["Exploit hash"] in dupe_only).most_common(1)[0]
print(f"most repeated dupe-only exploit: {top[1]} rows ({top[0][:12]}...)")
by_month = Counter(r["Flag submission time"][:7] for r in zero)
dupe_month = Counter(r["Flag submission time"][:7] for r in zero if kind(r) == "dupe")
print("0-day rows by month (dupes in brackets):", ", ".join(f"{m} {by_month[m]} ({dupe_month[m]})" for m in sorted(by_month)))
sheet rows: 763; newest flag time 2026-08-26
2026 rows: 438; of them 0-day: 235
dupe 206 87.7%
revoked 4 1.7%
named slot 22 9.4%
no slot name 3 1.3%
neither dupe nor revoked: 25
no slot name rows: exp487 (v1:mitigation-v4-6.12:17), exp450 (ag (signature error)), exp443 (v1:mitigation-v4-6.12:17)
distinct exploit hashes: 93; with a win: 23; only ever (dupe): 66; revoked only: 4
most repeated dupe-only exploit: 81 rows (ba265e992abb...)
0-day rows by month (dupes in brackets): 2026-01 6 (0), 2026-02 6 (0), 2026-03 11 (5), 2026-04 13 (9), 2026-05 3 (1), 2026-06 13 (10), 2026-07 20 (18), 2026-08 163 (163)
Sources
- OpenAI, A thousand fewer paths to root (November 4, 2026).
- kernelCTF rules (in force from 2026-09-04) and its public submission sheet.
- Zero Day Initiative, Announcing Pwn2Own Berlin for 2026 and the contest rules.
- TyphoonPWN 2026.
- Red Hat, Red Hat Enterprise Linux release dates, which lists kernel versions per release.
- Linux kernel at commit 05441d697f7f: Documentation/process/security-bugs.rst and Documentation/admin-guide/sysctl/kernel.rst.
The OpenAI page, the contest rules and the kernelCTF sheet were read on November 5, 2026; the kernelCTF sheet’s last row is dated August 26. Commit counts are pinned to 05441d697f7f. Dollar figures are as printed in the sources, and the ratios are our arithmetic.








No Comment! Be the first one.