Lessons from 59 Linux Kernel Bug Reports

At Microsoft’s FORGE Lab, we used codename MDASH to scan a subset of Linux kernel subsystems from late May to mid-June 2026. One full-time researcher and one intern led this three-month effort, which has so far produced 59 public upstream reports and 33 assigned CVEs. We confirmed working root privilege-escalation exploits for eight of those CVEs, and fixes for 38 of our reports have been merged into mainline.
Finding the bugs was only part of the work. The initial scans produced 26,502 raw findings in about two weeks, but turning those results into reproducible bugs, upstream reports, and patches took months of expert investigation and collaboration with Linux maintainers.
In this post, we look back at how we went from thousands of potential findings to validated vulnerabilities and upstream fixes, and what we learned about scaling vulnerability discovery with AI.
From scans to reports
We focused on several core kernel subsystems, such as networking, filesystems, and security modules. Our selection criteria were straightforward: we picked security-sensitive components with no obvious dependence on specialized hardware, making QEMU-based validation simple and unified. Code scanning mainly used Sonnet 4.6 (71.1% of code-scanning tokens), GPT-5.4 (20.1%), and GPT-5.5 (7.1%).
The codename MDASH scans produced 26,502 raw scan findings in about two weeks on two servers (64 cores/256 GB RAM and 32 cores/128 GB RAM), but turning that output into patches and reports took months of expert work. We filtered out low-confidence claims using codename MDASH’s confidence levels and separate fast screening agents we built, reducing the pool to 11,769 candidates. PoC-generating agents then attempted reproduction in QEMU, mainly using GPT-5.5 (77.3% of reproduction tokens), Opus 4.6 (13.2%), and GPT-5.5-cyber (9.4%). Experts then reviewed the 621 cases with PoC evidence to identify root causes, merge duplicates, and prepare upstream submissions.
Where the 621 cases went
Roughly one public upstream report landed from every ten PoC-backed cases: 59 reports from 621 cases (9.5%). Across those cases, the main reasons for not filing a separate new report were:
- 266 (42.8%) shared a root cause with another case, so one patch could fix them together.
- 163 (26.2%) were not reported after review, mostly due to limited security impact.
- 133 (21.4%) were already reported or fixed upstream.
Submission timing explains many of the 133 overlaps. Most of the 621 cases were new when the scans finished (though some may already have been in flight by others). Our first public report went out on June 27, 2026, more than five weeks after scanning started. By September 1, 55 of the 59 had been posted to the public lists; the other four were reported privately during the same window and routed to the public lists by mid-October.
Getting fixes merged
Upstream Linux typically requires vulnerability reports to come with a working patch. Agents helped draft initial fixes, but human validation, subsystem understanding, and iterating with maintainers during review remained essential. So far, fixes for 38 of our reports have been merged into upstream mainline—half on the first submission, and the rest after one to four revisions. Reaching a mainline release from the first public patch took a median of 41 days (up to 108 days), and we are still working with maintainers on the remaining reports.
How old the bugs were
Interestingly, most of the bugs we reported had been in the kernel for years. Across the 59 reports, the average bug was 8.1 years old when we first reported it publicly. One of the oldest, a cls_u32 shift-out-of-bounds bug (CVE-2026-74544), had been there for at least 21 years.
Selected findings
Among the 33 CVEs assigned so far from this batch, 24 are rated High or above (CVSS v3.1 score of at least 7.0). Below are eight CVEs for which we confirmed local root privilege-escalation exploits in controlled laboratory environments, with some cases requiring non-default settings or disabled exploit mitigations:
A closer look at CVE-2026-64563
CVE-2026-64563 is a one-line state-reset bug in rhashtable, the Linux kernel’s shared resizable hash table. Because callers trust the object pointers returned by a table walk, this small library flaw enabled a stable local privilege escalation on Linux—and affected Android with SELinux enforcing—without requiring special kernel configurations or user namespaces.
Many kernel subsystems store live objects, such as sockets, in rhashtable and walk the table when user space asks to list them. Because a large listing may be split across several replies, a walk can pause and resume. Its iterator saves where it stopped, including a pointer to the last visited entry (iter->p). While the walk is paused, however, the table can still change: entries can be removed and freed, and resizing can replace the underlying bucket table.
The missed reset
Before 2018, rhashtable_walk_stop() always cleared iter->p = NULL on pause. To improve walk stability across stop/start cycles, a 2018 commit (5d240a8936f6) kept iter->p across walk_stop() and moved the reset into rhashtable_walk_start_check()—but only on one of its two resume paths:
- Old table still present (
iter->walker.tbl != NULL): lines 884–919 scan the bucket chain foriter->pand resetiter->p = NULLif the entry was removed. - Old table freed by a resize (
iter->walker.tbl == NULL): lines 877–882 switch to the new table, resetslotandskip, and return-EAGAINearly without clearingiter->p:
if (!iter->walker.tbl) {
iter->walker.tbl = rht_dereference_rcu(ht->tbl, ht);
iter->slot = 0;
iter->skip = 0;
return -EAGAIN;
}
After that restart, the next call to rhashtable_walk_next() sees iter->p still set and follows iter->p->next instead of starting over at slot = 0. If that entry was freed while the walk was paused, dereferencing iter->p->next reads from freed memory.
Why did this bug go unnoticed for eight years? Triggering it requires three conditions to line up in the same walk: the caller must pause and resume the same iterator across walk_stop() and walk_start(), a table resize must free the old bucket table during that pause, and the entry at iter->p must also be removed and freed before the walk resumes. Most rhashtable walkers finish in a single pass without ever resuming a stopped iterator, and even when a caller does pause and resume, those three events rarely coincide by accident in normal workloads.
The upstream fix simply clears the stale pointer before returning:
+ iter->p = NULL;
From a socket listing to a kernel write
Because rhashtable is a shared library, its callers determine both whether this bug is reachable and how severe its security impact can be. Across the kernel, there are about two dozen files that use the rhashtable walker API, and most walk the table in a single pass and immediately exit. Among those callers, we found two that pause and resume an iterator across batches and can at least cause a crash: TIPC’s socket dump (tipc_nl_sk_walk()) and Netlink’s socket diagnostics (__netlink_diag_dump()).
Of the two, __netlink_diag_dump() is the exploitable caller. It is reachable by unprivileged users on standard kernels, and user space controls the pause between dump replies (via recvmsg()). An attacker can hold a dump paused while resizing the table and freeing the socket at iter->p, then reallocate that memory to control iter->p->next—causing rhashtable_walk_next() to return a fake socket pointer sk.
__netlink_diag_dump() then checks net_eq(sock_net(sk), net) and passes sk to sk_diag_fill() and sock_diag_save_cookie(), which initializes sk->sk_cookie if it is zero. That turns the fake sk pointer into a constrained 64-bit write (when the target 8 bytes are zero and sk->sk_net matches &init_net). Paired with other general exploitation techniques, this write is enough for local privilege escalation.
We thank Yuan Tan of NebuSec, who also independently found this vulnerability. He promptly shared its real severity with us and the maintainers, which helped the upstream fix, and later provided us with his working exploits for both Linux and Android. We also thank Frank Wu of NebuSec, who identified a viable exploitation primitive and developed a working privilege-escalation exploit.
Closing thoughts
- Finding bugs and fixing them are drastically out of balance. Two weeks of codename MDASH scanning produced the initial findings, which we narrowed to 621 PoC-backed cases. But it took over four person-months to report 59 of them—and five months later, 38 fixes have been merged. For complex infrastructure like the Linux kernel, keeping humans in the loop is still necessary, making human effort the bottleneck. Both bug hunters and maintainers can be overwhelmed by the volume of real findings, so focusing on genuinely high-severity issues is essential.
- More tokens may yield more bugs, but not a secure system. We scanned only a small fraction of the kernel, and further tuning and more tokens could uncover substantially more bugs. But even after fixing all of them, is the system secure? At Microsoft FORGE Lab, we’re using AI to expand the scope of vulnerability research, but our experience shows that discovery is only one part of the work. The more urgent problem is how to build practical security solutions that are operational, cumulative, and economically sensible.
These lessons also extend beyond our research. Through close collaboration with the engineering teams building codename MDASH, we’re applying what we learn from real-world vulnerability discovery and validation to inform how these capabilities evolve. The goal is to bring advances in AI-powered security research into the hands of security teams, helping them identify and address critical vulnerabilities more effectively.
