aboutsummaryrefslogtreecommitdiffstats
path: root/pkg
diff options
context:
space:
mode:
authorAleksandr Nogikh <nogikh@google.com>2024-03-13 18:52:18 +0100
committerAleksandr Nogikh <nogikh@google.com>2024-03-18 10:58:52 +0000
commitfc090d205d8c3d58f190659a98795d89421b7e6b (patch)
tree2b33cca3e6366a1565d70fdce01a51d6e50f6448 /pkg
parentd615901c739a765329b688494cee2f8e1b5037cb (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.go231
-rw-r--r--pkg/corpus/corpus_test.go98
-rw-r--r--pkg/corpus/minimize.go41
-rw-r--r--pkg/corpus/prio.go51
-rw-r--r--pkg/corpus/prio_test.go49
-rw-r--r--pkg/fuzzer/corpus.go114
-rw-r--r--pkg/fuzzer/corpus_test.go98
-rw-r--r--pkg/fuzzer/cover.go56
-rw-r--r--pkg/fuzzer/fuzzer.go27
-rw-r--r--pkg/fuzzer/fuzzer_test.go12
-rw-r--r--pkg/fuzzer/job.go34
-rw-r--r--pkg/rpctype/rpctype.go3
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
}