MCPcopy Index your code
hub / github.com/Gality369/syzkaller-rust

github.com/Gality369/syzkaller-rust @main

Chat with this repo
repository ↗ · DeepWiki ↗ · + Follow
1,359 symbols 4,524 edges 20 files 60 documented · 4%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

syzkaller-rust

A minimal Linux kernel fuzzer written in Rust, compatible with syzkaller's syz-executor binary protocol. It reuses the original syzkaller executor to run test programs, while reimplementing the fuzzer scheduling logic in Rust.

Background

This project is a Rust reimplementation of syzkaller's core orchestration logic. It does not reimplement the executor (the in-kernel syscall runner). Instead, it communicates directly with the original syz-executor binary via the FlatRPC protocol. This allows focusing on fuzzer scheduling, program generation, and corpus management, while maintaining full wire-level compatibility with the syzkaller executor.

Architecture

syzkaller-rust (this project)
    │
    ├── Launch QEMU virtual machine
    ├── Copy syz-executor to VM via SCP and start it over SSH
    ├── Perform FlatRPC handshake with the executor runner over TCP
    ├── Generate / mutate test programs (syscall sequences)
    ├── Serialize programs into the executor binary format and send them
    ├── Receive coverage signals (kcov) and update the corpus
    └── Detect kernel crashes (KASAN / KFENCE / panic / etc.)

Module Overview

