diff options
| author | Aleksandr Nogikh <nogikh@google.com> | 2024-03-13 18:52:18 +0100 |
|---|---|---|
| committer | Aleksandr Nogikh <nogikh@google.com> | 2024-03-18 10:58:52 +0000 |
| commit | fc090d205d8c3d58f190659a98795d89421b7e6b (patch) | |
| tree | 2b33cca3e6366a1565d70fdce01a51d6e50f6448 /pkg | |
| parent | d615901c739a765329b688494cee2f8e1b5037cb (diff) | |
pkg/corpus: a separate package for the corpus functionality
pkg/fuzzer and syz-manager have a common corpus functionality that can
be well be unified.
Create a separate pkg/corpus package that would be used by both of them.
It will simplify further work of moving pkg/fuzzer to the host.
Diffstat (limited to 'pkg')
| -rw-r--r-- | pkg/corpus/corpus.go | 231 | ||||
| -rw-r--r-- | pkg/corpus/corpus_test.go | 98 | ||||
| -rw-r--r-- | pkg/corpus/minimize.go | 41 | ||||
| -rw-r--r-- | pkg/corpus/prio.go | 51 | ||||
| -rw-r--r-- | pkg/corpus/prio_test.go | 49 | ||||
| -rw-r--r-- | pkg/fuzzer/corpus.go | 114 | ||||
| -rw-r--r-- | pkg/fuzzer/corpus_test.go | 98 | ||||
| -rw-r--r-- | pkg/fuzzer/cover.go | 56 | ||||
| -rw-r--r-- | pkg/fuzzer/fuzzer.go | 27 | ||||
| -rw-r--r-- | pkg/fuzzer/fuzzer_test.go | 12 | ||||
| -rw-r--r-- | pkg/fuzzer/job.go | 34 | ||||
| -rw-r--r-- | pkg/rpctype/rpctype.go | 3 |
12 files changed, 569 insertions, 245 deletions
diff --git a/pkg/corpus/corpus.go b/pkg/corpus/corpus.go new file mode 100644 index 000000000..1d14ba026 --- /dev/null +++ b/pkg/corpus/corpus.go @@ -0,0 +1,231 @@ +// Copyright 2024 syzkaller project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +package corpus + +import ( + "context" + "sync" + + "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" +) + +// Corpus object represents a set of syzkaller-found programs that +// cover the kernel up to the currently reached frontiers. +type Corpus struct { + ctx context.Context + mu sync.RWMutex + progs map[string]*Item + signal signal.Signal // signal of inputs in corpus + updates chan<- NewItemEvent + ProgramsList +} + +func NewCorpus(ctx context.Context) *Corpus { + return NewMonitoredCorpus(ctx, nil) +} + +func NewMonitoredCorpus(ctx context.Context, updates chan<- NewItemEvent) *Corpus { + return &Corpus{ + ctx: ctx, + progs: make(map[string]*Item), + updates: updates, + } +} + +// It may happen that a single program is relevant because of several +// sysalls. In that case, there will be several ItemUpdate entities. +type ItemUpdate struct { + Call int + RawCover []uint32 +} + +// Item objects are to be treated as immutable, otherwise it's just +// too hard to synchonize accesses to them across the whole project. +// When Corpus updates one of its items, it saves a copy of it. +type Item struct { + Sig string + Call int + Prog *prog.Prog + ProgData []byte // to save some Serialize() calls + HasAny bool // whether the prog contains squashed arguments + Signal signal.Signal + Cover []uint32 + Updates []ItemUpdate +} + +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 + } + return ".extra" +} + +type NewInput struct { + Prog *prog.Prog + Call int + Signal signal.Signal + Cover []uint32 + RawCover []uint32 +} + +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 + ProgData []byte +} + +func (corpus *Corpus) Save(inp NewInput) { + progData := inp.Prog.Serialize() + sig := hash.String(progData) + + corpus.mu.Lock() + defer corpus.mu.Unlock() + + update := ItemUpdate{ + Call: inp.Call, + RawCover: inp.RawCover, + } + exists := false + if old, ok := corpus.progs[sig]; ok { + exists = true + newSignal := old.Signal.Copy() + newSignal.Merge(inp.Signal) + var newCover cover.Cover + newCover.Merge(old.Cover) + newCover.Merge(inp.Cover) + newItem := &Item{ + Sig: sig, + Prog: old.Prog, + ProgData: progData, + Call: old.Call, + HasAny: old.HasAny, + Signal: newSignal, + Cover: newCover.Serialize(), + Updates: append([]ItemUpdate{}, old.Updates...), + } + const maxUpdates = 32 + if len(newItem.Updates) < maxUpdates { + newItem.Updates = append(newItem.Updates, update) + } + corpus.progs[sig] = newItem + } else { + corpus.progs[sig] = &Item{ + Sig: sig, + Call: inp.Call, + Prog: inp.Prog, + ProgData: progData, + HasAny: inp.Prog.ContainsAny(), + Signal: inp.Signal, + Cover: inp.Cover, + Updates: []ItemUpdate{update}, + } + corpus.saveProgram(inp.Prog, inp.Signal) + } + corpus.signal.Merge(inp.Signal) + if corpus.updates != nil { + select { + case <-corpus.ctx.Done(): + case corpus.updates <- NewItemEvent{ + Sig: sig, + Exists: exists, + ProgData: progData, + }: + } + } +} + +func (corpus *Corpus) DiffSignal(s signal.Signal) signal.Signal { + corpus.mu.RLock() + defer corpus.mu.RUnlock() + return corpus.signal.Diff(s) +} + +func (corpus *Corpus) Signal() signal.Signal { + corpus.mu.RLock() + defer corpus.mu.RUnlock() + return corpus.signal.Copy() +} + +func (corpus *Corpus) Items() []*Item { + corpus.mu.RLock() + defer corpus.mu.RUnlock() + ret := make([]*Item, 0, len(corpus.progs)) + for _, item := range corpus.progs { + ret = append(ret, item) + } + return ret +} + +func (corpus *Corpus) Item(sig string) *Item { + corpus.mu.RLock() + defer corpus.mu.RUnlock() + return corpus.progs[sig] +} + +// Stat is a snapshot of the relevant current state figures. +type Stat struct { + Progs int + Signal int +} + +func (corpus *Corpus) Stat() Stat { + corpus.mu.RLock() + defer corpus.mu.RUnlock() + return Stat{ + Progs: len(corpus.progs), + Signal: len(corpus.signal), + } +} + +type CallCov struct { + Count int + Cover cover.Cover +} + +func (corpus *Corpus) CallCover() map[string]*CallCov { + corpus.mu.RLock() + defer corpus.mu.RUnlock() + calls := make(map[string]*CallCov) + for _, inp := range corpus.progs { + call := inp.StringCall() + if calls[call] == nil { + calls[call] = new(CallCov) + } + cc := calls[call] + cc.Count++ + cc.Cover.Merge(inp.Cover) + } + return calls +} diff --git a/pkg/corpus/corpus_test.go b/pkg/corpus/corpus_test.go new file mode 100644 index 000000000..cce537087 --- /dev/null +++ b/pkg/corpus/corpus_test.go @@ -0,0 +1,98 @@ +// Copyright 2024 syzkaller project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +package corpus + +import ( + "context" + "math/rand" + "testing" + + "github.com/google/syzkaller/pkg/signal" + "github.com/google/syzkaller/prog" + "github.com/google/syzkaller/sys/targets" + "github.com/stretchr/testify/assert" +) + +func TestCorpusOperation(t *testing.T) { + // Basic corpus functionality. + target := getTarget(t, targets.TestOS, targets.TestArch64) + ch := make(chan NewItemEvent) + corpus := NewMonitoredCorpus(context.Background(), ch) + + // First program is saved. + rs := rand.NewSource(0) + inp1 := generateInput(target, rs, 5, 5) + go corpus.Save(inp1) + event := <-ch + progData := inp1.Prog.Serialize() + assert.Equal(t, progData, event.ProgData) + assert.Equal(t, false, event.Exists) + + // Second program is saved for every its call. + inp2 := generateInput(target, rs, 5, 5) + progData = inp2.Prog.Serialize() + for i := 0; i < 5; i++ { + inp2.Call = i + go corpus.Save(inp2) + event := <-ch + assert.Equal(t, progData, event.ProgData) + assert.Equal(t, i != 0, event.Exists) + } + + // Verify that we can query corpus items. + items := corpus.Items() + assert.Len(t, items, 2) + for _, item := range items { + assert.Equal(t, item, corpus.Item(item.Sig)) + } + + // Verify the total signal. + assert.Len(t, corpus.Signal(), 5) + + corpus.Minimize(true) +} + +func TestCorpusSaveConcurrency(t *testing.T) { + target := getTarget(t, targets.TestOS, targets.TestArch64) + corpus := NewCorpus(context.Background()) + + const ( + routines = 10 + iters = 100 + ) + + for i := 0; i < routines; i++ { + go func() { + rs := rand.NewSource(0) + r := rand.New(rs) + for it := 0; it < iters; it++ { + inp := generateInput(target, rs, 10, it) + corpus.Save(inp) + corpus.ChooseProgram(r).Clone() + } + }() + } +} + +func generateInput(target *prog.Target, rs rand.Source, ncalls, sizeSig int) NewInput { + p := target.Generate(rs, ncalls, target.DefaultChoiceTable()) + var raw []uint32 + for i := 1; i <= sizeSig; i++ { + raw = append(raw, uint32(i)) + } + return NewInput{ + Prog: p, + Call: int(rs.Int63() % int64(len(p.Calls))), + Signal: signal.FromRaw(raw, 0), + } +} + +func getTarget(t *testing.T, os, arch string) *prog.Target { + t.Parallel() + target, err := prog.GetTarget(os, arch) + if err != nil { + t.Fatal(err) + } + return target +} diff --git a/pkg/corpus/minimize.go b/pkg/corpus/minimize.go new file mode 100644 index 000000000..b47816ccf --- /dev/null +++ b/pkg/corpus/minimize.go @@ -0,0 +1,41 @@ +// Copyright 2024 syzkaller project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +package corpus + +import ( + "sort" + + "github.com/google/syzkaller/pkg/signal" +) + +func (corpus *Corpus) Minimize(cover bool) { + corpus.mu.Lock() + defer corpus.mu.Unlock() + + inputs := make([]signal.Context, 0, len(corpus.progs)) + for _, inp := range corpus.progs { + inputs = append(inputs, signal.Context{ + Signal: inp.Signal, + Context: inp, + }) + } + + // Note: inputs are unsorted (based on map iteration). + // This gives some intentional non-determinism during minimization. + // However, we want to give preference to non-squashed inputs, + // so let's sort by this criteria. + sort.SliceStable(inputs, func(i, j int) bool { + firstAny := inputs[i].Context.(*Item).HasAny + secondAny := inputs[j].Context.(*Item).HasAny + return !firstAny && secondAny + }) + + corpus.progs = make(map[string]*Item) + corpus.ProgramsList = ProgramsList{} + for _, ctx := range signal.Minimize(inputs) { + inp := ctx.(*Item) + corpus.progs[inp.Sig] = inp + corpus.saveProgram(inp.Prog, inp.Signal) + } +} diff --git a/pkg/corpus/prio.go b/pkg/corpus/prio.go new file mode 100644 index 000000000..a4d85a518 --- /dev/null +++ b/pkg/corpus/prio.go @@ -0,0 +1,51 @@ +// Copyright 2024 syzkaller project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +package corpus + +import ( + "math/rand" + "sort" + "sync" + + "github.com/google/syzkaller/pkg/signal" + "github.com/google/syzkaller/prog" +) + +type ProgramsList struct { + mu sync.RWMutex + progs []*prog.Prog + sumPrios int64 + accPrios []int64 +} + +func (pl *ProgramsList) saveProgram(p *prog.Prog, signal signal.Signal) { + pl.mu.Lock() + defer pl.mu.Unlock() + prio := int64(len(signal)) + if prio == 0 { + prio = 1 + } + pl.sumPrios += prio + pl.accPrios = append(pl.accPrios, pl.sumPrios) + pl.progs = append(pl.progs, p) +} + +func (pl *ProgramsList) ChooseProgram(r *rand.Rand) *prog.Prog { + pl.mu.RLock() + defer pl.mu.RUnlock() + if len(pl.progs) == 0 { + return nil + } + randVal := r.Int63n(pl.sumPrios + 1) + idx := sort.Search(len(pl.accPrios), func(i int) bool { + return pl.accPrios[i] >= randVal + }) + return pl.progs[idx] +} + +func (pl *ProgramsList) Programs() []*prog.Prog { + pl.mu.RLock() + defer pl.mu.RUnlock() + return pl.progs +} diff --git a/pkg/corpus/prio_test.go b/pkg/corpus/prio_test.go new file mode 100644 index 000000000..3eec54bed --- /dev/null +++ b/pkg/corpus/prio_test.go @@ -0,0 +1,49 @@ +// Copyright 2024 syzkaller project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +package corpus + +import ( + "context" + "math" + "math/rand" + "testing" + + "github.com/google/syzkaller/prog" + "github.com/google/syzkaller/sys/targets" +) + +func TestChooseProgram(t *testing.T) { + rs := rand.NewSource(0) + r := rand.New(rs) + target := getTarget(t, targets.TestOS, targets.TestArch64) + corpus := NewCorpus(context.Background()) + + const ( + maxIters = 1000 + sizeCorpus = 1000 + eps = 0.01 + ) + + priorities := make(map[*prog.Prog]int64) + for i := 0; i < sizeCorpus; i++ { + sizeSig := i + 1 + if sizeSig%250 == 0 { + sizeSig = 0 + } + inp := generateInput(target, rs, 10, sizeSig) + corpus.Save(inp) + priorities[inp.Prog] = int64(len(inp.Signal)) + } + counters := make(map[*prog.Prog]int) + for it := 0; it < maxIters; it++ { + counters[corpus.ChooseProgram(r)]++ + } + for p, prio := range priorities { + prob := float64(prio) / float64(corpus.sumPrios) + diff := math.Abs(prob*maxIters - float64(counters[p])) + if diff > eps*maxIters { + t.Fatalf("the difference (%f) is higher than %f%%", diff, eps*100) + } + } +} diff --git a/pkg/fuzzer/corpus.go b/pkg/fuzzer/corpus.go deleted file mode 100644 index b92ab1c64..000000000 --- a/pkg/fuzzer/corpus.go +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2024 syzkaller project authors. All rights reserved. -// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. - -package fuzzer - -import ( - "math/rand" - "sort" - "sync" - - "github.com/google/syzkaller/pkg/hash" - "github.com/google/syzkaller/pkg/signal" - "github.com/google/syzkaller/prog" -) - -type Corpus struct { - mu sync.RWMutex - progs []*prog.Prog - hashes map[hash.Sig]struct{} - sumPrios int64 - accPrios []int64 - signal signal.Signal // signal of inputs in corpus - maxSignal signal.Signal // max signal ever observed (including flakes) - newSignal signal.Signal -} - -// CorpusStat is a snapshot of the relevant current state figures. -type CorpusStat struct { - Progs int - Signal int - MaxSignal int -} - -func newCorpus() *Corpus { - return &Corpus{ - hashes: make(map[hash.Sig]struct{}), - } -} - -// TODO: maybe we want to treat progs from other fuzzers exactly like -// candidates? And even triage them? -func (corpus *Corpus) Save(p *prog.Prog, signal signal.Signal, sig hash.Sig) { - corpus.mu.Lock() - defer corpus.mu.Unlock() - if _, ok := corpus.hashes[sig]; !ok { - corpus.progs = append(corpus.progs, p) - corpus.hashes[sig] = struct{}{} - prio := int64(len(signal)) - if prio == 0 { - prio = 1 - } - corpus.sumPrios += prio - corpus.accPrios = append(corpus.accPrios, corpus.sumPrios) - } - corpus.signal.Merge(signal) - corpus.maxSignal.Merge(signal) -} - -// Signal that should no longer be chased after. -func (corpus *Corpus) AddMaxSignal(sign signal.Signal) { - // TODO: how do we ensure occasional drop of this max cover? - corpus.mu.Lock() - defer corpus.mu.Unlock() - corpus.maxSignal.Merge(sign) -} - -func (corpus *Corpus) AddRawMaxSignal(signal []uint32, prio uint8) signal.Signal { - corpus.mu.Lock() - defer corpus.mu.Unlock() - diff := corpus.maxSignal.DiffRaw(signal, prio) - if diff.Empty() { - return diff - } - corpus.maxSignal.Merge(diff) - corpus.newSignal.Merge(diff) - return diff -} - -func (corpus *Corpus) chooseProgram(r *rand.Rand) *prog.Prog { - corpus.mu.RLock() - defer corpus.mu.RUnlock() - if len(corpus.progs) == 0 { - return nil - } - randVal := r.Int63n(corpus.sumPrios + 1) - idx := sort.Search(len(corpus.accPrios), func(i int) bool { - return corpus.accPrios[i] >= randVal - }) - return corpus.progs[idx] -} - -func (corpus *Corpus) Programs() []*prog.Prog { - corpus.mu.RLock() - defer corpus.mu.RUnlock() - return corpus.progs -} - -func (corpus *Corpus) GrabNewSignal() signal.Signal { - corpus.mu.Lock() - defer corpus.mu.Unlock() - sign := corpus.newSignal - corpus.newSignal = nil - return sign -} - -func (corpus *Corpus) Stat() CorpusStat { - corpus.mu.RLock() - defer corpus.mu.RUnlock() - return CorpusStat{ - Progs: len(corpus.progs), - Signal: len(corpus.signal), - MaxSignal: len(corpus.maxSignal), - } -} diff --git a/pkg/fuzzer/corpus_test.go b/pkg/fuzzer/corpus_test.go deleted file mode 100644 index 0b62d8c5a..000000000 --- a/pkg/fuzzer/corpus_test.go +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright 2024 syzkaller project authors. All rights reserved. -// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. - -package fuzzer - -import ( - "math" - "math/rand" - "testing" - - "github.com/google/syzkaller/pkg/hash" - "github.com/google/syzkaller/pkg/signal" - "github.com/google/syzkaller/prog" - "github.com/google/syzkaller/sys/targets" -) - -type InputTest struct { - p *prog.Prog - sign signal.Signal - sig hash.Sig -} - -func TestChooseProgram(t *testing.T) { - rs := rand.NewSource(0) - r := rand.New(rs) - target := getTarget(t, targets.TestOS, targets.TestArch64) - corpus := newCorpus() - - const ( - maxIters = 1000 - sizeCorpus = 1000 - eps = 0.01 - ) - - priorities := make(map[*prog.Prog]int64) - for i := 0; i < sizeCorpus; i++ { - sizeSig := i + 1 - if sizeSig%250 == 0 { - sizeSig = 0 - } - inp := generateInput(target, rs, 10, sizeSig) - corpus.Save(inp.p, inp.sign, inp.sig) - priorities[inp.p] = int64(len(inp.sign)) - } - counters := make(map[*prog.Prog]int) - for it := 0; it < maxIters; it++ { - counters[corpus.chooseProgram(r)]++ - } - for p, prio := range priorities { - prob := float64(prio) / float64(corpus.sumPrios) - diff := math.Abs(prob*maxIters - float64(counters[p])) - if diff > eps*maxIters { - t.Fatalf("the difference (%f) is higher than %f%%", diff, eps*100) - } - } -} - -func TestCorpusSaveConcurrency(t *testing.T) { - target := getTarget(t, targets.TestOS, targets.TestArch64) - corpus := newCorpus() - - const ( - routines = 10 - iters = 100 - ) - - for i := 0; i < routines; i++ { - go func() { - rs := rand.NewSource(0) - r := rand.New(rs) - for it := 0; it < iters; it++ { - inp := generateInput(target, rs, 10, it) - corpus.Save(inp.p, inp.sign, inp.sig) - corpus.chooseProgram(r).Clone() - } - }() - } -} - -func generateInput(target *prog.Target, rs rand.Source, ncalls, sizeSig int) (inp InputTest) { - inp.p = target.Generate(rs, ncalls, target.DefaultChoiceTable()) - var raw []uint32 - for i := 1; i <= sizeSig; i++ { - raw = append(raw, uint32(i)) - } - inp.sign = signal.FromRaw(raw, 0) - inp.sig = hash.Hash(inp.p.Serialize()) - return -} - -func getTarget(t *testing.T, os, arch string) *prog.Target { - t.Parallel() - target, err := prog.GetTarget(os, arch) - if err != nil { - t.Fatal(err) - } - return target -} diff --git a/pkg/fuzzer/cover.go b/pkg/fuzzer/cover.go new file mode 100644 index 000000000..cc18862bd --- /dev/null +++ b/pkg/fuzzer/cover.go @@ -0,0 +1,56 @@ +// Copyright 2024 syzkaller project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +package fuzzer + +import ( + "sync" + + "github.com/google/syzkaller/pkg/signal" +) + +// Cover keeps track of the signal known to the fuzzer. +type Cover struct { + mu sync.RWMutex + maxSignal signal.Signal // max signal ever observed (including flakes) + newSignal signal.Signal // newly identified max signal +} + +// Signal that should no longer be chased after. +func (cover *Cover) AddMaxSignal(sign signal.Signal) { + cover.mu.Lock() + defer cover.mu.Unlock() + cover.maxSignal.Merge(sign) +} + +func (cover *Cover) addRawMaxSignal(signal []uint32, prio uint8) signal.Signal { + cover.mu.Lock() + defer cover.mu.Unlock() + diff := cover.maxSignal.DiffRaw(signal, prio) + if diff.Empty() { + return diff + } + cover.maxSignal.Merge(diff) + cover.newSignal.Merge(diff) + return diff +} + +func (cover *Cover) GrabNewSignal() signal.Signal { + cover.mu.Lock() + defer cover.mu.Unlock() + sign := cover.newSignal + cover.newSignal = nil + return sign +} + +type CoverStat struct { + MaxSignal int +} + +func (cover *Cover) Stat() CoverStat { + cover.mu.RLock() + defer cover.mu.RUnlock() + return CoverStat{ + MaxSignal: len(cover.maxSignal), + } +} diff --git a/pkg/fuzzer/fuzzer.go b/pkg/fuzzer/fuzzer.go index 64be8325c..17513716a 100644 --- a/pkg/fuzzer/fuzzer.go +++ b/pkg/fuzzer/fuzzer.go @@ -12,15 +12,15 @@ import ( "sync/atomic" "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/prog" ) type Fuzzer struct { Config *Config - Corpus *Corpus + Cover *Cover NeedCandidates chan struct{} ctx context.Context @@ -49,7 +49,7 @@ func NewFuzzer(ctx context.Context, cfg *Config, rnd *rand.Rand, target *prog.Target) *Fuzzer { f := &Fuzzer{ Config: cfg, - Corpus: newCorpus(), + Cover: &Cover{}, NeedCandidates: make(chan struct{}, 1), ctx: ctx, @@ -75,6 +75,7 @@ func NewFuzzer(ctx context.Context, cfg *Config, rnd *rand.Rand, type Config struct { Debug bool + Corpus *corpus.Corpus Logf func(level int, msg string, args ...interface{}) Coverage bool FaultInjection bool @@ -87,7 +88,7 @@ type Config struct { // If the number of queued candidates is less than MinCandidates, // NeedCandidates is triggered. MinCandidates uint - NewInputs chan rpctype.Input + NewInputs chan corpus.NewInput } type Request struct { @@ -133,7 +134,7 @@ func (fuzzer *Fuzzer) Done(req *Request, res *Result) { func (fuzzer *Fuzzer) triageProgCall(p *prog.Prog, info *ipc.CallInfo, call int, flags ProgTypes) { prio := signalPrio(p, info, call) - newMaxSignal := fuzzer.Corpus.AddRawMaxSignal(info.Signal, prio) + newMaxSignal := fuzzer.Cover.addRawMaxSignal(info.Signal, prio) if newMaxSignal.Empty() { return } @@ -304,12 +305,12 @@ func (fuzzer *Fuzzer) choiceTableUpdater() { return case <-fuzzer.ctRegenerate: } - fuzzer.updateChoiceTable(fuzzer.Corpus.Programs()) + fuzzer.updateChoiceTable(fuzzer.Config.Corpus.Programs()) } } func (fuzzer *Fuzzer) ChoiceTable() *prog.ChoiceTable { - progs := fuzzer.Corpus.Programs() + progs := fuzzer.Config.Corpus.Programs() fuzzer.ctMu.Lock() defer fuzzer.ctMu.Unlock() @@ -348,3 +349,15 @@ func (fuzzer *Fuzzer) logCurrentStats() { fuzzer.Logf(0, "%s", str) } } + +type Stat struct { + CoverStat + corpus.Stat +} + +func (fuzzer *Fuzzer) Stat() Stat { + return Stat{ + CoverStat: fuzzer.Cover.Stat(), + Stat: fuzzer.Config.Corpus.Stat(), + } +} diff --git a/pkg/fuzzer/fuzzer_test.go b/pkg/fuzzer/fuzzer_test.go index 1896b2b84..7000bb062 100644 --- a/pkg/fuzzer/fuzzer_test.go +++ b/pkg/fuzzer/fuzzer_test.go @@ -18,10 +18,10 @@ import ( "testing" "time" + "github.com/google/syzkaller/pkg/corpus" "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" @@ -41,7 +41,8 @@ func TestFuzz(t *testing.T) { defer cancel() fuzzer := NewFuzzer(ctx, &Config{ - Debug: true, + Debug: true, + Corpus: corpus.NewCorpus(ctx), Logf: func(level int, msg string, args ...interface{}) { if level > 1 { return @@ -52,7 +53,7 @@ func TestFuzz(t *testing.T) { EnabledCalls: map[*prog.Syscall]bool{ target.SyscallMap["syz_test_fuzzer1"]: true, }, - NewInputs: make(chan rpctype.Input), + NewInputs: make(chan corpus.NewInput), }, rand.New(testutil.RandSource(t)), target) go func() { @@ -77,7 +78,7 @@ func TestFuzz(t *testing.T) { tf.wait() t.Logf("resulting corpus:") - for _, p := range fuzzer.Corpus.Programs() { + for _, p := range fuzzer.Config.Corpus.Programs() { t.Logf("-----") t.Logf("%s", p.Serialize()) } @@ -99,6 +100,7 @@ func BenchmarkFuzzer(b *testing.B) { calls[c] = true } fuzzer := NewFuzzer(ctx, &Config{ + Corpus: corpus.NewCorpus(ctx), Coverage: true, EnabledCalls: calls, }, rand.New(rand.NewSource(time.Now().UnixNano())), target) @@ -160,7 +162,7 @@ func (f *testFuzzer) oneMore() bool { defer f.mu.Unlock() f.iter++ if f.iter%100 == 0 { - stat := f.fuzzer.Corpus.Stat() + stat := f.fuzzer.Stat() f.t.Logf("<iter %d>: corpus %d, signal %d, max signal %d, crash types %d", f.iter, stat.Progs, stat.Signal, stat.MaxSignal, len(f.crashes)) } diff --git a/pkg/fuzzer/job.go b/pkg/fuzzer/job.go index a0295affb..63493c914 100644 --- a/pkg/fuzzer/job.go +++ b/pkg/fuzzer/job.go @@ -7,10 +7,9 @@ import ( "fmt" "math/rand" + "github.com/google/syzkaller/pkg/corpus" "github.com/google/syzkaller/pkg/cover" - "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" ) @@ -71,7 +70,7 @@ func genProgRequest(fuzzer *Fuzzer, rnd *rand.Rand) *Request { } func mutateProgRequest(fuzzer *Fuzzer, rnd *rand.Rand) *Request { - p := fuzzer.Corpus.chooseProgram(rnd) + p := fuzzer.Config.Corpus.ChooseProgram(rnd) if p == nil { return nil } @@ -80,7 +79,7 @@ func mutateProgRequest(fuzzer *Fuzzer, rnd *rand.Rand) *Request { prog.RecommendedCalls, fuzzer.ChoiceTable(), fuzzer.Config.NoMutateCalls, - fuzzer.Corpus.Programs(), + fuzzer.Config.Corpus.Programs(), ) return &Request{ Prog: newP, @@ -126,10 +125,9 @@ func triageJobPrio(flags ProgTypes) jobPriority { } func (job *triageJob) run(fuzzer *Fuzzer) { - callName := ".extra" logCallName := "extra" if job.call != -1 { - callName = job.p.Calls[job.call].Meta.Name + callName := job.p.Calls[job.call].Meta.Name logCallName = fmt.Sprintf("call #%v %v", job.call, callName) } fuzzer.Logf(3, "triaging input for %v (new signal=%v)", logCallName, job.newSignal.Len()) @@ -144,9 +142,7 @@ func (job *triageJob) run(fuzzer *Fuzzer) { return } } - data := job.p.Serialize() - fuzzer.Logf(2, "added new input for %q to the corpus:\n%s", - logCallName, string(data)) + fuzzer.Logf(2, "added new input for %q to the corpus:\n%s", logCallName, job.p.String()) if job.flags&ProgSmashed == 0 { fuzzer.startJob(&smashJob{ p: job.p.Clone(), @@ -154,18 +150,18 @@ func (job *triageJob) run(fuzzer *Fuzzer) { jobPriority: newJobPriority(smashPrio), }) } - fuzzer.Corpus.Save(job.p, info.stableSignal, hash.Hash(data)) + input := corpus.NewInput{ + Prog: job.p, + Call: job.call, + Signal: info.stableSignal, + 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 <- rpctype.Input{ - Call: callName, - CallID: job.call, - Prog: data, - Signal: info.stableSignal.Serialize(), - Cover: info.cover.Serialize(), - RawCover: info.rawCover, - }: + case fuzzer.Config.NewInputs <- input: } } } @@ -298,7 +294,7 @@ func (job *smashJob) run(fuzzer *Fuzzer) { p.Mutate(rnd, prog.RecommendedCalls, fuzzer.ChoiceTable(), fuzzer.Config.NoMutateCalls, - fuzzer.Corpus.Programs()) + fuzzer.Config.Corpus.Programs()) result := fuzzer.exec(job, &Request{ Prog: p, NeedSignal: true, diff --git a/pkg/rpctype/rpctype.go b/pkg/rpctype/rpctype.go index 3b1244662..efd9d6589 100644 --- a/pkg/rpctype/rpctype.go +++ b/pkg/rpctype/rpctype.go @@ -14,11 +14,10 @@ import ( ) type Input struct { - Call string + 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 - CallID int // seq number of call in the prog to which the item is related (-1 for extra) RawCover []uint32 } |
