aboutsummaryrefslogtreecommitdiffstats
path: root/pkg/fuzzer/fuzzer.go
diff options
context:
space:
mode:
authorAleksandr Nogikh <nogikh@google.com>2024-03-15 19:53:15 +0100
committerAleksandr Nogikh <nogikh@google.com>2024-03-25 13:12:00 +0000
commit409ee912f2c4f07e3064b4e6f4a83e1f812531d8 (patch)
treeef7701845a10852597eb2fbf3c962d27f47ca161 /pkg/fuzzer/fuzzer.go
parent5d5b1ae5147428cf089a616a3114af1add92068d (diff)
all: move fuzzer to the host
Instead of doing fuzzing in parallel in running VM, make all decisions in the host syz-manager process. Instantiate and keep a fuzzer.Fuzzer object in syz-manager and update the RPC between syz-manager and syz-fuzzer to exchange exact programs to execute and their resulting signal and coverage. To optimize the networking traffic, exchange mostly only the difference between the known max signal and the detected signal.
Diffstat (limited to 'pkg/fuzzer/fuzzer.go')
-rw-r--r--pkg/fuzzer/fuzzer.go77
1 files changed, 27 insertions, 50 deletions
diff --git a/pkg/fuzzer/fuzzer.go b/pkg/fuzzer/fuzzer.go
index 196e59506..14c3f5902 100644
--- a/pkg/fuzzer/fuzzer.go
+++ b/pkg/fuzzer/fuzzer.go
@@ -13,15 +13,15 @@ import (
"time"
"github.com/google/syzkaller/pkg/corpus"
- "github.com/google/syzkaller/pkg/hash"
"github.com/google/syzkaller/pkg/ipc"
+ "github.com/google/syzkaller/pkg/rpctype"
+ "github.com/google/syzkaller/pkg/signal"
"github.com/google/syzkaller/prog"
)
type Fuzzer struct {
- Config *Config
- Cover *Cover
- NeedCandidates chan struct{}
+ Config *Config
+ Cover *Cover
ctx context.Context
mu sync.Mutex
@@ -39,18 +39,16 @@ type Fuzzer struct {
runningJobs atomic.Int64
queuedCandidates atomic.Int64
- // If the source of candidates runs out of them, we risk
- // generating too many needCandidate requests (one for
- // each Config.MinCandidates). We prevent this with candidatesRequested.
- candidatesRequested atomic.Bool
+
+ outOfQueue atomic.Bool
+ outOfQueueNext atomic.Int64
}
func NewFuzzer(ctx context.Context, cfg *Config, rnd *rand.Rand,
target *prog.Target) *Fuzzer {
f := &Fuzzer{
- Config: cfg,
- Cover: &Cover{},
- NeedCandidates: make(chan struct{}, 1),
+ Config: cfg,
+ Cover: &Cover{},
ctx: ctx,
stats: map[string]uint64{},
@@ -82,18 +80,16 @@ type Config struct {
EnabledCalls map[*prog.Syscall]bool
NoMutateCalls map[int]bool
FetchRawCover bool
- // If the number of queued candidates is less than MinCandidates,
- // NeedCandidates is triggered.
- MinCandidates uint
- NewInputs chan corpus.NewInput
+ NewInputFilter func(input *corpus.NewInput) bool
}
type Request struct {
Prog *prog.Prog
NeedCover bool
NeedRawCover bool
- NeedSignal bool
+ NeedSignal rpctype.SignalType
NeedHints bool
+ SignalFilter signal.Signal // If specified, the resulting signal MAY be a subset of it.
// Fields that are only relevant within pkg/fuzzer.
flags ProgTypes
stat string
@@ -110,7 +106,7 @@ func (fuzzer *Fuzzer) Done(req *Request, res *Result) {
// Triage individual calls.
// We do it before unblocking the waiting threads because
// it may result it concurrent modification of req.Prog.
- if req.NeedSignal && res.Info != nil {
+ if req.NeedSignal != rpctype.NoSignal && res.Info != nil {
for call, info := range res.Info.Calls {
fuzzer.triageProgCall(req.Prog, &info, call, req.flags)
}
@@ -166,7 +162,6 @@ func signalPrio(p *prog.Prog, info *ipc.CallInfo, call int) (prio uint8) {
type Candidate struct {
Prog *prog.Prog
- Hash hash.Sig
Smashed bool
Minimized bool
}
@@ -178,20 +173,19 @@ func (fuzzer *Fuzzer) NextInput() *Request {
panic("queuedCandidates is out of sync")
}
}
- if fuzzer.NeedCandidatesNow() &&
- !fuzzer.candidatesRequested.CompareAndSwap(false, true) {
- select {
- case fuzzer.NeedCandidates <- struct{}{}:
- default:
- }
- }
return req
}
func (fuzzer *Fuzzer) nextInput() *Request {
- nextExec := fuzzer.nextExec.tryPop()
- if nextExec != nil {
- return nextExec.value
+ // The fuzzer may get biased to one specific part of the kernel.
+ // Periodically generate random programs to ensure that the coverage
+ // is more uniform.
+ if !fuzzer.outOfQueue.Load() ||
+ fuzzer.outOfQueueNext.Add(1)%400 > 0 {
+ nextExec := fuzzer.nextExec.tryPop()
+ if nextExec != nil {
+ return nextExec.value
+ }
}
// Either generate a new input or mutate an existing one.
mutateRate := 0.95
@@ -227,16 +221,15 @@ func (fuzzer *Fuzzer) Logf(level int, msg string, args ...interface{}) {
fuzzer.Config.Logf(level, msg, args...)
}
-func (fuzzer *Fuzzer) NeedCandidatesNow() bool {
- return fuzzer.queuedCandidates.Load() < int64(fuzzer.Config.MinCandidates)
-}
-
func (fuzzer *Fuzzer) AddCandidates(candidates []Candidate) {
fuzzer.queuedCandidates.Add(int64(len(candidates)))
for _, candidate := range candidates {
fuzzer.pushExec(candidateRequest(candidate), priority{candidatePrio})
}
- fuzzer.candidatesRequested.Store(false)
+}
+
+func (fuzzer *Fuzzer) EnableOutOfQueue() {
+ fuzzer.outOfQueue.Store(true)
}
func (fuzzer *Fuzzer) rand() *rand.Rand {
@@ -250,7 +243,7 @@ func (fuzzer *Fuzzer) pushExec(req *Request, prio priority) {
if req.stat == "" {
panic("Request.Stat field must be set")
}
- if req.NeedHints && (req.NeedCover || req.NeedSignal) {
+ if req.NeedHints && (req.NeedCover || req.NeedSignal != rpctype.NoSignal) {
panic("Request.NeedHints is mutually exclusive with other fields")
}
fuzzer.nextExec.push(&priorityQueueItem[*Request]{
@@ -330,19 +323,3 @@ func (fuzzer *Fuzzer) logCurrentStats() {
fuzzer.Logf(0, "%s", str)
}
}
-
-type Stats struct {
- CoverStats
- corpus.Stats
- Candidates int
- RunningJobs int
-}
-
-func (fuzzer *Fuzzer) Stats() Stats {
- return Stats{
- CoverStats: fuzzer.Cover.Stats(),
- Stats: fuzzer.Config.Corpus.Stats(),
- Candidates: int(fuzzer.queuedCandidates.Load()),
- RunningJobs: int(fuzzer.runningJobs.Load()),
- }
-}