The Linux kernel ships roughly 20 to 30 million lines of C code. Every subsystem, every driver, every syscall handler was written under a different set of constraints, by different people, at different points across three decades of development. The attack surface that results is enormous, and the history of kernel security research reflects that: finding bugs has never been the hard part. The hard part has always been finding them fast enough, with enough coverage to matter.
Finding over 100 bugs in 30 days, as described in this piece, is not shocking if you understand the toolchain behind it. It is, however, a useful moment to trace how we got here and what it costs.
The Kernel as a Bug Surface
The kernel occupies a uniquely vulnerable position in the software stack. It runs with full hardware privileges, processes untrusted input from unprivileged userspace through the syscall interface, and manages resources under concurrent access from multiple threads and CPUs. A use-after-free in a filesystem driver is not the same as a use-after-free in a userspace application. In the kernel, it frequently becomes a local privilege escalation, and sometimes a remote code execution if the vulnerable path is reachable from a container or network context.
The syscall interface alone exposes hundreds of entry points. On Linux x86-64, there are over 300 syscalls, and many of them accept complex nested data structures: io_uring submissions, bpf() programs, ioctl variants that are essentially entire sub-APIs. Each of those paths needs its own corpus of interesting inputs to test properly.
Manual auditing can find interesting bugs in these paths, but manual auditing does not scale to 100 bugs in 30 days unless the researcher is very lucky or the target is already well-understood. What has changed over the past decade is the quality and coverage of automated fuzzing for kernel code.
Syzkaller and the Coverage-Guided Approach
Syzkaller is the tool that made high-velocity kernel bug finding routine. Developed at Google by Dmitry Vyukov and others starting around 2015, it is a coverage-guided fuzzer built specifically for operating system kernels. The core idea is familiar from AFL and libFuzzer: use instrumented builds to observe which code paths each input exercises, and bias the fuzzer toward inputs that reach new coverage. The kernel-specific insight is that syscalls are stateful and sequential, so the fuzzer needs to understand relationships between them.
A typical syzkaller run generates a sequence of syscalls described in its own specification language (syzlang), executes that sequence in a VM, collects coverage via the kcov kernel interface, and uses that feedback to mutate the program for the next iteration. When a crash occurs, it records the crashing program, the sanitizer report, and the kernel log.
The kcov interface is worth understanding. It exposes per-task coverage information to userspace:
fd = open("/sys/kernel/debug/kcov", O_RDWR);
ioctl(fd, KCOV_INIT_TRACE, COVER_SIZE);
cover = (uint64_t*)mmap(NULL, COVER_SIZE * sizeof(uint64_t),
PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
ioctl(fd, KCOV_ENABLE, KCOV_TRACE_PC);
// ... execute syscalls ...
n = cover[0]; // number of PCs covered
for (i = 0; i < n; i++)
printf("0x%llx\n", cover[i + 1]);
ioctl(fd, KCOV_DISABLE, 0);
This per-task granularity is what lets syzkaller attribute coverage to specific syscall sequences rather than the noisy aggregate coverage you would get from a system-wide profiler. It also enables accurate deduplication: two crashes that hit the same code path can be identified as the same underlying bug even if the triggering sequences differ.
Syscall Descriptions: The Real Unlock
What actually determines syzkaller’s effectiveness in practice is the quality of its syscall descriptions. Syzlang is a domain-specific language for expressing the types, constraints, and relationships of syscall arguments. A description for openat looks roughly like:
openat(fd const[AT_FDCWD], file ptr[in, filename], flags flags[open_flags], mode flags[open_mode]) fd
The key mechanism is the resource type. It expresses that the output of one syscall, such as a file descriptor, a socket, or a process ID, can be passed as a meaningful input to another. This is what lets syzkaller generate realistic syscall sequences rather than random combinations that fail at the first call and exercise no interesting code paths.
When a subsystem lacks descriptions, syzkaller’s coverage of it is shallow. When someone writes detailed descriptions for a previously underspecified subsystem, the bug rate in that subsystem spikes. This reflects that the descriptions are doing real semantic modeling, telling the fuzzer what inputs are worth constructing. The syzlang corpus is itself a significant artifact of collective security research, encoding knowledge about the kernel’s API surface accumulated over years.
A campaign that finds 100+ bugs in 30 days has almost certainly either added detailed descriptions for underspecified subsystems, run on configurations with more attack surface exposed, or combined syzkaller with complementary sanitizers like KMSAN for uninitialized memory or KCSAN for data races.
What Gets Found
The bug classes that kernel fuzzing reliably surfaces are well-understood at this point. Use-after-free and double-free are the most common exploitable class; the kernel’s reference counting model is complex enough that keeping track of object lifetimes correctly across async paths and error paths is genuinely hard. KASAN, the kernel address sanitizer, catches these at runtime by instrumenting heap allocations and marking freed memory as inaccessible.
Out-of-bounds reads and writes show up frequently in buffer handling code, often triggered by malformed length fields in structures passed from userspace. Null pointer dereferences are common and often not directly exploitable on modern kernels, but they remain bugs worth fixing.
The more interesting finds tend to involve race conditions. KCSAN instruments memory accesses to detect data races at runtime. Races in the kernel are notoriously hard to trigger reliably, but syzkaller’s ability to run multiple threads executing different syscall sequences in parallel means it does hit them. A race between close() and a concurrent read in a file descriptor table, for example, can produce a use-after-free that is both analyzable and real. These are the bugs that take the most work to understand and fix correctly.
syzbot and the Maintenance Load
The continuous fuzzing service syzbot runs syzkaller against kernel mainline and various stable branches around the clock. Its public dashboard shows hundreds of open issues at any given time. Many sit unfixed for months. This is not because maintainers are careless; it is because the ratio of bugs found to maintainers available to fix them is deeply unfavorable.
A researcher who finds 100+ bugs in 30 days and reports them responsibly is creating real work for kernel subsystem maintainers. Each bug requires a reproducer review, a root cause analysis, a patch, a review cycle, and backports to stable branches if the bug is serious enough. The bottleneck in kernel security has shifted from finding bugs to triaging and fixing them at the rate they are discovered.
This is not an argument against aggressive fuzzing campaigns. It is an argument for pairing that capability with tooling and process that makes the downstream work tractable. Good crash reproducers, bisection data from git bisect, and patches submitted alongside reports all reduce the load significantly. Researchers who do this well move the entire ecosystem forward. Researchers who dump a list of crash hashes and move on create friction that slows everything down.
The Infrastructure Behind the Number
A finding rate of 100+ bugs per month from a focused campaign is not evidence that the Linux kernel is uniquely insecure for its size and complexity. It is evidence that coverage-guided kernel fuzzing has become genuinely mature infrastructure. Syzkaller, syzbot, the sanitizer toolchain (KASAN, KMSAN, KCSAN, UBSAN), and the syzlang corpus represent years of compounding investment. The toolchain did not spring into existence; it was built incrementally by people who also had to fix the bugs they found.
The kernel codebase grows by several hundred thousand lines per release cycle. New subsystems arrive with incomplete descriptions and no fuzzing history. The attack surface expands faster than the descriptions for it do, which means the bug discovery rate has a structural floor that does not go away.
The interesting question is not whether more bugs exist, because the answer is certainly yes. The interesting question is whether the toolchain investment and the maintainer bandwidth to respond to it are growing at compatible rates. At the moment, looking at the open issue queue on syzbot, the gap between detection velocity and remediation velocity is real and widening. The fuzzer has outrun the patch process, and the fix for that is not a better fuzzer.