aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--prog/encoding.go14
-rw-r--r--prog/encoding_test.go25
-rw-r--r--prog/generation.go11
-rw-r--r--prog/mutation.go45
-rw-r--r--prog/mutation_test.go6
-rw-r--r--prog/rand.go89
-rw-r--r--prog/rand_test.go10
-rw-r--r--syz-fuzzer/fuzzer.go51
-rw-r--r--syz-fuzzer/proc.go10
-rw-r--r--syz-hub/state/state.go44
-rw-r--r--syz-manager/hub.go3
-rw-r--r--syz-manager/manager.go22
-rw-r--r--syz-manager/rpc.go11
-rw-r--r--tools/syz-mutate/mutate.go2
-rw-r--r--tools/syz-stress/stress.go10
15 files changed, 222 insertions, 131 deletions
diff --git a/prog/encoding.go b/prog/encoding.go
index b36bf9637..6bded49d5 100644
--- a/prog/encoding.go
+++ b/prog/encoding.go
@@ -1130,8 +1130,9 @@ func (p *parser) strictFailf(msg string, args ...interface{}) {
// CallSet returns a set of all calls in the program.
// It does very conservative parsing and is intended to parse past/future serialization formats.
-func CallSet(data []byte) (map[string]struct{}, error) {
+func CallSet(data []byte) (map[string]struct{}, int, error) {
calls := make(map[string]struct{})
+ ncalls := 0
s := bufio.NewScanner(bytes.NewReader(data))
s.Buffer(nil, maxLineLen)
for s.Scan() {
@@ -1141,7 +1142,7 @@ func CallSet(data []byte) (map[string]struct{}, error) {
}
bracket := bytes.IndexByte(ln, '(')
if bracket == -1 {
- return nil, fmt.Errorf("line does not contain opening bracket")
+ return nil, 0, fmt.Errorf("line does not contain opening bracket")
}
call := ln[:bracket]
if eq := bytes.IndexByte(call, '='); eq != -1 {
@@ -1152,15 +1153,16 @@ func CallSet(data []byte) (map[string]struct{}, error) {
call = call[eq:]
}
if len(call) == 0 {
- return nil, fmt.Errorf("call name is empty")
+ return nil, 0, fmt.Errorf("call name is empty")
}
calls[string(call)] = struct{}{}
+ ncalls++
}
if err := s.Err(); err != nil {
- return nil, err
+ return nil, 0, err
}
if len(calls) == 0 {
- return nil, fmt.Errorf("program does not contain any calls")
+ return nil, 0, fmt.Errorf("program does not contain any calls")
}
- return calls, nil
+ return calls, ncalls, nil
}
diff --git a/prog/encoding_test.go b/prog/encoding_test.go
index e5aa82146..c62e6647f 100644
--- a/prog/encoding_test.go
+++ b/prog/encoding_test.go
@@ -53,29 +53,34 @@ func TestSerializeData(t *testing.T) {
func TestCallSet(t *testing.T) {
t.Parallel()
tests := []struct {
- prog string
- ok bool
- calls []string
+ prog string
+ ok bool
+ calls []string
+ ncalls int
}{
{
"",
false,
[]string{},
+ 0,
},
{
"r0 = (foo)",
false,
[]string{},
+ 0,
},
{
"getpid()",
true,
[]string{"getpid"},
+ 1,
},
{
"r11 = getpid()",
true,
[]string{"getpid"},
+ 1,
},
{
"getpid()\n" +
@@ -86,11 +91,12 @@ func TestCallSet(t *testing.T) {
"close$foo(&(0x0000) = {})\n",
true,
[]string{"getpid", "open", "close$foo"},
+ 4,
},
}
for i, test := range tests {
t.Run(fmt.Sprint(i), func(t *testing.T) {
- calls, err := CallSet([]byte(test.prog))
+ calls, ncalls, err := CallSet([]byte(test.prog))
if err != nil && test.ok {
t.Fatalf("parsing failed: %v", err)
}
@@ -102,6 +108,9 @@ func TestCallSet(t *testing.T) {
if !reflect.DeepEqual(callArray, test.calls) {
t.Fatalf("got call set %+v, expect %+v", callArray, test.calls)
}
+ if ncalls != test.ncalls {
+ t.Fatalf("got %v calls, expect %v", ncalls, test.ncalls)
+ }
})
}
}
@@ -109,12 +118,13 @@ func TestCallSet(t *testing.T) {
func TestCallSetRandom(t *testing.T) {
target, rs, iters := initTest(t)
for i := 0; i < iters; i++ {
- p := target.Generate(rs, 10, nil)
+ const ncalls = 10
+ p := target.Generate(rs, ncalls, nil)
calls0 := make(map[string]struct{})
for _, c := range p.Calls {
calls0[c.Meta.Name] = struct{}{}
}
- calls1, err := CallSet(p.Serialize())
+ calls1, ncalls1, err := CallSet(p.Serialize())
if err != nil {
t.Fatalf("CallSet failed: %v", err)
}
@@ -123,6 +133,9 @@ func TestCallSetRandom(t *testing.T) {
if !reflect.DeepEqual(callArray0, callArray1) {
t.Fatalf("got call set:\n%+v\nexpect:\n%+v", callArray1, callArray0)
}
+ if ncalls1 != ncalls {
+ t.Fatalf("got %v calls, expect %v", ncalls1, ncalls)
+ }
}
}
diff --git a/prog/generation.go b/prog/generation.go
index 85d1bbb02..1ceda4820 100644
--- a/prog/generation.go
+++ b/prog/generation.go
@@ -7,8 +7,8 @@ import (
"math/rand"
)
-// Generate generates a random program of length ~ncalls.
-// calls is a set of allowed syscalls, if nil all syscalls are used.
+// Generate generates a random program with ncalls calls.
+// ct contains a set of allowed syscalls, if nil all syscalls are used.
func (target *Target) Generate(rs rand.Source, ncalls int, ct *ChoiceTable) *Prog {
p := &Prog{
Target: target,
@@ -22,6 +22,13 @@ func (target *Target) Generate(rs rand.Source, ncalls int, ct *ChoiceTable) *Pro
p.Calls = append(p.Calls, c)
}
}
+ // For the last generated call we could get additional calls that create
+ // resources and overflow ncalls. Remove some of these calls.
+ // The resources in the last call will be replaced with the default values,
+ // which is exactly what we want.
+ for len(p.Calls) > ncalls {
+ p.removeCall(ncalls - 1)
+ }
p.debugValidate()
return p
}
diff --git a/prog/mutation.go b/prog/mutation.go
index b50f48803..62acba586 100644
--- a/prog/mutation.go
+++ b/prog/mutation.go
@@ -23,6 +23,9 @@ const maxBlobLen = uint64(100 << 10)
// corpus: The entire corpus, including original program p.
func (p *Prog) Mutate(rs rand.Source, ncalls int, ct *ChoiceTable, corpus []*Prog) {
r := newRand(p.Target, rs)
+ if ncalls < len(p.Calls) {
+ ncalls = len(p.Calls)
+ }
ctx := &mutator{
p: p,
r: r,
@@ -30,7 +33,7 @@ func (p *Prog) Mutate(rs rand.Source, ncalls int, ct *ChoiceTable, corpus []*Pro
ct: ct,
corpus: corpus,
}
- for stop, ok := false, false; !stop; stop = ok && r.oneOf(3) {
+ for stop, ok := false, false; !stop; stop = ok && len(p.Calls) != 0 && r.oneOf(3) {
switch {
case r.oneOf(5):
// Not all calls have anything squashable,
@@ -50,6 +53,9 @@ func (p *Prog) Mutate(rs rand.Source, ncalls int, ct *ChoiceTable, corpus []*Pro
p.Target.SanitizeCall(c)
}
p.debugValidate()
+ if got := len(p.Calls); got < 1 || got > ncalls {
+ panic(fmt.Sprintf("bad number of calls after mutation: %v, want [1, %v]", got, ncalls))
+ }
}
// Internal state required for performing mutations -- currently this matches
@@ -67,7 +73,7 @@ type mutator struct {
// (exclusive) concatenated with p0's calls from index i (inclusive).
func (ctx *mutator) splice() bool {
p, r := ctx.p, ctx.r
- if len(ctx.corpus) == 0 || len(p.Calls) == 0 {
+ if len(ctx.corpus) == 0 || len(p.Calls) == 0 || len(p.Calls) >= ctx.ncalls {
return false
}
p0 := ctx.corpus[r.Intn(len(ctx.corpus))]
@@ -135,8 +141,10 @@ func (ctx *mutator) insertCall() bool {
}
s := analyze(ctx.ct, ctx.corpus, p, c)
calls := r.generateCall(s, p, idx)
- // TODO: the program might have more than ncalls
p.insertBefore(c, calls)
+ for len(p.Calls) > ctx.ncalls {
+ p.removeCall(idx)
+ }
return true
}
@@ -158,11 +166,11 @@ func (ctx *mutator) mutateArg() bool {
return false
}
- c, ok := chooseCall(p, r)
- if !ok {
+ idx := chooseCall(p, r)
+ if idx < 0 {
return false
}
- s := analyze(ctx.ct, ctx.corpus, p, c)
+ c := p.Calls[idx]
updateSizes := true
for stop, ok := false, false; !stop; stop = ok && r.oneOf(3) {
ok = true
@@ -171,24 +179,33 @@ func (ctx *mutator) mutateArg() bool {
if len(ma.args) == 0 {
return false
}
+ s := analyze(ctx.ct, ctx.corpus, p, c)
chosenIdx := randomChoice(ma.priorities, r)
- arg, ctx := ma.args[chosenIdx], ma.ctxes[chosenIdx]
- calls, ok1 := p.Target.mutateArg(r, s, arg, ctx, &updateSizes)
+ arg, argCtx := ma.args[chosenIdx], ma.ctxes[chosenIdx]
+ calls, ok1 := p.Target.mutateArg(r, s, arg, argCtx, &updateSizes)
if !ok1 {
ok = false
continue
}
p.insertBefore(c, calls)
+ idx += len(calls)
+ for len(p.Calls) > ctx.ncalls {
+ idx--
+ p.removeCall(idx)
+ }
+ if idx < 0 || idx >= len(p.Calls) || p.Calls[idx] != c {
+ panic(fmt.Sprintf("wrong call index: idx=%v calls=%v p.Calls=%v ncalls=%v",
+ idx, len(calls), len(p.Calls), ctx.ncalls))
+ }
if updateSizes {
p.Target.assignSizesCall(c)
}
- p.Target.SanitizeCall(c)
}
return true
}
// Select a call based on the complexity of the arguments.
-func chooseCall(p *Prog, r *randGen) (*Call, bool) {
+func chooseCall(p *Prog, r *randGen) int {
var callPriorities []float64
noArgs := true
@@ -207,10 +224,9 @@ func chooseCall(p *Prog, r *randGen) (*Call, bool) {
// Calls without arguments.
if noArgs {
- return nil, false
+ return -1
}
-
- return p.Calls[randomChoice(callPriorities, r)], true
+ return randomChoice(callPriorities, r)
}
// Generate a random index from a given 1-D array of priorities.
@@ -241,9 +257,6 @@ func (target *Target) mutateArg(r *randGen, s *state, arg Arg, ctx ArgCtx, updat
newArg := r.allocAddr(s, base.Type(), base.Res.Size(), base.Res)
replaceArg(base, newArg)
}
- for _, c := range calls {
- target.SanitizeCall(c)
- }
return calls, true
}
diff --git a/prog/mutation_test.go b/prog/mutation_test.go
index 89fe5473a..0d12699fc 100644
--- a/prog/mutation_test.go
+++ b/prog/mutation_test.go
@@ -136,7 +136,7 @@ func TestMutateArgument(t *testing.T) {
ctx := &mutator{
p: p1,
r: newRand(p1.Target, rs),
- ncalls: 0,
+ ncalls: 2 * len(p.Calls),
ct: ct,
corpus: nil,
}
@@ -163,7 +163,7 @@ func TestSizeMutateArg(t *testing.T) {
ctx := &mutator{
p: p1,
r: r,
- ncalls: 10,
+ ncalls: 2 * len(p.Calls),
ct: ct,
corpus: nil,
}
@@ -451,7 +451,7 @@ func runMutationTests(t *testing.T, tests [][2]string, valid bool) {
t.Fatalf("failed to deserialize the program: %v", err)
}
want := goal.Serialize()
- iters := int(1e5)
+ iters := int(1e6)
if !valid {
iters /= 10
}
diff --git a/prog/rand.go b/prog/rand.go
index bb3c81789..bf6d66e9a 100644
--- a/prog/rand.go
+++ b/prog/rand.go
@@ -16,6 +16,14 @@ import (
_ "github.com/google/syzkaller/pkg/ifuzz/generated" // pull in generated instruction descriptions
)
+const (
+ // "Recommended" number of calls in programs that we try to aim at during fuzzing.
+ RecommendedCalls = 20
+ // "Recommended" max number of calls in programs.
+ // If we receive longer programs from hub/corpus we discard them.
+ MaxCalls = 40
+)
+
type randGen struct {
*rand.Rand
target *Target
@@ -344,8 +352,7 @@ func (r *randGen) allocVMA(s *state, typ Type, numPages uint64) *PointerArg {
func (r *randGen) createResource(s *state, res *ResourceType) (arg Arg, calls []*Call) {
if r.inCreateResource {
- special := res.SpecialValues()
- return MakeResultArg(res, nil, special[r.Intn(len(special))]), nil
+ return nil, nil
}
r.inCreateResource = true
defer func() { r.inCreateResource = false }()
@@ -675,44 +682,27 @@ func (r *randGen) generateArgImpl(s *state, typ Type, ignoreSpecial bool) (arg A
}
func (a *ResourceType) generate(r *randGen, s *state) (arg Arg, calls []*Call) {
- switch {
- case r.nOutOf(2, 5):
- var res *ResultArg
- res, calls = resourceCentric(a, s, r)
- if res == nil {
- return r.createResource(s, a)
- }
- arg = MakeResultArg(a, res, 0)
- case r.nOutOf(1, 2):
- // Get an existing resource.
- alltypes := make([][]*ResultArg, 0, len(s.resources))
- for _, res1 := range s.resources {
- alltypes = append(alltypes, res1)
- }
- sort.Slice(alltypes, func(i, j int) bool {
- return alltypes[i][0].Type().Name() < alltypes[j][0].Type().Name()
- })
- var allres []*ResultArg
- for _, res1 := range alltypes {
- name1 := res1[0].Type().Name()
- if r.target.isCompatibleResource(a.Desc.Name, name1) ||
- r.oneOf(20) && r.target.isCompatibleResource(a.Desc.Kind[0], name1) {
- allres = append(allres, res1...)
- }
+ if r.oneOf(3) {
+ arg = r.existingResource(s, a)
+ if arg != nil {
+ return
}
- if len(allres) != 0 {
- arg = MakeResultArg(a, allres[r.Intn(len(allres))], 0)
- } else {
- arg, calls = r.createResource(s, a)
+ }
+ if r.nOutOf(2, 3) {
+ arg, calls = r.resourceCentric(s, a)
+ if arg != nil {
+ return
}
- case r.nOutOf(2, 3):
- // Create a new resource.
+ }
+ if r.nOutOf(4, 5) {
arg, calls = r.createResource(s, a)
- default:
- special := a.SpecialValues()
- arg = MakeResultArg(a, nil, special[r.Intn(len(special))])
+ if arg != nil {
+ return
+ }
}
- return arg, calls
+ special := a.SpecialValues()
+ arg = MakeResultArg(a, nil, special[r.Intn(len(special))])
+ return
}
func (a *BufferType) generate(r *randGen, s *state) (arg Arg, calls []*Call) {
@@ -841,9 +831,32 @@ func (a *CsumType) generate(r *randGen, s *state) (arg Arg, calls []*Call) {
return MakeConstArg(a, 0), nil
}
+func (r *randGen) existingResource(s *state, res *ResourceType) Arg {
+ alltypes := make([][]*ResultArg, 0, len(s.resources))
+ for _, res1 := range s.resources {
+ alltypes = append(alltypes, res1)
+ }
+ sort.Slice(alltypes, func(i, j int) bool {
+ return alltypes[i][0].Type().Name() < alltypes[j][0].Type().Name()
+ })
+ var allres []*ResultArg
+ for _, res1 := range alltypes {
+ name1 := res1[0].Type().Name()
+ if r.target.isCompatibleResource(res.Desc.Name, name1) ||
+ r.oneOf(50) && r.target.isCompatibleResource(res.Desc.Kind[0], name1) {
+ allres = append(allres, res1...)
+ }
+ }
+ if len(allres) == 0 {
+ return nil
+ }
+ return MakeResultArg(res, allres[r.Intn(len(allres))], 0)
+}
+
// Finds a compatible resource with the type `t` and the calls that initialize that resource.
-func resourceCentric(t *ResourceType, s *state, r *randGen) (resource *ResultArg, calls []*Call) {
+func (r *randGen) resourceCentric(s *state, t *ResourceType) (arg Arg, calls []*Call) {
var p *Prog
+ var resource *ResultArg
for idx := range r.Perm(len(s.corpus)) {
p = s.corpus[idx].Clone()
resources := getCompatibleResources(p, t.TypeName, r)
@@ -893,7 +906,7 @@ func resourceCentric(t *ResourceType, s *state, r *randGen) (resource *ResultArg
p.removeCall(i)
}
- return resource, p.Calls
+ return MakeResultArg(t, resource, 0), p.Calls
}
func getCompatibleResources(p *Prog, resourceType string, r *randGen) (resources []*ResultArg) {
diff --git a/prog/rand_test.go b/prog/rand_test.go
index d308bf890..cfea62e27 100644
--- a/prog/rand_test.go
+++ b/prog/rand_test.go
@@ -31,12 +31,13 @@ func TestNotEscaping(t *testing.T) {
func TestDeterminism(t *testing.T) {
target, rs, iters := initTest(t)
iters /= 10 // takes too long
+ var corpus []*Prog
for i := 0; i < iters; i++ {
seed := rs.Int63()
rs1 := rand.NewSource(seed)
- p1 := generateProg(t, target, rs1)
+ p1 := generateProg(t, target, rs1, corpus)
rs2 := rand.NewSource(seed)
- p2 := generateProg(t, target, rs2)
+ p2 := generateProg(t, target, rs2, corpus)
ps1 := string(p1.Serialize())
ps2 := string(p2.Serialize())
r1 := rs1.Int63()
@@ -44,12 +45,13 @@ func TestDeterminism(t *testing.T) {
if r1 != r2 || ps1 != ps2 {
t.Errorf("seed=%v\nprog 1 (%v):\n%v\nprog 2 (%v):\n%v", seed, r1, ps1, r2, ps2)
}
+ corpus = append(corpus, p1)
}
}
-func generateProg(t *testing.T, target *Target, rs rand.Source) *Prog {
+func generateProg(t *testing.T, target *Target, rs rand.Source, corpus []*Prog) *Prog {
p := target.Generate(rs, 5, nil)
- p.Mutate(rs, 10, nil, nil)
+ p.Mutate(rs, 10, nil, corpus)
for i, c := range p.Calls {
comps := make(CompMap)
for v := range extractValues(c) {
diff --git a/syz-fuzzer/fuzzer.go b/syz-fuzzer/fuzzer.go
index 38659aa71..b6e8be4b4 100644
--- a/syz-fuzzer/fuzzer.go
+++ b/syz-fuzzer/fuzzer.go
@@ -368,21 +368,7 @@ func (fuzzer *Fuzzer) poll(needCandidates bool, stats map[string]uint64) bool {
fuzzer.addInputFromAnotherFuzzer(inp)
}
for _, candidate := range r.Candidates {
- p, err := fuzzer.target.Deserialize(candidate.Prog, prog.NonStrict)
- if err != nil {
- log.Fatalf("failed to parse program from manager: %v", err)
- }
- flags := ProgCandidate
- if candidate.Minimized {
- flags |= ProgMinimized
- }
- if candidate.Smashed {
- flags |= ProgSmashed
- }
- fuzzer.workQueue.enqueue(&WorkCandidate{
- p: p,
- flags: flags,
- })
+ fuzzer.addCandidateInput(candidate)
}
if needCandidates && len(r.Candidates) == 0 && atomic.LoadUint32(&fuzzer.triagedCandidates) == 0 {
atomic.StoreUint32(&fuzzer.triagedCandidates, 1)
@@ -401,15 +387,44 @@ func (fuzzer *Fuzzer) sendInputToManager(inp rpctype.RPCInput) {
}
func (fuzzer *Fuzzer) addInputFromAnotherFuzzer(inp rpctype.RPCInput) {
- p, err := fuzzer.target.Deserialize(inp.Prog, prog.NonStrict)
- if err != nil {
- log.Fatalf("failed to deserialize prog from another fuzzer: %v", err)
+ p := fuzzer.deserializeInput(inp.Prog)
+ if p == nil {
+ return
}
sig := hash.Hash(inp.Prog)
sign := inp.Signal.Deserialize()
fuzzer.addInputToCorpus(p, sign, sig)
}
+func (fuzzer *Fuzzer) addCandidateInput(candidate rpctype.RPCCandidate) {
+ p := fuzzer.deserializeInput(candidate.Prog)
+ if p == nil {
+ return
+ }
+ flags := ProgCandidate
+ if candidate.Minimized {
+ flags |= ProgMinimized
+ }
+ if candidate.Smashed {
+ flags |= ProgSmashed
+ }
+ fuzzer.workQueue.enqueue(&WorkCandidate{
+ p: p,
+ flags: flags,
+ })
+}
+
+func (fuzzer *Fuzzer) deserializeInput(inp []byte) *prog.Prog {
+ p, err := fuzzer.target.Deserialize(inp, prog.NonStrict)
+ if err != nil {
+ log.Fatalf("failed to deserialize prog: %v\n%s", err, inp)
+ }
+ if len(p.Calls) > prog.MaxCalls {
+ return nil
+ }
+ return p
+}
+
func (fuzzer *FuzzerSnapshot) chooseProgram(r *rand.Rand) *prog.Prog {
randVal := r.Int63n(fuzzer.sumPrios + 1)
idx := sort.Search(len(fuzzer.corpusPrios), func(i int) bool {
diff --git a/syz-fuzzer/proc.go b/syz-fuzzer/proc.go
index cbac400ac..d815a58b9 100644
--- a/syz-fuzzer/proc.go
+++ b/syz-fuzzer/proc.go
@@ -22,10 +22,6 @@ import (
"github.com/google/syzkaller/prog"
)
-const (
- programLength = 30
-)
-
// Proc represents a single fuzzing process (executor).
type Proc struct {
fuzzer *Fuzzer
@@ -90,13 +86,13 @@ func (proc *Proc) loop() {
fuzzerSnapshot := proc.fuzzer.snapshot()
if len(fuzzerSnapshot.corpus) == 0 || i%generatePeriod == 0 {
// Generate a new prog.
- p := proc.fuzzer.target.Generate(proc.rnd, programLength, ct)
+ p := proc.fuzzer.target.Generate(proc.rnd, prog.RecommendedCalls, ct)
log.Logf(1, "#%v: generated", proc.pid)
proc.execute(proc.execOpts, p, ProgNormal, StatGenerate)
} else {
// Mutate an existing prog.
p := fuzzerSnapshot.chooseProgram(proc.rnd).Clone()
- p.Mutate(proc.rnd, programLength, ct, fuzzerSnapshot.corpus)
+ p.Mutate(proc.rnd, prog.RecommendedCalls, ct, fuzzerSnapshot.corpus)
log.Logf(1, "#%v: mutated", proc.pid)
proc.execute(proc.execOpts, p, ProgNormal, StatFuzz)
}
@@ -214,7 +210,7 @@ func (proc *Proc) smashInput(item *WorkSmash) {
fuzzerSnapshot := proc.fuzzer.snapshot()
for i := 0; i < 100; i++ {
p := item.p.Clone()
- p.Mutate(proc.rnd, programLength, proc.fuzzer.choiceTable, fuzzerSnapshot.corpus)
+ p.Mutate(proc.rnd, prog.RecommendedCalls, proc.fuzzer.choiceTable, fuzzerSnapshot.corpus)
log.Logf(1, "#%v: smash mutated", proc.pid)
proc.execute(proc.execOpts, p, ProgNormal, StatSmash)
}
diff --git a/syz-hub/state/state.go b/syz-hub/state/state.go
index a8a1b36ea..dd722d80c 100644
--- a/syz-hub/state/state.go
+++ b/syz-hub/state/state.go
@@ -58,8 +58,15 @@ func Make(dir string) (*State, error) {
}
osutil.MkdirAll(st.dir)
- st.Corpus, st.corpusSeq = loadDB(filepath.Join(st.dir, "corpus.db"), "corpus")
- st.Repros, st.reproSeq = loadDB(filepath.Join(st.dir, "repro.db"), "repro")
+ var err error
+ st.Corpus, st.corpusSeq, err = loadDB(filepath.Join(st.dir, "corpus.db"), "corpus")
+ if err != nil {
+ log.Fatal(err)
+ }
+ st.Repros, st.reproSeq, err = loadDB(filepath.Join(st.dir, "repro.db"), "repro")
+ if err != nil {
+ log.Fatal(err)
+ }
managersDir := filepath.Join(st.dir, "manager")
osutil.MkdirAll(managersDir)
@@ -80,20 +87,26 @@ func Make(dir string) (*State, error) {
return st, err
}
-func loadDB(file, name string) (*db.DB, uint64) {
+func loadDB(file, name string) (*db.DB, uint64, error) {
log.Logf(0, "reading %v...", name)
db, err := db.Open(file)
if err != nil {
- log.Fatalf("failed to open %v database: %v", name, err)
+ return nil, 0, fmt.Errorf("failed to open %v database: %v", name, err)
}
log.Logf(0, "read %v programs", len(db.Records))
var maxSeq uint64
for key, rec := range db.Records {
- if _, err := prog.CallSet(rec.Val); err != nil {
+ _, ncalls, err := prog.CallSet(rec.Val)
+ if err != nil {
log.Logf(0, "bad file: can't parse call set: %v", err)
db.Delete(key)
continue
}
+ if ncalls > prog.MaxCalls {
+ log.Logf(0, "bad file: too many calls: %v", ncalls)
+ db.Delete(key)
+ continue
+ }
if sig := hash.Hash(rec.Val); sig.String() != key {
log.Logf(0, "bad file: hash %v, want hash %v", key, sig.String())
db.Delete(key)
@@ -104,9 +117,9 @@ func loadDB(file, name string) (*db.DB, uint64) {
}
}
if err := db.Flush(); err != nil {
- log.Fatalf("failed to flush corpus database: %v", err)
+ return nil, 0, fmt.Errorf("failed to flush corpus database: %v", err)
}
- return db, maxSeq
+ return db, maxSeq, nil
}
func (st *State) createManager(name string) (*Manager, error) {
@@ -130,11 +143,11 @@ func (st *State) createManager(name string) (*Manager, error) {
if st.reproSeq < mgr.reproSeq {
st.reproSeq = mgr.reproSeq
}
- var err error
- mgr.Corpus, err = db.Open(mgr.corpusFile)
+ corpus, _, err := loadDB(mgr.corpusFile, name)
if err != nil {
return nil, fmt.Errorf("failed to open manager corpus %v: %v", mgr.corpusFile, err)
}
+ mgr.Corpus = corpus
log.Logf(0, "created manager %v: corpus=%v, corpusSeq=%v, reproSeq=%v",
mgr.name, len(mgr.Corpus.Records), mgr.corpusSeq, mgr.reproSeq)
st.Managers[name] = mgr
@@ -202,7 +215,7 @@ func (st *State) AddRepro(name string, repro []byte) error {
if mgr == nil || mgr.Connected.IsZero() {
return fmt.Errorf("unconnected manager %v", name)
}
- if _, err := prog.CallSet(repro); err != nil {
+ if _, _, err := prog.CallSet(repro); err != nil {
log.Logf(0, "manager %v: failed to extract call set: %v, program:\n%v",
mgr.name, err, string(repro))
return nil
@@ -242,7 +255,7 @@ func (st *State) PendingRepro(name string) ([]byte, error) {
if mgr.ownRepros[key] {
continue
}
- calls, err := prog.CallSet(rec.Val)
+ calls, _, err := prog.CallSet(rec.Val)
if err != nil {
return nil, fmt.Errorf("failed to extract call set: %v\nprogram: %s", err, rec.Val)
}
@@ -277,7 +290,7 @@ func (st *State) pendingInputs(mgr *Manager) ([][]byte, int, error) {
if _, ok := mgr.Corpus.Records[key]; ok {
continue
}
- calls, err := prog.CallSet(rec.Val)
+ calls, _, err := prog.CallSet(rec.Val)
if err != nil {
return nil, 0, fmt.Errorf("failed to extract call set: %v\nprogram: %s", err, rec.Val)
}
@@ -338,10 +351,15 @@ func (st *State) addInputs(mgr *Manager, inputs [][]byte) {
}
func (st *State) addInput(mgr *Manager, input []byte) {
- if _, err := prog.CallSet(input); err != nil {
+ _, ncalls, err := prog.CallSet(input)
+ if err != nil {
log.Logf(0, "manager %v: failed to extract call set: %v, program:\n%v", mgr.name, err, string(input))
return
}
+ if want := prog.MaxCalls; ncalls > want {
+ log.Logf(0, "manager %v: too long program, ignoring (%v/%v)", mgr.name, ncalls, want)
+ return
+ }
sig := hash.String(input)
mgr.Corpus.Save(sig, nil, 0)
if _, ok := st.Corpus.Records[sig]; !ok {
diff --git a/syz-manager/hub.go b/syz-manager/hub.go
index ff0f26066..51937b536 100644
--- a/syz-manager/hub.go
+++ b/syz-manager/hub.go
@@ -171,7 +171,8 @@ func (hc *HubConnector) processProgs(progs [][]byte) int {
dropped := 0
candidates := make([][]byte, 0, len(progs))
for _, inp := range progs {
- if _, err := hc.target.Deserialize(inp, prog.NonStrict); err != nil {
+ p, err := hc.target.Deserialize(inp, prog.NonStrict)
+ if err != nil || len(p.Calls) > prog.MaxCalls {
dropped++
continue
}
diff --git a/syz-manager/manager.go b/syz-manager/manager.go
index e2a1be2ba..2a0b7dd48 100644
--- a/syz-manager/manager.go
+++ b/syz-manager/manager.go
@@ -102,7 +102,7 @@ const (
phaseTriagedHub
)
-const currentDBVersion = 3
+const currentDBVersion = 4
type Crash struct {
vmIndex int
@@ -463,23 +463,30 @@ func (mgr *Manager) loadCorpus() {
// Version 2->3: big-endian hints.
smashed = false
fallthrough
+ case 3:
+ // Version 3->4: to shake things up.
+ minimized = false
+ fallthrough
case currentDBVersion:
}
syscalls := make(map[int]bool)
for _, id := range mgr.checkResult.EnabledCalls[mgr.cfg.Sandbox] {
syscalls[id] = true
}
- deleted := 0
+ broken, tooLong := 0, 0
for key, rec := range mgr.corpusDB.Records {
p, err := mgr.target.Deserialize(rec.Val, prog.NonStrict)
if err != nil {
- if deleted < 10 {
- log.Logf(0, "deleting broken program: %v\n%s", err, rec.Val)
- }
mgr.corpusDB.Delete(key)
- deleted++
+ broken++
continue
}
+ if len(p.Calls) > prog.MaxCalls {
+ mgr.corpusDB.Delete(key)
+ tooLong++
+ continue
+ }
+
disabled := false
for _, c := range p.Calls {
if !syscalls[c.Meta.ID] {
@@ -501,7 +508,8 @@ func (mgr *Manager) loadCorpus() {
})
}
mgr.fresh = len(mgr.corpusDB.Records) == 0
- log.Logf(0, "%-24v: %v (%v deleted)", "corpus", len(mgr.candidates), deleted)
+ log.Logf(0, "%-24v: %v (deleted %v broken, %v too long)",
+ "corpus", len(mgr.candidates), broken, tooLong)
// Now this is ugly.
// We duplicate all inputs in the corpus and shuffle the second part.
diff --git a/syz-manager/rpc.go b/syz-manager/rpc.go
index c60b7a9a5..91e31dbd9 100644
--- a/syz-manager/rpc.go
+++ b/syz-manager/rpc.go
@@ -165,7 +165,7 @@ func (serv *RPCServer) selectInputs(enabled map[string]bool, inputs0 []rpctype.R
inputs []rpctype.RPCInput, signal signal.Signal) {
signal = signal0.Copy()
for _, inp := range inputs0 {
- calls, err := prog.CallSet(inp.Prog)
+ calls, _, err := prog.CallSet(inp.Prog)
if err != nil {
panic(fmt.Sprintf("rotateInputs: CallSet failed: %v\n%s", err, inp.Prog))
}
@@ -210,11 +210,16 @@ func (serv *RPCServer) NewInput(a *rpctype.NewInputArgs, r *int) error {
inputSignal := a.Signal.Deserialize()
log.Logf(4, "new input from %v for syscall %v (signal=%v, cover=%v)",
a.Name, a.Call, inputSignal.Len(), len(a.Cover))
- if _, err := serv.target.Deserialize(a.RPCInput.Prog, prog.NonStrict); err != nil {
- // This should not happen, but we see such cases episodically, reason unknown.
+ p, err := serv.target.Deserialize(a.RPCInput.Prog, prog.NonStrict)
+ if err != nil {
+ // This should not happen, but we see such cases episodically (probably corrupted VM memory).
log.Logf(0, "failed to deserialize program from fuzzer: %v\n%s", err, a.RPCInput.Prog)
return nil
}
+ if len(p.Calls) > prog.MaxCalls {
+ log.Logf(0, "rejecting too long program from fuzzer: %v calls\n%s", len(p.Calls), a.RPCInput.Prog)
+ return nil
+ }
serv.mu.Lock()
defer serv.mu.Unlock()
diff --git a/tools/syz-mutate/mutate.go b/tools/syz-mutate/mutate.go
index 1ed4704e2..3dcc446c6 100644
--- a/tools/syz-mutate/mutate.go
+++ b/tools/syz-mutate/mutate.go
@@ -24,7 +24,7 @@ var (
flagOS = flag.String("os", runtime.GOOS, "target os")
flagArch = flag.String("arch", runtime.GOARCH, "target arch")
flagSeed = flag.Int("seed", -1, "prng seed")
- flagLen = flag.Int("len", 30, "number of calls in programs")
+ flagLen = flag.Int("len", prog.RecommendedCalls, "number of calls in programs")
flagEnable = flag.String("enable", "", "comma-separated list of enabled syscalls")
flagCorpus = flag.String("corpus", "", "name of the corpus file")
)
diff --git a/tools/syz-stress/stress.go b/tools/syz-stress/stress.go
index 3ca669a1b..18d4fa872 100644
--- a/tools/syz-stress/stress.go
+++ b/tools/syz-stress/stress.go
@@ -41,8 +41,6 @@ var (
gate *ipc.Gate
)
-const programLength = 30
-
func main() {
flag.Usage = func() {
flag.PrintDefaults()
@@ -99,15 +97,15 @@ func main() {
for i := 0; ; i++ {
var p *prog.Prog
if *flagGenerate && len(corpus) == 0 || i%4 != 0 {
- p = target.Generate(rs, programLength, ct)
+ p = target.Generate(rs, prog.RecommendedCalls, ct)
execute(pid, env, execOpts, p)
- p.Mutate(rs, programLength, ct, corpus)
+ p.Mutate(rs, prog.RecommendedCalls, ct, corpus)
execute(pid, env, execOpts, p)
} else {
p = corpus[rnd.Intn(len(corpus))].Clone()
- p.Mutate(rs, programLength, ct, corpus)
+ p.Mutate(rs, prog.RecommendedCalls, ct, corpus)
execute(pid, env, execOpts, p)
- p.Mutate(rs, programLength, ct, corpus)
+ p.Mutate(rs, prog.RecommendedCalls, ct, corpus)
execute(pid, env, execOpts, p)
}
}