Skip to content

Commit e8260a6

Browse files
dBranskymatankalina
authored andcommitted
verifier: Prototype for new syz-verifier with new rpcserver and fuzzer
This commit introduces a prototype for the new syz-verifier using the new rpcserver and fuzzer. An in-depth explanation is available in README.md. Remaining work includes connecting the reproduction loop, HTTP servers, enabling snapshots, and the comparison phase. The prototype already implements a working differential fuzzing loop.
1 parent 184c5ec commit e8260a6

File tree

5 files changed

+689
-428
lines changed

5 files changed

+689
-428
lines changed

CONTRIBUTORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,4 @@ Jeongjun Park
143143
Nikita Zhandarovich
144144
Jiacheng Xu
145145
Kuzey Arda Bulut
146+
Daniel Bransky

Makefile

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,8 +226,7 @@ kfuzztest:
226226
endif
227227

228228
verifier: descriptions
229-
# TODO: switch syz-verifier to use syz-executor.
230-
# GOOS=$(HOSTOS) GOARCH=$(HOSTARCH) $(HOSTGO) build $(GOHOSTFLAGS) -o ./bin/syz-verifier github.com/google/syzkaller/syz-verifier
229+
GOOS=$(HOSTOS) GOARCH=$(HOSTARCH) $(HOSTGO) build $(GOHOSTFLAGS) -o ./bin/syz-verifier github.com/google/syzkaller/syz-verifier
231230

232231
# `extract` extracts const files from various kernel sources, and may only
233232
# re-generate parts of files.

syz-verifier/README.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# syz-verifier
2+
3+
`syz-verifier` is a differential fuzzing tool for comparing the execution behavior of programs across different versions of the Linux kernel to detect semantic bugs and inconsistencies.
4+
5+
## Design Overview
6+
7+
The syz-verifier implements a centralized fuzzing architecture where a single `Verifier` instance manages multiple kernel configurations for differential testing:
8+
9+
### Core Architecture
10+
11+
```
12+
┌─────────────────────────────────┐
13+
│ Verifier │
14+
│ │
15+
│ ┌───────────────────────────┐ │
16+
│ │ Fuzzer │ │
17+
│ │ (Program Generation) │ │
18+
│ └───────────────────────────┘ │
19+
│ │ │
20+
│ ▼ │
21+
│ ┌───────────────────────────┐ │
22+
│ │ Distribution Logic │ │
23+
│ └───────────────────────────┘ │
24+
│ │ │ │ │
25+
└─────────┼────────┼────────┼─────┘
26+
▼ ▼ ▼
27+
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
28+
│ Queue A │ │ Queue B │ │ Queue C │
29+
│ (Kernel A) │ │ (Kernel B) │ │ (Kernel C) │
30+
└─────────────┘ └─────────────┘ └─────────────┘
31+
```
32+
33+
### Key Components
34+
35+
1. **Single Fuzzer Instance**: The verifier maintains one `fuzzer.Fuzzer` that generates test programs
36+
2. **Per-Kernel Queues**: Each kernel configuration gets its own `queue.PlainQueue` for task distribution
37+
38+
### Main loop
39+
40+
1. **Generation**: The central fuzzer generates a new program
41+
2. **Distribution**: The program is cloned and sent to each kernel's queue
42+
3. **Execution**: Each kernel executes the program independently
43+
4. **Collection**: The verifier waits for all kernels to complete
44+
5. **Comparison**: Results are collected for differential analysis
45+
## Implementation Details
46+
47+
### Verifier Structure
48+
49+
The `Verifier` struct contains:
50+
- `fuzzer atomic.Pointer[fuzzer.Fuzzer]`: Single fuzzer instance for program generation
51+
- `sources map[int]*queue.PlainQueue`: Per-kernel queue mapping (kernel ID → queue)
52+
- `kernels map[int]*Kernel`: Kernel configuration mapping
53+
- `manager.HTTPServer`: The central HTTP server for all kernels
54+
55+
56+
Holds the fuzzer and implements a "proxy" between it and all the kernels.
57+
Also is responsible of aggregating requests from the different kernels
58+
59+
### Kernel Structure
60+
61+
Implements the functions so that it can work with the rpc server.
62+
- MachineChecked: aggregate features and enabled syscalls to the verifier.
63+
- MaxSignal: request the max signal from the verifier.
64+
- BugFrames: not implemented.
65+
- CoverageFilter: currently filters coverage similar to the KernelContext in diff.go. This is still work in progress.
66+
67+
## Usage
68+
69+
### Basic Usage
70+
71+
This is a prototype implementation demonstrating the core differential fuzzing architecture.
72+
73+
```bash
74+
# Build the verifier
75+
make verifier
76+
77+
# Run with kernel configurations (example)
78+
./bin/syz-verifier -configs=kernel1.cfg,kernel2.cfg -debug
79+
80+
# For debug we can also run with a single kernel
81+
./bin/syz-verifier -configs=kernel1.cfg -debug
82+
```
83+
84+
## Development Status
85+
86+
This is a **prototype** showcasing the verifier design with:
87+
- ✅ Centralized fuzzer architecture
88+
- ✅ Per-kernel queue distribution
89+
- ✅ Synchronous execution model
90+
- 🚧 Result comparison logic (TODO)
91+
- 🚧 Reproduction loop (TODO)
92+
- 🚧 Http server (TODO)
93+
- 🚧 Snapshots! are very important for diff fuzz (TODO)

