From 409ee912f2c4f07e3064b4e6f4a83e1f812531d8 Mon Sep 17 00:00:00 2001 From: Aleksandr Nogikh Date: Fri, 15 Mar 2024 19:53:15 +0100 Subject: 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. --- pkg/corpus/corpus.go | 20 ----------- pkg/cover/canonicalizer.go | 38 +++++++------------- pkg/cover/canonicalizer_test.go | 46 +++++++----------------- pkg/fuzzer/cover.go | 6 ++++ pkg/fuzzer/fuzzer.go | 77 +++++++++++++++-------------------------- pkg/fuzzer/fuzzer_test.go | 7 ++-- pkg/fuzzer/job.go | 29 ++++++++-------- pkg/fuzzer/stats.go | 26 ++++++++++++-- pkg/rpctype/rpctype.go | 72 +++++++++++++++++++------------------- pkg/signal/signal.go | 71 ++++++++++++++++--------------------- 10 files changed, 165 insertions(+), 227 deletions(-) (limited to 'pkg') diff --git a/pkg/corpus/corpus.go b/pkg/corpus/corpus.go index 772037178..8eed1fc63 100644 --- a/pkg/corpus/corpus.go +++ b/pkg/corpus/corpus.go @@ -9,7 +9,6 @@ import ( "github.com/google/syzkaller/pkg/cover" "github.com/google/syzkaller/pkg/hash" - "github.com/google/syzkaller/pkg/rpctype" "github.com/google/syzkaller/pkg/signal" "github.com/google/syzkaller/prog" ) @@ -63,15 +62,6 @@ func (item Item) StringCall() string { return stringCall(item.Prog, item.Call) } -// RPCInputShort() does not include coverage. -func (item Item) RPCInputShort() rpctype.Input { - return rpctype.Input{ - Call: item.Call, - Prog: item.ProgData, - Signal: item.Signal.Serialize(), - } -} - func stringCall(p *prog.Prog, call int) string { if call != -1 { return p.Calls[call].Meta.Name @@ -91,16 +81,6 @@ func (item NewInput) StringCall() string { return stringCall(item.Prog, item.Call) } -func (item NewInput) RPCInput() rpctype.Input { - return rpctype.Input{ - Call: item.Call, - Prog: item.Prog.Serialize(), - Signal: item.Signal.Serialize(), - Cover: item.Cover, - RawCover: item.RawCover, - } -} - type NewItemEvent struct { Sig string Exists bool diff --git a/pkg/cover/canonicalizer.go b/pkg/cover/canonicalizer.go index c7c385aed..d3b014af8 100644 --- a/pkg/cover/canonicalizer.go +++ b/pkg/cover/canonicalizer.go @@ -9,7 +9,6 @@ import ( "github.com/google/syzkaller/pkg/host" "github.com/google/syzkaller/pkg/log" - "github.com/google/syzkaller/pkg/signal" ) type Canonicalizer struct { @@ -120,18 +119,18 @@ func (can *Canonicalizer) NewInstance(modules []host.KernelModule) *Canonicalize } } -func (ci *CanonicalizerInstance) Canonicalize(cov []uint32, sign signal.Serial) ([]uint32, signal.Serial) { +func (ci *CanonicalizerInstance) Canonicalize(elems []uint32) []uint32 { if ci.canonical.moduleKeys == nil { - return cov, sign + return elems } - return ci.canonicalize.convertPCs(cov, sign) + return ci.canonicalize.convertPCs(elems) } -func (ci *CanonicalizerInstance) Decanonicalize(cov []uint32, sign signal.Serial) ([]uint32, signal.Serial) { +func (ci *CanonicalizerInstance) Decanonicalize(elems []uint32) []uint32 { if ci.canonical.moduleKeys == nil { - return cov, sign + return elems } - return ci.decanonicalize.convertPCs(cov, sign) + return ci.decanonicalize.convertPCs(elems) } func (ci *CanonicalizerInstance) DecanonicalizeFilter(bitmap map[uint32]uint32) map[uint32]uint32 { @@ -177,34 +176,21 @@ func findModule(pc uint32, moduleKeys []uint32) (moduleIdx int) { return moduleIdx - 1 } -func (convert *Convert) convertPCs(cov []uint32, sign signal.Serial) ([]uint32, signal.Serial) { +func (convert *Convert) convertPCs(pcs []uint32) []uint32 { // Convert coverage. - var retCov []uint32 + var ret []uint32 convCtx := &convertContext{convert: convert} - for _, pc := range cov { + for _, pc := range pcs { if newPC, ok := convert.convertPC(pc); ok { - retCov = append(retCov, newPC) + ret = append(ret, newPC) } else { convCtx.discard(pc) } } if msg := convCtx.discarded(); msg != "" { - log.Logf(4, "error in PC conversion: %v", msg) - } - // Convert signals. - retSign := &signal.Serial{} - convCtx = &convertContext{convert: convert} - for idx, elem := range sign.Elems { - if newSign, ok := convert.convertPC(uint32(elem)); ok { - retSign.AddElem(newSign, sign.Prios[idx]) - } else { - convCtx.discard(uint32(elem)) - } - } - if msg := convCtx.discarded(); msg != "" { - log.Logf(4, "error in signal conversion: %v", msg) + log.Logf(4, "error in PC/signal conversion: %v", msg) } - return retCov, *retSign + return ret } func (convert *Convert) convertPC(pc uint32) (uint32, bool) { diff --git a/pkg/cover/canonicalizer_test.go b/pkg/cover/canonicalizer_test.go index db418abe1..aa840a96f 100644 --- a/pkg/cover/canonicalizer_test.go +++ b/pkg/cover/canonicalizer_test.go @@ -13,7 +13,6 @@ import ( "github.com/google/syzkaller/pkg/cover" "github.com/google/syzkaller/pkg/host" - "github.com/google/syzkaller/pkg/signal" ) type RPCServer struct { @@ -28,8 +27,8 @@ type Fuzzer struct { goalCov []uint32 bitmap map[uint32]uint32 goalBitmap map[uint32]uint32 - sign signal.Serial - goalSign signal.Serial + sign []uint32 + goalSign []uint32 } type canonicalizeValue int @@ -49,13 +48,9 @@ func TestNilModules(t *testing.T) { serv.fuzzers["f1"].cov = []uint32{0x00010000, 0x00020000, 0x00030000, 0x00040000} serv.fuzzers["f1"].goalCov = []uint32{0x00010000, 0x00020000, 0x00030000, 0x00040000} - serv.fuzzers["f1"].sign = signal.FromRaw(serv.fuzzers["f1"].cov, 0).Serialize() - serv.fuzzers["f1"].goalSign = signal.FromRaw(serv.fuzzers["f1"].goalCov, 0).Serialize() serv.fuzzers["f2"].cov = []uint32{0x00010000, 0x00020000, 0x00030000, 0x00040000} serv.fuzzers["f2"].goalCov = []uint32{0x00010000, 0x00020000, 0x00030000, 0x00040000} - serv.fuzzers["f2"].sign = signal.FromRaw(serv.fuzzers["f2"].cov, 0).Serialize() - serv.fuzzers["f2"].goalSign = signal.FromRaw(serv.fuzzers["f2"].goalCov, 0).Serialize() serv.fuzzers["f1"].bitmap = map[uint32]uint32{ 0x00010011: 1, @@ -87,15 +82,15 @@ func TestNilModules(t *testing.T) { } serv.fuzzers["f1"].goalCov = []uint32{0x00010000, 0x00020000, 0x00030000, 0x00040000} - serv.fuzzers["f1"].goalSign = signal.FromRaw(serv.fuzzers["f1"].goalCov, 0).Serialize() + serv.fuzzers["f1"].goalSign = serv.fuzzers["f1"].goalCov serv.fuzzers["f2"].goalCov = []uint32{0x00010000, 0x00020000, 0x00030000, 0x00040000} - serv.fuzzers["f2"].goalSign = signal.FromRaw(serv.fuzzers["f2"].goalCov, 0).Serialize() + serv.fuzzers["f2"].goalSign = serv.fuzzers["f2"].goalCov if err := serv.runTest(Decanonicalize); err != "" { t.Fatalf("failed in decanonicalization: %v", err) } } -// Confirms there is no change to signals if coverage is disabled and fallback signals are used. +// Confirms there is no change to PCs if coverage is disabled and fallback signals are used. func TestDisabledSignals(t *testing.T) { serv := &RPCServer{ fuzzers: make(map[string]*Fuzzer), @@ -112,18 +107,18 @@ func TestDisabledSignals(t *testing.T) { serv.connect("f2", f2Modules, false) pcs := []uint32{0x00010000, 0x00020000, 0x00030000, 0x00040000} - serv.fuzzers["f1"].sign = signal.FromRaw(pcs, 0).Serialize() - serv.fuzzers["f1"].goalSign = signal.FromRaw(pcs, 0).Serialize() + serv.fuzzers["f1"].cov = pcs + serv.fuzzers["f1"].goalCov = pcs - serv.fuzzers["f2"].sign = signal.FromRaw(pcs, 0).Serialize() - serv.fuzzers["f2"].goalSign = signal.FromRaw(pcs, 0).Serialize() + serv.fuzzers["f2"].sign = pcs + serv.fuzzers["f2"].goalSign = pcs if err := serv.runTest(Canonicalize); err != "" { t.Fatalf("failed in canonicalization: %v", err) } - serv.fuzzers["f1"].goalSign = signal.FromRaw(pcs, 0).Serialize() - serv.fuzzers["f2"].goalSign = signal.FromRaw(pcs, 0).Serialize() + serv.fuzzers["f1"].goalSign = pcs + serv.fuzzers["f2"].goalSign = pcs if err := serv.runTest(Decanonicalize); err != "" { t.Fatalf("failed in decanonicalization: %v", err) } @@ -152,8 +147,6 @@ func TestModules(t *testing.T) { 0x00035000, 0x00040000, 0x00045000, 0x00050000, 0x00055000} serv.fuzzers["f1"].goalCov = []uint32{0x00010000, 0x00015000, 0x00020000, 0x00025000, 0x00030000, 0x00035000, 0x00040000, 0x00045000, 0x00050000, 0x00055000} - serv.fuzzers["f1"].sign = signal.FromRaw(serv.fuzzers["f1"].cov, 0).Serialize() - serv.fuzzers["f1"].goalSign = signal.FromRaw(serv.fuzzers["f1"].goalCov, 0).Serialize() // The modules addresss are inverted between: (2 and 4), (3 and 5), // affecting the output canonical coverage values in these ranges. @@ -161,8 +154,6 @@ func TestModules(t *testing.T) { 0x00035000, 0x00040000, 0x00045000, 0x00050000, 0x00055000} serv.fuzzers["f2"].goalCov = []uint32{0x00010000, 0x00015000, 0x00040000, 0x00025000, 0x00045000, 0x0004a000, 0x00020000, 0x00030000, 0x0003b000, 0x00055000} - serv.fuzzers["f2"].sign = signal.FromRaw(serv.fuzzers["f2"].cov, 0).Serialize() - serv.fuzzers["f2"].goalSign = signal.FromRaw(serv.fuzzers["f2"].goalCov, 0).Serialize() serv.fuzzers["f1"].bitmap = map[uint32]uint32{ 0x00010011: 1, @@ -195,10 +186,8 @@ func TestModules(t *testing.T) { serv.fuzzers["f1"].goalCov = []uint32{0x00010000, 0x00015000, 0x00020000, 0x00025000, 0x00030000, 0x00035000, 0x00040000, 0x00045000, 0x00050000, 0x00055000} - serv.fuzzers["f1"].goalSign = signal.FromRaw(serv.fuzzers["f1"].goalCov, 0).Serialize() serv.fuzzers["f2"].goalCov = []uint32{0x00010000, 0x00015000, 0x00020000, 0x00025000, 0x00030000, 0x00035000, 0x00040000, 0x00045000, 0x00050000, 0x00055000} - serv.fuzzers["f2"].goalSign = signal.FromRaw(serv.fuzzers["f2"].goalCov, 0).Serialize() if err := serv.runTest(Decanonicalize); err != "" { t.Fatalf("failed in decanonicalization: %v", err) } @@ -225,15 +214,12 @@ func TestChangingModules(t *testing.T) { // in this range should be deleted. serv.fuzzers["f2"].cov = []uint32{0x00010000, 0x00015000, 0x00020000, 0x00025000} serv.fuzzers["f2"].goalCov = []uint32{0x00010000, 0x00015000, 0x00025000} - serv.fuzzers["f2"].sign = signal.FromRaw(serv.fuzzers["f2"].cov, 0).Serialize() - serv.fuzzers["f2"].goalSign = signal.FromRaw(serv.fuzzers["f2"].goalCov, 0).Serialize() if err := serv.runTest(Canonicalize); err != "" { t.Fatalf("failed in canonicalization: %v", err) } serv.fuzzers["f2"].goalCov = []uint32{0x00010000, 0x00015000, 0x00025000} - serv.fuzzers["f2"].goalSign = signal.FromRaw(serv.fuzzers["f2"].goalCov, 0).Serialize() if err := serv.runTest(Decanonicalize); err != "" { t.Fatalf("failed in decanonicalization: %v", err) } @@ -241,12 +227,11 @@ func TestChangingModules(t *testing.T) { func (serv *RPCServer) runTest(val canonicalizeValue) string { var cov []uint32 - var sign signal.Serial for name, fuzzer := range serv.fuzzers { if val == Canonicalize { - cov, sign = fuzzer.instModules.Canonicalize(fuzzer.cov, fuzzer.sign) + cov = fuzzer.instModules.Canonicalize(fuzzer.cov) } else { - cov, sign = fuzzer.instModules.Decanonicalize(fuzzer.cov, fuzzer.sign) + cov = fuzzer.instModules.Decanonicalize(fuzzer.cov) instBitmap := fuzzer.instModules.DecanonicalizeFilter(fuzzer.bitmap) if !reflect.DeepEqual(instBitmap, fuzzer.goalBitmap) { return fmt.Sprintf("failed in bitmap conversion. Fuzzer %v.\nExpected: 0x%x.\nReturned: 0x%x", @@ -257,12 +242,7 @@ func (serv *RPCServer) runTest(val canonicalizeValue) string { return fmt.Sprintf("failed in coverage conversion. Fuzzer %v.\nExpected: 0x%x.\nReturned: 0x%x", name, fuzzer.goalCov, cov) } - if !reflect.DeepEqual(sign.Deserialize(), fuzzer.goalSign.Deserialize()) { - return fmt.Sprintf("failed in signal conversion. Fuzzer %v.\nExpected: 0x%x.\nReturned: 0x%x", - name, fuzzer.goalSign, sign) - } fuzzer.cov = cov - fuzzer.sign = sign } return "" } diff --git a/pkg/fuzzer/cover.go b/pkg/fuzzer/cover.go index 886da004d..5af00f167 100644 --- a/pkg/fuzzer/cover.go +++ b/pkg/fuzzer/cover.go @@ -35,6 +35,12 @@ func (cover *Cover) addRawMaxSignal(signal []uint32, prio uint8) signal.Signal { return diff } +func (cover *Cover) CopyMaxSignal() signal.Signal { + cover.mu.RLock() + defer cover.mu.RUnlock() + return cover.maxSignal.Copy() +} + func (cover *Cover) GrabNewSignal() signal.Signal { cover.mu.Lock() defer cover.mu.Unlock() 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()), - } -} diff --git a/pkg/fuzzer/fuzzer_test.go b/pkg/fuzzer/fuzzer_test.go index 4f8cf41c5..bd6d9a8fe 100644 --- a/pkg/fuzzer/fuzzer_test.go +++ b/pkg/fuzzer/fuzzer_test.go @@ -22,6 +22,7 @@ import ( "github.com/google/syzkaller/pkg/csource" "github.com/google/syzkaller/pkg/ipc" "github.com/google/syzkaller/pkg/ipc/ipcconfig" + "github.com/google/syzkaller/pkg/rpctype" "github.com/google/syzkaller/pkg/testutil" "github.com/google/syzkaller/prog" "github.com/google/syzkaller/sys/targets" @@ -54,7 +55,6 @@ func TestFuzz(t *testing.T) { EnabledCalls: map[*prog.Syscall]bool{ target.SyscallMap["syz_test_fuzzer1"]: true, }, - NewInputs: make(chan corpus.NewInput), }, rand.New(testutil.RandSource(t)), target) go func() { @@ -129,7 +129,7 @@ func emulateExec(req *Request) (*Result, string, error) { if req.NeedCover { callInfo.Cover = []uint32{cover} } - if req.NeedSignal { + if req.NeedSignal != rpctype.NoSignal { callInfo.Signal = []uint32{cover} } info.Calls = append(info.Calls, callInfo) @@ -205,7 +205,6 @@ func (f *testFuzzer) wait() { for title, cnt := range f.crashes { t.Logf("%s: %d", title, cnt) } - t.Logf("stats:\n%v", f.fuzzer.GrabStats()) } // TODO: it's already implemented in syz-fuzzer/proc.go, @@ -239,7 +238,7 @@ var crashRe = regexp.MustCompile(`{{CRASH: (.*?)}}`) func (proc *executorProc) execute(req *Request) (*Result, string, error) { execOpts := proc.execOpts // TODO: it's duplicated from fuzzer.go. - if req.NeedSignal { + if req.NeedSignal != rpctype.NoSignal { execOpts.Flags |= ipc.FlagCollectSignal } if req.NeedCover { diff --git a/pkg/fuzzer/job.go b/pkg/fuzzer/job.go index dfc0b807e..f567fc2cc 100644 --- a/pkg/fuzzer/job.go +++ b/pkg/fuzzer/job.go @@ -10,6 +10,7 @@ import ( "github.com/google/syzkaller/pkg/corpus" "github.com/google/syzkaller/pkg/cover" "github.com/google/syzkaller/pkg/ipc" + "github.com/google/syzkaller/pkg/rpctype" "github.com/google/syzkaller/pkg/signal" "github.com/google/syzkaller/prog" ) @@ -64,7 +65,7 @@ func genProgRequest(fuzzer *Fuzzer, rnd *rand.Rand) *Request { fuzzer.ChoiceTable()) return &Request{ Prog: p, - NeedSignal: true, + NeedSignal: rpctype.NewSignal, stat: statGenerate, } } @@ -83,7 +84,7 @@ func mutateProgRequest(fuzzer *Fuzzer, rnd *rand.Rand) *Request { ) return &Request{ Prog: newP, - NeedSignal: true, + NeedSignal: rpctype.NewSignal, stat: statFuzz, } } @@ -98,7 +99,7 @@ func candidateRequest(input Candidate) *Request { } return &Request{ Prog: input.Prog, - NeedSignal: true, + NeedSignal: rpctype.NewSignal, stat: statCandidate, flags: flags, } @@ -157,13 +158,10 @@ func (job *triageJob) run(fuzzer *Fuzzer) { Cover: info.cover.Serialize(), RawCover: info.rawCover, } - fuzzer.Config.Corpus.Save(input) - if fuzzer.Config.NewInputs != nil { - select { - case <-fuzzer.ctx.Done(): - case fuzzer.Config.NewInputs <- input: - } + if filter := fuzzer.Config.NewInputFilter; filter != nil && !filter(&input) { + return } + fuzzer.Config.Corpus.Save(input) } type deflakedCover struct { @@ -179,7 +177,7 @@ func (job *triageJob) deflake(fuzzer *Fuzzer) (info deflakedCover, stop bool) { for i := 0; i < signalRuns; i++ { result := fuzzer.exec(job, &Request{ Prog: job.p, - NeedSignal: true, + NeedSignal: rpctype.AllSignal, NeedCover: true, NeedRawCover: fuzzer.Config.FetchRawCover, stat: statTriage, @@ -226,9 +224,10 @@ func (job *triageJob) minimize(fuzzer *Fuzzer, newSignal signal.Signal) (stop bo } for i := 0; i < minimizeAttempts; i++ { result := fuzzer.exec(job, &Request{ - Prog: p1, - NeedSignal: true, - stat: statMinimize, + Prog: p1, + NeedSignal: rpctype.AllSignal, + SignalFilter: newSignal, + stat: statMinimize, }) if result.Stop { stop = true @@ -298,7 +297,7 @@ func (job *smashJob) run(fuzzer *Fuzzer) { fuzzer.Config.Corpus.Programs()) result := fuzzer.exec(job, &Request{ Prog: p, - NeedSignal: true, + NeedSignal: rpctype.NewSignal, stat: statSmash, }) if result.Stop { @@ -386,7 +385,7 @@ func (job *hintsJob) run(fuzzer *Fuzzer) { func(p *prog.Prog) bool { result := fuzzer.exec(job, &Request{ Prog: p, - NeedSignal: true, + NeedSignal: rpctype.NewSignal, stat: statHint, }) return !result.Stop diff --git a/pkg/fuzzer/stats.go b/pkg/fuzzer/stats.go index 17bc6131c..044febc64 100644 --- a/pkg/fuzzer/stats.go +++ b/pkg/fuzzer/stats.go @@ -3,6 +3,8 @@ package fuzzer +import "github.com/google/syzkaller/pkg/corpus" + const ( statGenerate = "exec gen" statFuzz = "exec fuzz" @@ -17,10 +19,28 @@ const ( statBufferTooSmall = "buffer too small" ) -func (fuzzer *Fuzzer) GrabStats() map[string]uint64 { +type Stats struct { + CoverStats + corpus.Stats + Candidates int + RunningJobs int + // Let's keep stats in Named as long as the rest of the code does not depend + // on their specific values. + Named map[string]uint64 +} + +func (fuzzer *Fuzzer) Stats() Stats { + ret := Stats{ + CoverStats: fuzzer.Cover.Stats(), + Stats: fuzzer.Config.Corpus.Stats(), + Candidates: int(fuzzer.queuedCandidates.Load()), + RunningJobs: int(fuzzer.runningJobs.Load()), + Named: make(map[string]uint64), + } fuzzer.mu.Lock() defer fuzzer.mu.Unlock() - ret := fuzzer.stats - fuzzer.stats = map[string]uint64{} + for k, v := range fuzzer.stats { + ret.Named[k] = v + } return ret } diff --git a/pkg/rpctype/rpctype.go b/pkg/rpctype/rpctype.go index efd9d6589..9f0b6846e 100644 --- a/pkg/rpctype/rpctype.go +++ b/pkg/rpctype/rpctype.go @@ -13,20 +13,47 @@ import ( "github.com/google/syzkaller/pkg/signal" ) -type Input struct { - Call int // seq number of call in the prog to which the item is related (-1 for extra) - Prog []byte - Signal signal.Serial - Cover []uint32 - RawCover []uint32 +type SignalType int + +const ( + NoSignal SignalType = 0 // we don't need any signal + NewSignal SignalType = 1 // we need the newly seen signal + AllSignal SignalType = 2 // we need all signal +) + +// ExecutionRequest describes the task of executing a particular program. +// Corresponds to Fuzzer.Request. +type ExecutionRequest struct { + ID int64 + ProgData []byte + NeedCover bool + NeedRawCover bool + NeedHints bool + NeedSignal SignalType + SignalFilter signal.Signal +} + +// ExecutionResult is sent after ExecutionRequest is completed. +type ExecutionResult struct { + ID int64 + Info ipc.ProgInfo +} + +// ExchangeInfoRequest is periodically sent by syz-fuzzer to syz-manager. +type ExchangeInfoRequest struct { + Name string + NeedProgs int + StatsDelta map[string]uint64 + Results []ExecutionResult } -type Candidate struct { - Prog []byte - Minimized bool - Smashed bool +// ExchangeInfoReply is a reply to ExchangeInfoRequest. +type ExchangeInfoReply struct { + Requests []ExecutionRequest + NewMaxSignal []uint32 } +// TODO: merge ExecutionRequest and ExecTask. type ExecTask struct { Prog []byte ID int64 @@ -40,7 +67,6 @@ type ConnectArgs struct { type ConnectRes struct { EnabledCalls []int - NoMutateCalls map[int]bool GitRevision string TargetRevision string AllSandboxes bool @@ -64,24 +90,6 @@ type SyscallReason struct { Reason string } -type NewInputArgs struct { - Name string - Input -} - -type PollArgs struct { - Name string - NeedCandidates bool - MaxSignal signal.Serial - Stats map[string]uint64 -} - -type PollRes struct { - Candidates []Candidate - NewInputs []Input - MaxSignal signal.Serial -} - type RunnerConnectArgs struct { Pool, VM int } @@ -199,9 +207,3 @@ type RunTestDoneArgs struct { Info []*ipc.ProgInfo Error string } - -type LogMessageReq struct { - Level int - Name string - Message string -} diff --git a/pkg/signal/signal.go b/pkg/signal/signal.go index 7a2a8bd16..2860be95e 100644 --- a/pkg/signal/signal.go +++ b/pkg/signal/signal.go @@ -11,11 +11,6 @@ type ( type Signal map[elemType]prioType -type Serial struct { - Elems []elemType - Prios []prioType -} - func (s Signal) Len() int { return len(s) } @@ -64,42 +59,6 @@ func FromRaw(raw []uint32, prio uint8) Signal { return s } -func (s Signal) Serialize() Serial { - if s.Empty() { - return Serial{} - } - res := Serial{ - Elems: make([]elemType, len(s)), - Prios: make([]prioType, len(s)), - } - i := 0 - for e, p := range s { - res.Elems[i] = e - res.Prios[i] = p - i++ - } - return res -} - -func (ser *Serial) AddElem(elem uint32, prio prioType) { - ser.Elems = append(ser.Elems, elemType(elem)) - ser.Prios = append(ser.Prios, prio) -} - -func (ser Serial) Deserialize() Signal { - if len(ser.Elems) != len(ser.Prios) { - panic("corrupted Serial") - } - if len(ser.Elems) == 0 { - return nil - } - s := make(Signal, len(ser.Elems)) - for i, e := range ser.Elems { - s[e] = ser.Prios[i] - } - return s -} - func (s Signal) Diff(s1 Signal) Signal { if s1.Empty() { return nil @@ -160,6 +119,36 @@ func (s *Signal) Merge(s1 Signal) { } } +// FilterRaw returns a subset of original raw elements that coincides with the one in Signal. +func (s Signal) FilterRaw(raw []uint32) []uint32 { + var ret []uint32 + for _, e := range raw { + if _, ok := s[elemType(e)]; ok { + ret = append(ret, e) + } + } + return ret +} + +// DiffFromRaw returns a subset of the raw elements that is not present in Signal. +func (s Signal) DiffFromRaw(raw []uint32) []uint32 { + var ret []uint32 + for _, e := range raw { + if _, ok := s[elemType(e)]; !ok { + ret = append(ret, e) + } + } + return ret +} + +func (s Signal) ToRaw() []uint32 { + var raw []uint32 + for e := range s { + raw = append(raw, uint32(e)) + } + return raw +} + type Context struct { Signal Signal Context interface{} -- cgit mrf-deployment