Home · ASPLOS 2025 · Reliability, Testing & Verification

KernelGPT: Enhanced Kernel Fuzzing via Large Language Models

Chenyuan Yang, Zijie Zhao and Lingming Zhang

Conference
ASPLOS 2025
Published
2025
Pages
560-573
Citations
28

Kernel fuzzers like Syzkaller find bugs only where someone has hand-written a specification describing a syscall's syntax and semantics — and for the drivers and sockets that make up 70% of the Linux kernel, huge stretches have no specification at all, because writing one takes deep per-module expertise; KernelGPT uses a large language model, which absorbed the kernel's code and folklore during pretraining, to synthesize those specifications automatically — iteratively asking for the source code it says it is missing, then repairing its output against real validator errors — and the resulting specifications lifted fuzzing coverage 18%, found 24 previously unknown kernel bugs (11 CVEs, 12 fixed, one confirmed by Linus Torvalds), and were merged into Syzkaller at its developers' request.

The argument in one glance

  1. Problem

    The fuzzer is world-class; its map of the kernel is full of blank regions

    Kernel bugs reachable through the system-call interface threaten billions of devices, because that interface is exactly what attackers can touch. Syzkaller — the state-of-the-art kernel fuzzer, with over 5,000 bugs found and fixed — generates syscall sequences guided by hand-written specifications that describe each call's argument types, valid values, and dependencies. But device drivers and sockets, the most complex parts of the kernel (41.6% and 27% of its code), hide thousands of distinct operations behind generic syscalls like ioctl, where a numeric command plus an untyped pointer selects the real operation. Writing a specification demands deep per-module expertise. The result: of the driver handlers missing descriptions in this study, 60% had NO specification at all — unfuzzed attack surface, sitting there for years.

  2. What people did

    Generate the specifications automatically

    KSG probes a booted kernel dynamically and recovers parameter types with symbolic execution. DIFUZE and SyzDescribe — the state of the art — analyze driver source statically, using expert-written pattern rules: a miscdevice's .name field gives the device file name; a switch over the command variable gives the ioctl command values.

  3. Why it fell short

    Hand-coded rules capture the common case, and the kernel is mostly uncommon cases

    Dynamic probing only sees drivers already loaded and set up. Static rules encode the patterns their authors thought of: the device mapper driver puts its file name in .nodename rather than .name (legitimate but rare), and modifies the ioctl command with cmd = _IOC_NR(command) before dispatching — so SyzDescribe emits a wrong device name, wrong command values, and imprecise types for it. Rules cannot read comments or documentation, rot as the kernel evolves, and their output is unreadable — random numbers for names — which matters, because Syzkaller's developers refuse to merge specifications humans cannot audit. A previous attempt to upstream SyzDescribe output died in code review.

  4. This paper's idea

    The model that read the whole kernel writes the specifications

    KernelGPT (University of Illinois) observes that large language models absorbed the kernel's source, documentation, and decades of usage folklore during pretraining — including the rare idioms no rule set covers. It synthesizes specifications in three iterative stages (identifier deduction, type recovery, dependency analysis) where the model itself names the functions and types it is missing, a code extractor fetches them, and the loop re-queries until nothing is unknown. Generated specifications are then checked by Syzkaller's own validation tools, and failures are repaired by showing the model the error message plus the relevant source. Results: valid specifications for 93% of driver handlers and 86% of socket handlers that were missing them (the static state of the art managed 27% and zero), 18% more fuzzing coverage, 24 previously unknown bugs — 21 confirmed, 12 fixed, 11 CVEs, one personally confirmed by Linus Torvalds — and specifications merged into Syzkaller at the development team's request, with one word changed.

Why this problem is worth solving

An operating system kernel is the one program every other program must trust. Its bugs are reachable by anyone who can run code on the machine, because the system-call interface — the defined doorway between user programs and the kernel — is open by design.