Module Responsibility
avoidance.rs Persist and reload learned timeout-avoidance state (edge/syscall failure counts)
main.rs Entry point: parse config, launch manager
manager.rs Main loop: VM lifecycle, FlatRPC handshake, fuzz loop
program.rs Syscall descriptors (syzkaller internal IDs), program data structures, random filename generation
exec.rs Serialize programs into the executor binary format (varint encoding, compatible with encodingexec.go)
fuzzer.rs Program generation (random) and mutation (insert/remove/modify calls, argument mutation, splicing)
corpus.rs Corpus: track discovered coverage signals, store programs that produce new signal, persist and reload snapshots
crash.rs Kernel crash detection: recognize BUG/KASAN/KFENCE/panic patterns, save crash reports
protocol.rs FlatRPC message encode/decode (size-prefixed FlatBuffers)
flatrpc_generated.rs Auto-generated FlatBuffers code (from syzkaller's flatrpc.fbs)
qemu.rs QEMU VM launch, SSH port forwarding, serial console output capture
ssh.rs SSH command execution, SCP file transfer
config.rs JSON configuration file parsing

Prerequisites

  • Rust 1.70+ (cargo)
  • QEMU (qemu-system-x86_64)
  • syzkaller's syz-executor binary (statically compiled for linux/amd64)
  • Kernel image (bzImage) and QEMU disk image (e.g. bullseye.img)
  • SSH key for the VM

Build

cd syzkaller-rust
cargo build --release

Configuration

Start from the checked-in template and keep your machine-specific paths in an untracked local file:

cp config.example.json config.local.json

Then edit config.local.json:

config.example.json already includes the recommended Linux core bundle target: data/target-bundles/linux-amd64-core.json.

max_execs is optional. When set, the manager exits cleanly after that many execution requests (including timed out ones), which is useful for bounded smoke tests and scripted end-to-end checks.

target_bundle is also optional. When present, the runtime loads exported JSON target metadata instead of syscall_descriptions:

{
    "target_bundle": "data/target-bundles/linux-amd64-core.json"
}

Running

RUST_LOG=info ./target/release/syzkaller-rust config.local.json

Log levels: error / warn / info / debug.

Smoke Run

For a bounded end-to-end smoke run, use the dedicated CLI wrapper instead of editing config.json by hand:

RUST_LOG=info ./target/release/syzkaller-rust smoke config.local.json

By default this:

  • forces max_execs=32
  • uses data/target-bundles/linux-amd64-core.json when the config does not already specify target_bundle or syscall_descriptions
  • validates the configured kernel/image/executor/QEMU paths before starting
  • runs inside a clean per-target workdir at <workdir>/smoke/<target-slug>, resetting that directory before each run
  • prints a JSON summary after the bounded run completes

That keeps bounded smoke corpus and crash artifacts separate from any longer running fuzz session that uses the base workdir.

For a multi-target regression pass with isolated per-target workdirs, use the suite wrapper:

RUST_LOG=info ./target/release/syzkaller-rust smoke-suite config.local.json

By default this runs:

  • descriptions/linux/file-subset.txt
  • descriptions/linux/path-info-subset.txt
  • descriptions/linux/dirent-subset.txt
  • descriptions/linux/pipe-io-subset.txt
  • descriptions/linux/msg-io-subset.txt
  • descriptions/linux/recvmsg-io-subset.txt
  • descriptions/linux/recvmmsg-io-subset.txt
  • descriptions/linux/dgram-io-subset.txt
  • descriptions/linux/socket-io-subset.txt
  • descriptions/linux/sockopt-buf-subset.txt
  • descriptions/linux/sock-ifreq-subset.txt
  • descriptions/linux/sock-ifconf-subset.txt
  • descriptions/linux/sock-ethtool-subset.txt
  • descriptions/linux/pipe-fionread-subset.txt
  • descriptions/linux/accept-connect-subset.txt
  • descriptions/linux/socket-subset.txt
  • descriptions/linux/image-subset.txt
  • descriptions/linux/mm-subset.txt
  • descriptions/linux/process-subset.txt

Each target gets its own clean workdir under workdir/smoke-suite/<slug>, so corpus and artifact summaries do not bleed across targets.

You can override either one:

RUST_LOG=info ./target/release/syzkaller-rust smoke config.local.json 8
RUST_LOG=info ./target/release/syzkaller-rust smoke config.local.json 8 descriptions/linux/socket-io-subset.txt
RUST_LOG=info ./target/release/syzkaller-rust smoke config.local.json 1 descriptions/linux/image-subset.txt
RUST_LOG=info ./target/release/syzkaller-rust smoke config.local.json 4 bundle:data/target-bundles/linux-amd64-core.json
RUST_LOG=info ./target/release/syzkaller-rust smoke-suite config.local.json 4 descriptions/linux/mm-subset.txt descriptions/linux/process-subset.txt
RUST_LOG=info ./target/release/syzkaller-rust smoke-suite config.local.json 1 descriptions/linux/socket-io-subset.txt

The following directories are created under workdir/ at runtime: - crashes/ — kernel crash reports (console output) - timeouts/ — timed-out or executor-reported hanged programs, including their syscall shape and timeout-prone edge profile, for later triage

Timeout artifacts also preserve the latest guest serial tail, and when available the syz-executor SSH stdout/stderr stream, so executor-side hangs can be triaged without rerunning the whole smoke target immediately.

Timeout and hang artifacts now also include a lightweight per-request trace showing whether the manager observed Executing, opaque State, or ExecResult messages before the request stalled.

FlatRPC Protocol

The fuzzer implements syzkaller's FlatRPC handshake protocol:

fuzzer  → executor:  ConnectRequest  (version, architecture info)
executor → fuzzer:  ConnectReply    (supported features, timeout config)
executor → fuzzer:  InfoRequest     (process configuration request)
fuzzer  → executor:  InfoReply       (coverage filter, signal filter)
--- handshake complete, enter execution loop ---
fuzzer  → executor:  ExecRequest     (program data + exec options, via shared memory)
executor → fuzzer:  ExecResult      (per-call return values, coverage signals)

Wire format: 4-byte little-endian size prefix + FlatBuffers-encoded message body.

Program Wire Format

Test programs are passed to the executor as a stream of varints (zigzag-encoded):

uint64  num_calls
// for each call:
[COPYIN instructions]*   // write data pages (filenames, buffer contents, etc.)
uint64  syscall_id       // syzkaller internal ID (NOT the Linux syscall NR)
uint64  copyout_idx      // copyout result index (!0 = no copyout)
uint64  num_args
[arg]*                   // type + value for each argument
uint64  EXEC_INSTR_EOF   // !0u64 end marker

Important: syscall_id is the index into the executor's syscalls[] table for the target platform, not the Linux syscall number (NR). For example, read has syzkaller ID 5186 on linux/amd64 in the current bundled target, while its Linux NR is 0.

Target Bundles

The repository now ships two checked-in Linux bundle fixtures:

  • data/target-bundles/linux-amd64-smoke.json
  • the narrow 27-syscall smoke seed used to prove out the original bundle path
  • data/target-bundles/linux-amd64-core.json
  • the curated 41-syscall Linux core target that the bounded smoke command now uses by default

The linux-amd64-core target currently includes:

accept, bind, close, connect, dup3, eventfd2, fstat, getcwd, getegid, geteuid, getgid, getpgid, getpid, getsockopt, gettid, getuid, ioctl, listen, lseek, madvise, mkdirat, mmap, mprotect, mremap, msync, munmap, newfstatat, openat, pipe2, pread64, pwrite64, read, recvmsg, sendmmsg, sendmsg, setpgid, setsockopt, socket, socketpair, write, writev

To regenerate the curated core bundle from upstream syzkaller target metadata:

cd tools/syzkaller-target-bundle
go run . \
  --syscalls-file ../../data/target-bundles/linux-amd64-core.syscalls \
  --output ../../data/target-bundles/linux-amd64-core.json

You can inspect bundle coverage without booting a VM:

cargo run --quiet -- target-summary bundle:data/target-bundles/linux-amd64-core.json 12

Recommended bounded Linux run:

RUST_LOG=info ./target/release/syzkaller-rust smoke config.local.json 128 bundle:data/target-bundles/linux-amd64-core.json

Description DSL

The current Rust-side description parser supports a deliberately small subset of syzlang-oriented concepts:

  • scalar constants via const NAME = VALUE
  • named constant and flag groups via constset NAME[SIZE] = ... and flagset NAME[SIZE] = ...
  • syz-sysgen style .txt.const constant files for linux/amd64 constant imports
  • automatic loading of sibling name.txt.const files when parsing name.txt
  • resource declarations with optional parent resources
  • include "file.txt" and directory fragment loading
  • header-style include <linux/...> directives are accepted and ignored
  • directory loading of .txt and .txt.const fragments in sorted order
  • unconstrained scalar integer arguments via int8, int16, int32, int64, and intptr
  • endian-qualified scalar ranges via int16be[min:max], int32be[min:max], and int64be[min:max]
  • typed constants and flags via forms like const[AF_INET, int16] and flags[socket_type, int16]
  • fixed-size struct blocks like sockaddr_in { ... } [size[16]]
  • ranged fixed-element arrays via array[int8, 4:8], array[qid, 1:3], and default short arrays like array[int8]
  • variable-sized array elements behind pointer or top-level array values, with preserved per-element boundaries so array[cmsghdr_like, 1:2]-style message batches can derive both total byte size and element count without collapsing back to opaque flat buffers
  • trailing variable-sized struct fields when the final field is a fixed-element array, so packed message shapes like { count bytesize[data]; data array[...] } can materialize and validate without falling back to raw buffers
  • inline pointer fields inside fixed-size structs, unions, and arrays of structs, including iovec-style layouts where nested len[...] fields track pointed-to payload sizes and executor serialization emits nested copyins
  • nested pointer-bearing containers like msghdr layouts, where optional inline pointers derive zero lengths when absent and nested array[iovec] fields keep per-element payload lengths and aggregate counts aligned
  • output-oriented nested containers like recv_msghdr, where inline ptr[out] fields materialize zeroed reserved buffers with real capacities instead of collapsing to opaque placeholder pointers
  • default C-style struct padding plus explicit [packed] / align[N] layout semantics, so msghdr-style fixed headers and aligned trailing payload containers derive real field offsets and total sizes instead of packed-byte approximations
  • aligned control-message batches inside send_msghdr-style containers, where msg_control ptr[in, array[cmsghdr_like, ...]] keeps element boundaries, aligned cmsg_len values, and aggregate msg_controllen sizes consistent through generation, validation, replay, and executor copyin
  • batched message vectors like array[send_mmsghdr, 1:2], where each fixed-size element embeds a full msghdr container plus its own per-message metadata, so sendmmsg-style inputs keep nested lengths and optional pointers coherent across multiple messages in one syscall
  • union blocks like sockaddr_arg [ v4 sockaddr_in v6 buffer[28:28] ], including fixed-size [size[N]] unions and [varlen] unions
  • virtual memory area arguments via vma, vma[opt], vma[N], and vma[min:max]
  • string arguments via string, string["literal"], string[set_name], string[filename], string[..., N], and stringnoz[...]
  • top-level string-set definitions like names = "foo", "bar"
  • parameterized top-level type templates for aliases, structs, and unions, such as type wrap[PAYLOAD] { payload PAYLOAD } and type alias_wrap[PAYLOAD] wrap[PAYLOAD]
  • parent-derived inline sizes inside fixed-size structs and struct templates via len[parent, intN] and bytesize[parent, intN]
  • zero-sized void fields plus offsetof[field, intN] for fixed-size struct layouts, which is enough for small nlattr-style headers
  • rooted named-path size targets via forms like len[arg:field], bytesize[type_name:field], bytesize[parent:parent:field], bytesize[syscall:arg], and nested `off

Core symbols most depended-on inside this repo

len
called by 417
src/corpus.rs
parse_syscall_descs
called by 206
src/description.rs
encode_scalar_bytes
called by 161
src/program.rs
write_val
called by 63
src/exec.rs
serialize_program
called by 47
src/exec.rs
validate
called by 43
src/program.rs
parse_exec_events
called by 43
src/exec.rs
invalid_arg
called by 35
src/program.rs

Shape

Function 934
Method 222
Class 157
Enum 38
Struct 6
TypeAlias 2

Languages

Rust95%
Go5%

Modules by API surface

src/flatrpc_generated.rs239 symbols
src/description.rs238 symbols
src/program.rs223 symbols
src/fuzzer.rs122 symbols
src/crash.rs100 symbols
src/exec.rs66 symbols
src/repro.rs62 symbols
src/manager.rs59 symbols
src/main.rs57 symbols
tools/syzkaller-target-bundle/main.go40 symbols
src/avoidance.rs30 symbols
tools/syzkaller-target-bundle/main_test.go24 symbols

For agents

$ claude mcp add syzkaller-rust \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact