diff --git a/prog/encoding.go b/prog/encoding.go index b36bf963..6bded49d 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 e5aa8214..c62e6647 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 85d1bbb0..1ceda482 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 b50f4880..62acba58 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 89fe5473..0d12699f 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 bb3c8178..bf6d66e9 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) + if r.oneOf(3) { + arg = r.existingResource(s, a) + if arg != nil { + return } - 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 len(allres) != 0 { - arg = MakeResultArg(a, allres[r.Intn(len(allres))], 0) - } else { - arg, calls = r.createResource(s, a) - } - case r.nOutOf(2, 3): - // Create a new resource. - arg, calls = r.createResource(s, a) - default: - special := a.SpecialValues() - arg = MakeResultArg(a, nil, special[r.Intn(len(special))]) } - return arg, calls + if r.nOutOf(2, 3) { + arg, calls = r.resourceCentric(s, a) + if arg != nil { + return + } + } + if r.nOutOf(4, 5) { + arg, calls = r.createResource(s, a) + if arg != nil { + return + } + } + 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 d308bf89..cfea62e2 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 38659aa7..b6e8be4b 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 cbac400a..d815a58b 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 a8a1b36e..dd722d80 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 ff0f2606..51937b53 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 e2a1be2b..2a0b7dd4 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 c60b7a9a..91e31dbd 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 1ed4704e..3dcc446c 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 3ca669a1..18d4fa87 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) } }