automatically generating huge numbers of test inputs — here, sequences of system calls — and running them against the target while watching for crashes, memory corruption, and hangs. A kernel fuzzing campaign runs for days or weeks and executes billions of calls, so the generator must produce VALID calls at machine speed: random bytes bounce off the first argument check and test nothing deep
has been the field’s main weapon for decades, and Syzkaller is its flagship: a coverage-guided fuzzer whose bug tally — over 5,000 fixed in Linux — has made it part of the kernel’s ongoing quality process.

But Syzkaller does not know what a syscall looks like by magic. It reads

descriptions written in syzlang, Syzkaller’s domain-specific language, defining for each call its argument types and layouts, semantically valid values (the MSM graphics driver answers only at “/dev/msm”), inter-call dependencies (the file descriptor returned by openat is the handle later ioctl calls need), intra-call dependencies (which ioctl command value selects which argument structure), and constraints like valid integer ranges. Good specifications let the fuzzer skip the ocean of trivially invalid sequences and spend its budget probing deep kernel logic
written, overwhelmingly, by hand — by people who understand both syzlang and the specific kernel module. That expertise is scarce, the kernel keeps growing, and the gap concentrates exactly where the attack surface is richest: drivers and sockets, 70% of the kernel’s code, where one generic syscall fans out into thousands of distinct operations. In this paper’s survey, 75 driver and 66 socket handlers were missing descriptions; 45 of those drivers had none at all. Every one is code an attacker can reach and the fuzzer cannot.

What people did before, and where it broke

  • Hand-written specifications. The gold standard for quality and the reason Syzkaller works — but a manual process gated on rare joint expertise, perpetually behind the kernel, and prone to going stale as modules evolve.
  • Dynamic probing (KSG). Open the device files present in a booted kernel, hook the handlers, recover types with symbolic execution. Sound where it applies — but it cannot see drivers that are not loaded or need setup steps the prober does not know.
  • Static analysis with pattern rules (DIFUZE, and SyzDescribe, the state of the art). Find handler registrations, then apply expert-written mappings from code patterns to specification fragments. Three structural limits. The rules model common implementations only — the device mapper’s .nodename file name and its modified command value both fall outside them, so its generated specification is wrong in three ways at once. The rules cannot read text — comments and documentation carry intent no syntax pattern exposes. And the output is unreadable: machine-generated numbers for syscall and field names, which Syzkaller’s maintainers explicitly reject because specifications they cannot audit can hide flaws. An attempt to merge SyzDescribe output upstream stalled in review and was abandoned.

How KernelGPT works

An LLM that asks for the code it needs, checked by the fuzzer's own tools

  1. Handler extraction (LLVM-based)

    A source-code extractor parses the kernel with the LLVM toolchain, pattern-matching driver and socket operation-handler registrations (structures that install function pointers for ioctl, open, setsockopt, and friends). It also indexes every function, struct, union, and enum definition in the kernel — the retrieval pool for later steps. This part stays deliberately simple; the intelligence lives downstream.

  2. Stage 1 — identifier deduction new

    The analysis model (GPT-4, temperature 0.1, few-shot prompts) reads the handler source and infers the identifier values that select operations: device file names, ioctl command values, socket domains. Crucially, when the handler delegates its logic to a function the prompt does not contain, the model is instructed to output that function's name in an UNKNOWN list rather than guess.

  3. The iterative loop new

    The extractor fetches source for each unknown target; the model is re-queried with the new code plus the usage context from the previous round; the loop continues until no unknowns remain or five iterations pass. This solves two problems at once — a syscall's full relevant source exceeds even 128K-token context windows, and irrelevant helper code actively misleads — by letting the MODEL decide which code matters for the current question.

  4. Stage 2 — type recovery new

    For each identifier value, the model recovers the argument structure: fetching struct definitions, recursing into nested types (marked unknown, analyzed next round), and — where static analysis sees only independent fields — reading SEMANTIC relations from the code, like a count field that holds the length of a neighboring array, emitted as syzlang's len[] construct.

  5. Stage 3 — dependency analysis new

    The model determines whether a syscall's return value is a resource other calls consume — the file descriptor that threads a sequence of calls together — using the return-value-relevant functions it flagged itself back in stage 1. Dependencies are what let the fuzzer build multi-call sequences that reach deep state.

  6. Validation and repair new

    Every generated specification runs through Syzkaller's own tools (syz-extract, syz-generate), which catch undefined types, wrong macro names, and unmatched dependencies. Each error message is matched to its faulty description, and the model is asked to repair it — shown the description, the error, and the relevant kernel source. In the evaluation this rescued 42 additional handler specifications on top of the 85 that validated on the first pass.