syz-verifier/main.go

Lines changed: 70 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,24 @@
11
// Copyright 2021 syzkaller project authors. All rights reserved.
22
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
33

4-
// TODO: switch syz-verifier to use syz-fuzzer.
5-
6-
//go:build ignore
7-
84
// package main starts the syz-verifier tool. High-level documentation can be
95
// found in docs/syz_verifier.md.
106
package main
117

128
import (
139
"flag"
14-
"io"
10+
"fmt"
1511
"os"
16-
"path/filepath"
17-
"strconv"
1812

13+
"github.com/google/syzkaller/pkg/flatrpc"
14+
"github.com/google/syzkaller/pkg/fuzzer"
15+
"github.com/google/syzkaller/pkg/fuzzer/queue"
1916
"github.com/google/syzkaller/pkg/log"
17+
"github.com/google/syzkaller/pkg/manager"
2018
"github.com/google/syzkaller/pkg/mgrconfig"
2119
"github.com/google/syzkaller/pkg/osutil"
2220
"github.com/google/syzkaller/pkg/report"
21+
"github.com/google/syzkaller/pkg/rpcserver"
2322
"github.com/google/syzkaller/pkg/tool"
2423
"github.com/google/syzkaller/prog"
2524
"github.com/google/syzkaller/vm"
@@ -33,146 +32,95 @@ const (
3332
// and reporting crashes. It also keeps track of the Runners executing on
3433
// spawned VMs, what programs have been sent to each Runner and what programs
3534
// have yet to be sent on any of the Runners.
36-
type poolInfo struct {
37-
cfg *mgrconfig.Config
38-
pool *vm.Pool
39-
Reporter *report.Reporter
40-
// checked is set to true when the set of system calls not supported on the
41-
// kernel is known.
42-
checked bool
35+
func Setup(name string, cfg *mgrconfig.Config, debug bool) (*Kernel, error) {
36+
kernel := &Kernel{
37+
name: name,
38+
debug: debug,
39+
cfg: cfg,
40+
crashes: make(chan *report.Report, 128),
41+
servStats: rpcserver.NewNamedStats(name),
42+
candidates: make(chan []fuzzer.Candidate),
43+
reportGenerator: manager.ReportGeneratorCache(cfg),
44+
enabledSyscalls: make(chan map[*prog.Syscall]bool, 1),
45+
features: make(chan flatrpc.Feature, 1),
46+
}
47+
var err error
48+
kernel.reporter, err = report.NewReporter(cfg)
49+
if err != nil {
50+
return nil, fmt.Errorf("failed to create reporter for %q: %w", name, err)
51+
}
52+
53+
kernel.serv, err = rpcserver.New(&rpcserver.RemoteConfig{
54+
Config: cfg,
55+
Manager: kernel,
56+
Stats: kernel.servStats,
57+
Debug: debug,
58+
})
59+
if err != nil {
60+
return nil, fmt.Errorf("failed to create rpc server for %q: %w", name, err)
61+
}
62+
63+
vmPool, err := vm.Create(cfg, debug)
64+
if err != nil {
65+
return nil, fmt.Errorf("failed to create vm.Pool for %q: %w", name, err)
66+
}
67+
68+
kernel.pool = vm.NewDispatcher(vmPool, kernel.FuzzerInstance)
69+
return kernel, nil
4370
}
4471

4572
func main() {
4673
var cfgs tool.CfgsFlag
4774
flag.Var(&cfgs, "configs", "[MANDATORY] list of at least two kernel-specific comma-sepatated configuration files")
4875
flagDebug := flag.Bool("debug", false, "dump all VM output to console")
49-
flagStats := flag.String("stats", "", "where stats will be written when"+
50-
"execution of syz-verifier finishes, defaults to stdout")
51-
flagEnv := flag.Bool("new-env", true, "create a new environment for each program")
52-
flagAddress := flag.String("address", "127.0.0.1:8080", "http address for monitoring")
53-
flagReruns := flag.Int("rerun", 3, "number of time program is rerun when a mismatch is found")
76+
// flagAddress := flag.String("address", "127.0.0.1:8080", "http address for monitoring")
77+
// flagReruns := flag.Int("rerun", 3, "number of time program is rerun when a mismatch is found")
5478
flag.Parse()
5579

56-
pools := make(map[int]*poolInfo)
80+
kernels := make(map[int]*Kernel)
5781
for idx, cfg := range cfgs {
58-
var err error
59-
pi := &poolInfo{}
60-
pi.cfg, err = mgrconfig.LoadFile(cfg)
82+
kcfg, err := mgrconfig.LoadFile(cfg)
6183
if err != nil {
6284
log.Fatalf("%v", err)
6385
}
64-
// TODO: call pi.pool.Close() on exit.
65-
pi.pool, err = vm.Create(pi.cfg, *flagDebug)
86+
kernels[idx], err = Setup(kcfg.Name, kcfg, *flagDebug)
6687
if err != nil {
67-
log.Fatalf("%v", err)
88+
log.Fatalf("failed to setup kcfg context for %s: %v", kcfg.Name, err)
6889
}
69-
pools[idx] = pi
90+
91+
log.Logf(0, "loaded kernel %s", kcfg.Name)
7092
}
7193

72-
if len(pools) < 2 {
94+
log.Logf(0, "loaded %d kernel configurations", len(kernels))
95+
if len(kernels) < 2 && !*flagDebug {
7396
flag.Usage()
7497
os.Exit(1)
7598
}
76-
77-
cfg := pools[0].cfg
78-
workdir, target, sysTarget, addr := cfg.Workdir, cfg.Target, cfg.SysTarget, cfg.RPC
79-
for idx := 1; idx < len(pools); idx++ {
80-
cfg := pools[idx].cfg
81-
82-
// TODO: pass the configurations that should be the same for all
83-
// kernels in a default config file in order to avoid this checks and
84-
// add testing
85-
if workdir != cfg.Workdir {
86-
log.Fatalf("working directory mismatch")
87-
}
88-
if target != cfg.Target {
89-
log.Fatalf("target mismatch")
90-
}
91-
if sysTarget != cfg.SysTarget {
92-
log.Fatalf("system target mismatch")
93-
}
94-
if addr != pools[idx].cfg.RPC {
95-
log.Fatalf("tcp address mismatch")
99+
workdir := kernels[0].cfg.Workdir
100+
reqMaxSignal := make(chan int, len(kernels))
101+
sources := make(map[int]*queue.PlainQueue)
102+
for idx, kernel := range kernels {
103+
if kernel.cfg.Workdir != workdir {
104+
log.Fatalf("all kernel configurations must have the same workdir, got %q and %q", workdir, kernel.cfg.Workdir)
96105
}
106+
kernel.reqMaxSignal = reqMaxSignal
107+
sources[idx] = queue.Plain()
108+
kernel.source = sources[idx]
97109
}
110+
osutil.MkdirAll(workdir)
98111

99-
exe := sysTarget.ExeExtension
100-
runnerBin := filepath.Join(cfg.Syzkaller, "bin", target.OS+"_"+target.Arch, "syz-runner"+exe)
101-
if !osutil.IsExist(runnerBin) {
102-
log.Fatalf("bad syzkaller config: can't find %v", runnerBin)
103-
}
104-
execBin := cfg.ExecutorBin
105-
if !osutil.IsExist(execBin) {
106-
log.Fatalf("bad syzkaller config: can't find %v", execBin)
107-
}
108-
109-
crashdir := filepath.Join(workdir, "crashes")
110-
osutil.MkdirAll(crashdir)
111-
for idx := range pools {
112-
OS, Arch := target.OS, target.Arch
113-
targetPath := OS + "-" + Arch + "-" + strconv.Itoa(idx)
114-
osutil.MkdirAll(filepath.Join(workdir, targetPath))
115-
osutil.MkdirAll(filepath.Join(crashdir, targetPath))
116-
}
117-
118-
resultsdir := filepath.Join(workdir, "results")
119-
osutil.MkdirAll(resultsdir)
120-
121-
var sw io.Writer
122-
var err error
123-
if *flagStats == "" {
124-
sw = os.Stdout
125-
} else {
126-
statsFile := filepath.Join(workdir, *flagStats)
127-
sw, err = os.Create(statsFile)
128-
if err != nil {
129-
log.Fatalf("failed to create stats output file: %v", err)
130-
}
131-
}
132-
133-
for idx, pi := range pools {
134-
var err error
135-
pi.Reporter, err = report.NewReporter(pi.cfg)
136-
if err != nil {
137-
log.Fatalf("failed to create reporter for instance-%d: %v", idx, err)
138-
}
139-
}
140-
141-
calls := make(map[*prog.Syscall]bool)
142-
143-
for _, id := range cfg.Syscalls {
144-
c := target.Syscalls[id]
145-
calls[c] = true
146-
}
112+
log.Logf(0, "initialized %d sources", len(sources))
147113

148114
vrf := &Verifier{
149-
workdir: workdir,
150-
crashdir: crashdir,
151-
resultsdir: resultsdir,
152-
pools: pools,
153-
target: target,
154-
calls: calls,
155-
reasons: make(map[*prog.Syscall]string),
156-
runnerBin: runnerBin,
157-
executorBin: execBin,
158-
addr: addr,
159-
reportReasons: len(cfg.EnabledSyscalls) != 0 || len(cfg.DisabledSyscalls) != 0,
160-
stats: MakeStats(),
161-
statsWrite: sw,
162-
newEnv: *flagEnv,
163-
reruns: *flagReruns,
115+
kernels: kernels,
116+
cfg: kernels[0].cfg, // for now take the first kernel's config
117+
corpusPreload: make(chan []fuzzer.Candidate),
118+
disabledHashes: make(map[string]struct{}),
119+
target: kernels[0].cfg.Target,
120+
sources: sources,
164121
}
165122

166-
vrf.Init()
167-
168-
vrf.StartProgramsAnalysis()
169-
vrf.startInstances()
170-
171-
monitor := MakeMonitor()
172-
monitor.SetStatsTracking(vrf.stats)
173-
174-
log.Logf(0, "run the Monitor at http://%s", *flagAddress)
175-
go monitor.ListenAndServe(*flagAddress)
123+
ctx := vm.ShutdownCtx()
124+
vrf.RunVerifierFuzzer(ctx)
176125

177-
select {}
178126
}

0 commit comments

Comments
 (0)