Baike.dev
All toolsOnline toolsAI codingTrendingOpen sourceNewsSubmit
Log in
< Back to tools
S

syzkaller-rust

> 编程语言
Open source

A Rust Reimplementation of Syzkaller

656 stars0 likes0 views
WebsiteGitHub

About

A Rust Reimplementation of Syzkaller

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

bash
cd syzkaller-rust
cargo build --release

Configuration

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

bash
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:

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

Running

bash
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:

bash
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 /smoke/, 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:

bash
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/, so corpus and artifact summaries do not bleed across targets.

You can override either one:

…

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:

bash
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:

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

Recommended bounded Linux run:

bash
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 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 `arr

Issues· 0 open

View all issuesOpen on GitHub

No open issues yet, or sync has not completed.

> Tags

Rust

No comments yet. Be the first to share.

> Details

PublishedAug 1, 2026
UpdatedSep 17, 2026
Category编程语言
PricingOpen source

> Related tools

T
TypeScript
JavaScript 的超集,为前端与全栈提供静态类型
P
Python
通用编程语言,广泛用于 Web、数据与 AI
G
Go
Google 推出的简洁高效系统语言