Total cost of the campaign that generated 532 new syscall descriptions: about 5.6 million input tokens, 400 thousand output tokens, 34 dollars, and 4.7 hours — against 3.8 hours for the static state of the art to produce about a quarter as many. The 34 dollars then steers fuzzing campaigns that run for days or weeks.

The idea

The rules static analyzers hard-code, the model already learned — including the long tail. SyzDescribe knows “miscdevice.name is the device file name” because an expert wrote that rule; it fails on .nodename because no one wrote that rule. A large language model pretrained on the kernel’s source, its documentation, its mailing lists, and years of code that USES these drivers has seen both idioms, and the command-value modification, and the comment explaining the quirk. KernelGPT’s bet is that specification generation is knowledge distillation, not program analysis: the information was already absorbed, and the system’s job is to extract it reliably — with few-shot examples to fix the output format, low temperature to suppress creativity, and validation to catch the residue of error.

Let the model name its own unknowns. The iterative loop is the paper’s engineering core. A naive design pastes all possibly-relevant source into one giant prompt; the ablation shows what that costs — on the kvm driver, one-shot prompting recovers 42 syscalls and 11 types where the iterative version recovers 71 and 28, and coverage collapses from 15,605 basic blocks to 5,457. The fix inverts retrieval: the model reports which specific function or type blocks its current inference, the extractor fetches exactly that, and analysis proceeds in focused steps. Retrieval guided by the analyst beats retrieval guessed in advance — the model is simultaneously the reasoner and the query planner.

Do not fuzz with the model; build the fuzzer’s knowledge with it. KernelGPT deliberately refuses the obvious design of having the LLM generate syscall sequences directly. A fuzzing campaign executes billions of inputs; a model generating each one would be absurdly slow and expensive, and would discard Syzkaller’s decade of coverage feedback, mutation, and crash triage machinery. Instead the model runs ONCE per subsystem — 34 dollars total — to synthesize the static knowledge (specifications) that the fast, mature, deterministic engine then exploits at machine speed for weeks. And because the output must outlive the campaign, readability is treated as a requirement, not a nicety: the model emits meaningful names (ioctlDMLISTDEVICES,notioctlDM_LIST_DEVICES, not ioctl34545_2), which is why Syzkaller’s developers asked for the specifications and merged them nearly unchanged.

The device mapper, traced

From two struct definitions to three new bugs

The device mapper driver (drivers/md/dm-ioctl.c) maps physical disks to virtual block devices. Syzkaller had no specification for it. SyzDescribe generated a wrong one. Follow KernelGPT through it.

Step 1 of 5

What the extractor hands the model

Two registrations: a file_operations struct installing dm_ctl_ioctl as the ioctl handler, and a miscdevice struct whose .name is DM_NAME but whose .nodename is DM_DIR "/" DM_CONTROL_NODE. The rule-based tools read .name and emit the wrong device file; the model, which has seen the rarer .nodename convention in its training data, emits the correct "/dev/mapper/control".

SyzDescribe's output: wrong device name, wrong command value, untyped byte-array argument
the wrong name alone means every generated call misses the driver entirely

Why it works

  • Pretraining already covered the kernel’s code, documentation, and usage folklore → the rare idioms that escape hand-coded pattern rules (nodename file names, transformed command values, semantic field links) are in the model’s reach → specification generation becomes extraction plus verification rather than rule authoring.
  • The model names its own missing functions and types → the extractor fetches exactly the code the current inference needs → context stays small and relevant, beating both the 128K-token ceiling and the noise of irrelevant helpers — worth 1.39× coverage over one-shot prompting in the ablation.
  • Specifications are validated by Syzkaller’s own tooling and repaired against real error messages → occasional model mistakes are caught mechanically, not shipped → 42 of the 127 successful handler specifications exist only because of the repair loop.
  • The model runs once; the fuzzer runs for weeks → 34 dollars of LLM inference configures billions of high-speed executions by mature machinery → intelligence is spent at the knowledge layer, throughput stays at the engine layer.
  • Output uses meaningful, human-auditable names → maintainers can review, trust, and merge the specifications → the work compounds upstream instead of living in a paper artifact.

What it costs

  • A capability threshold. With GPT-3.5 the system infers 85 syscalls where GPT-4 infers 143, and coverage drops 21%; the approach needs a frontier-class model (GPT-4o matches GPT-4), with the price, closed-source dependence, and reproducibility caveats that implies.
  • Residual errors survive validation. The manual audit of 313 generated ioctl descriptions found about 0.9% with wrong identifier values and 9 with wrong types; deep chains of indirect delegation still cause missed syscalls in 3 of 45 audited drivers. Validation is syntactic — runtime semantic checking of specifications remains open for the whole field.
  • Scope. Drivers and sockets on Linux, centered on ioctl and the socket-family syscalls; hardware-requiring and debug-only drivers are filtered out because a virtual machine cannot exercise them meaningfully.
  • Fuzzing noise. Coverage and crash counts come from bounded campaigns (24 hours, three repetitions) with the usual variance of fuzzing experiments; the bug list, not the coverage delta, is the durable evidence.
  • The gap moves, it does not vanish. Specifications still rot as the kernel evolves; KernelGPT lowers the cost of regenerating them from expert-weeks to dollars, but someone must still run it and re-review.

The evaluation

Linux 6.7, specifications generated under allyesconfig, fuzzing under Google’s syzbot configuration with 4 QEMU virtual machines; baselines are hand-written Syzkaller specifications and SyzDescribe, the static state of the art.

Three sources of syscall specifications

PropertyHand-written (Syzkaller upstream)Static analysis (SyzDescribe)KernelGPT
Handlers with missing specs covered
Correctness of what is generatedReference quality, but stale and incomplete in places (the RDS socket spec omitted sendto — hiding a CVE)Device mapper spec wrong in three ways (device name, command values, types); two of 30 benchmark drivers got non-working device names
Fuzzing coverage (24h × 3, added to Syzkaller)204,923 basic blocks (baseline)
New bugs found— (the campaigns run continuously; these 24 were beyond its specs)
Upstream acceptanceThe standard everything is judged against
Cost of the full missing-spec generation run: about 34 dollars and 4.7 hours for 532 syscalls (the static tool needed 3.8 hours for 146). Ablation: iterative multi-stage prompting yields 1.28× the syscalls, 2.37× the types, and 1.39× the coverage of one-big-prompt generation; GPT-3.5 is insufficient, GPT-4o comparable to GPT-4.Underlined values explain themselves when clicked.

Check yourself

Check your understanding

  1. 1. SyzDescribe's rule "the miscdevice .name field gives the device file name" works for most drivers and fails on the device mapper. Predict the general failure mode of rule-based specification generation — and why a pretrained model escapes it.

  2. 2. The obvious LLM-for-fuzzing design has the model generate syscall sequences directly. KernelGPT instead uses the model only to write specifications for Syzkaller. Why is that division of labor the better architecture?

  3. 3. Instead of pasting all syscall-related source into one large prompt, KernelGPT queries iteratively, letting the model output an UNKNOWN list of functions and types it needs next. The ablation says this matters a lot. Why?

  4. 4. Syzkaller's developers refused machine-generated specifications before, yet merged KernelGPT's CEC driver specification nearly unchanged — and asked for it. What does the readability requirement actually protect?

Sources

Related papers on Reliability, Testing & Verification

See all 80 →