prog: control program length

We have _some_ limits on program length, but they are really soft.
When we ask to generate a program with 10 calls, sometimes we get
100-150 calls. There are also no checks when we accept external
programs from corpus/hub. Issue #1630 contains an example where
this crashes VM (executor limit on number of 1000 resources is
violated). Larger programs also harm the process overall (slower,
consume more memory, lead to monster reproducers, etc).

Add a set of measure for hard control over program length.
Ensure that generated/mutated programs are not too long;
drop too long programs coming from corpus/hub in manager;
drop too long programs in hub.
As a bonus ensure that mutation don't produce programs with
0 calls (which is currently possible and happens).

Fixes #1630
This commit is contained in:
Dmitry Vyukov 2020-03-07 13:12:35 +01:00
parent 05359321bb
commit 9b1f3e6653
15 changed files with 224 additions and 133 deletions

View File

@ -1130,8 +1130,9 @@ func (p *parser) strictFailf(msg string, args ...interface{}) {
// CallSet returns a set of all calls in the program. // CallSet returns a set of all calls in the program.
// It does very conservative parsing and is intended to parse past/future serialization formats. // 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{}) calls := make(map[string]struct{})
ncalls := 0
s := bufio.NewScanner(bytes.NewReader(data)) s := bufio.NewScanner(bytes.NewReader(data))
s.Buffer(nil, maxLineLen) s.Buffer(nil, maxLineLen)
for s.Scan() { for s.Scan() {
@ -1141,7 +1142,7 @@ func CallSet(data []byte) (map[string]struct{}, error) {
} }
bracket := bytes.IndexByte(ln, '(') bracket := bytes.IndexByte(ln, '(')
if bracket == -1 { 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] call := ln[:bracket]
if eq := bytes.IndexByte(call, '='); eq != -1 { if eq := bytes.IndexByte(call, '='); eq != -1 {
@ -1152,15 +1153,16 @@ func CallSet(data []byte) (map[string]struct{}, error) {
call = call[eq:] call = call[eq:]
} }
if len(call) == 0 { 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{}{} calls[string(call)] = struct{}{}
ncalls++
} }
if err := s.Err(); err != nil { if err := s.Err(); err != nil {
return nil, err return nil, 0, err
} }
if len(calls) == 0 { 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
} }

View File

@ -53,29 +53,34 @@ func TestSerializeData(t *testing.T) {
func TestCallSet(t *testing.T) { func TestCallSet(t *testing.T) {
t.Parallel() t.Parallel()
tests := []struct { tests := []struct {
prog string prog string
ok bool ok bool
calls []string calls []string
ncalls int
}{ }{
{ {
"", "",
false, false,
[]string{}, []string{},
0,
}, },
{ {
"r0 = (foo)", "r0 = (foo)",
false, false,
[]string{}, []string{},
0,
}, },
{ {
"getpid()", "getpid()",
true, true,
[]string{"getpid"}, []string{"getpid"},
1,
}, },
{ {
"r11 = getpid()", "r11 = getpid()",
true, true,
[]string{"getpid"}, []string{"getpid"},
1,
}, },
{ {
"getpid()\n" + "getpid()\n" +
@ -86,11 +91,12 @@ func TestCallSet(t *testing.T) {
"close$foo(&(0x0000) = {})\n", "close$foo(&(0x0000) = {})\n",
true, true,
[]string{"getpid", "open", "close$foo"}, []string{"getpid", "open", "close$foo"},
4,
}, },
} }
for i, test := range tests { for i, test := range tests {
t.Run(fmt.Sprint(i), func(t *testing.T) { 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 { if err != nil && test.ok {
t.Fatalf("parsing failed: %v", err) t.Fatalf("parsing failed: %v", err)
} }
@ -102,6 +108,9 @@ func TestCallSet(t *testing.T) {
if !reflect.DeepEqual(callArray, test.calls) { if !reflect.DeepEqual(callArray, test.calls) {
t.Fatalf("got call set %+v, expect %+v", 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) { func TestCallSetRandom(t *testing.T) {
target, rs, iters := initTest(t) target, rs, iters := initTest(t)
for i := 0; i < iters; i++ { 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{}) calls0 := make(map[string]struct{})
for _, c := range p.Calls { for _, c := range p.Calls {
calls0[c.Meta.Name] = struct{}{} calls0[c.Meta.Name] = struct{}{}
} }
calls1, err := CallSet(p.Serialize()) calls1, ncalls1, err := CallSet(p.Serialize())
if err != nil { if err != nil {
t.Fatalf("CallSet failed: %v", err) t.Fatalf("CallSet failed: %v", err)
} }
@ -123,6 +133,9 @@ func TestCallSetRandom(t *testing.T) {
if !reflect.DeepEqual(callArray0, callArray1) { if !reflect.DeepEqual(callArray0, callArray1) {
t.Fatalf("got call set:\n%+v\nexpect:\n%+v", callArray1, callArray0) t.Fatalf("got call set:\n%+v\nexpect:\n%+v", callArray1, callArray0)
} }
if ncalls1 != ncalls {
t.Fatalf("got %v calls, expect %v", ncalls1, ncalls)
}
} }
} }

View File

@ -7,8 +7,8 @@ import (
"math/rand" "math/rand"
) )
// Generate generates a random program of length ~ncalls. // Generate generates a random program with ncalls calls.
// calls is a set of allowed syscalls, if nil all syscalls are used. // 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 { func (target *Target) Generate(rs rand.Source, ncalls int, ct *ChoiceTable) *Prog {
p := &Prog{ p := &Prog{
Target: target, Target: target,
@ -22,6 +22,13 @@ func (target *Target) Generate(rs rand.Source, ncalls int, ct *ChoiceTable) *Pro
p.Calls = append(p.Calls, c) 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() p.debugValidate()
return p return p
} }

View File

@ -23,6 +23,9 @@ const maxBlobLen = uint64(100 << 10)
// corpus: The entire corpus, including original program p. // corpus: The entire corpus, including original program p.
func (p *Prog) Mutate(rs rand.Source, ncalls int, ct *ChoiceTable, corpus []*Prog) { func (p *Prog) Mutate(rs rand.Source, ncalls int, ct *ChoiceTable, corpus []*Prog) {
r := newRand(p.Target, rs) r := newRand(p.Target, rs)
if ncalls < len(p.Calls) {
ncalls = len(p.Calls)
}
ctx := &mutator{ ctx := &mutator{
p: p, p: p,
r: r, r: r,
@ -30,7 +33,7 @@ func (p *Prog) Mutate(rs rand.Source, ncalls int, ct *ChoiceTable, corpus []*Pro
ct: ct, ct: ct,
corpus: corpus, 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 { switch {
case r.oneOf(5): case r.oneOf(5):
// Not all calls have anything squashable, // 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.Target.SanitizeCall(c)
} }
p.debugValidate() 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 // 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). // (exclusive) concatenated with p0's calls from index i (inclusive).
func (ctx *mutator) splice() bool { func (ctx *mutator) splice() bool {
p, r := ctx.p, ctx.r 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 return false
} }
p0 := ctx.corpus[r.Intn(len(ctx.corpus))] 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) s := analyze(ctx.ct, ctx.corpus, p, c)
calls := r.generateCall(s, p, idx) calls := r.generateCall(s, p, idx)
// TODO: the program might have more than ncalls
p.insertBefore(c, calls) p.insertBefore(c, calls)
for len(p.Calls) > ctx.ncalls {
p.removeCall(idx)
}
return true return true
} }
@ -158,11 +166,11 @@ func (ctx *mutator) mutateArg() bool {
return false return false
} }
c, ok := chooseCall(p, r) idx := chooseCall(p, r)
if !ok { if idx < 0 {
return false return false
} }
s := analyze(ctx.ct, ctx.corpus, p, c) c := p.Calls[idx]
updateSizes := true updateSizes := true
for stop, ok := false, false; !stop; stop = ok && r.oneOf(3) { for stop, ok := false, false; !stop; stop = ok && r.oneOf(3) {
ok = true ok = true
@ -171,24 +179,33 @@ func (ctx *mutator) mutateArg() bool {
if len(ma.args) == 0 { if len(ma.args) == 0 {
return false return false
} }
s := analyze(ctx.ct, ctx.corpus, p, c)
chosenIdx := randomChoice(ma.priorities, r) chosenIdx := randomChoice(ma.priorities, r)
arg, ctx := ma.args[chosenIdx], ma.ctxes[chosenIdx] arg, argCtx := ma.args[chosenIdx], ma.ctxes[chosenIdx]
calls, ok1 := p.Target.mutateArg(r, s, arg, ctx, &updateSizes) calls, ok1 := p.Target.mutateArg(r, s, arg, argCtx, &updateSizes)
if !ok1 { if !ok1 {
ok = false ok = false
continue continue
} }
p.insertBefore(c, calls) 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 { if updateSizes {
p.Target.assignSizesCall(c) p.Target.assignSizesCall(c)
} }
p.Target.SanitizeCall(c)
} }
return true return true
} }
// Select a call based on the complexity of the arguments. // 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 var callPriorities []float64
noArgs := true noArgs := true
@ -207,10 +224,9 @@ func chooseCall(p *Prog, r *randGen) (*Call, bool) {
// Calls without arguments. // Calls without arguments.
if noArgs { if noArgs {
return nil, false return -1
} }
return randomChoice(callPriorities, r)
return p.Calls[randomChoice(callPriorities, r)], true
} }
// Generate a random index from a given 1-D array of priorities. // 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) newArg := r.allocAddr(s, base.Type(), base.Res.Size(), base.Res)
replaceArg(base, newArg) replaceArg(base, newArg)
} }
for _, c := range calls {
target.SanitizeCall(c)
}
return calls, true return calls, true
} }

View File

@ -136,7 +136,7 @@ func TestMutateArgument(t *testing.T) {
ctx := &mutator{ ctx := &mutator{
p: p1, p: p1,
r: newRand(p1.Target, rs), r: newRand(p1.Target, rs),
ncalls: 0, ncalls: 2 * len(p.Calls),
ct: ct, ct: ct,
corpus: nil, corpus: nil,
} }
@ -163,7 +163,7 @@ func TestSizeMutateArg(t *testing.T) {
ctx := &mutator{ ctx := &mutator{
p: p1, p: p1,
r: r, r: r,
ncalls: 10, ncalls: 2 * len(p.Calls),
ct: ct, ct: ct,
corpus: nil, 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) t.Fatalf("failed to deserialize the program: %v", err)
} }
want := goal.Serialize() want := goal.Serialize()
iters := int(1e5) iters := int(1e6)
if !valid { if !valid {
iters /= 10 iters /= 10
} }

View File

@ -16,6 +16,14 @@ import (
_ "github.com/google/syzkaller/pkg/ifuzz/generated" // pull in generated instruction descriptions _ "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 { type randGen struct {
*rand.Rand *rand.Rand
target *Target 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) { func (r *randGen) createResource(s *state, res *ResourceType) (arg Arg, calls []*Call) {
if r.inCreateResource { if r.inCreateResource {
special := res.SpecialValues() return nil, nil
return MakeResultArg(res, nil, special[r.Intn(len(special))]), nil
} }
r.inCreateResource = true r.inCreateResource = true
defer func() { r.inCreateResource = false }() 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) { func (a *ResourceType) generate(r *randGen, s *state) (arg Arg, calls []*Call) {
switch { if r.oneOf(3) {
case r.nOutOf(2, 5): arg = r.existingResource(s, a)
var res *ResultArg if arg != nil {
res, calls = resourceCentric(a, s, r) return
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 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) { 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 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. // 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 p *Prog
var resource *ResultArg
for idx := range r.Perm(len(s.corpus)) { for idx := range r.Perm(len(s.corpus)) {
p = s.corpus[idx].Clone() p = s.corpus[idx].Clone()
resources := getCompatibleResources(p, t.TypeName, r) resources := getCompatibleResources(p, t.TypeName, r)
@ -893,7 +906,7 @@ func resourceCentric(t *ResourceType, s *state, r *randGen) (resource *ResultArg
p.removeCall(i) p.removeCall(i)
} }
return resource, p.Calls return MakeResultArg(t, resource, 0), p.Calls
} }
func getCompatibleResources(p *Prog, resourceType string, r *randGen) (resources []*ResultArg) { func getCompatibleResources(p *Prog, resourceType string, r *randGen) (resources []*ResultArg) {

View File

@ -31,12 +31,13 @@ func TestNotEscaping(t *testing.T) {
func TestDeterminism(t *testing.T) { func TestDeterminism(t *testing.T) {
target, rs, iters := initTest(t) target, rs, iters := initTest(t)
iters /= 10 // takes too long iters /= 10 // takes too long
var corpus []*Prog
for i := 0; i < iters; i++ { for i := 0; i < iters; i++ {
seed := rs.Int63() seed := rs.Int63()
rs1 := rand.NewSource(seed) rs1 := rand.NewSource(seed)
p1 := generateProg(t, target, rs1) p1 := generateProg(t, target, rs1, corpus)
rs2 := rand.NewSource(seed) rs2 := rand.NewSource(seed)
p2 := generateProg(t, target, rs2) p2 := generateProg(t, target, rs2, corpus)
ps1 := string(p1.Serialize()) ps1 := string(p1.Serialize())
ps2 := string(p2.Serialize()) ps2 := string(p2.Serialize())
r1 := rs1.Int63() r1 := rs1.Int63()
@ -44,12 +45,13 @@ func TestDeterminism(t *testing.T) {
if r1 != r2 || ps1 != ps2 { if r1 != r2 || ps1 != ps2 {
t.Errorf("seed=%v\nprog 1 (%v):\n%v\nprog 2 (%v):\n%v", seed, r1, ps1, r2, 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 := target.Generate(rs, 5, nil)
p.Mutate(rs, 10, nil, nil) p.Mutate(rs, 10, nil, corpus)
for i, c := range p.Calls { for i, c := range p.Calls {
comps := make(CompMap) comps := make(CompMap)
for v := range extractValues(c) { for v := range extractValues(c) {

View File

@ -368,21 +368,7 @@ func (fuzzer *Fuzzer) poll(needCandidates bool, stats map[string]uint64) bool {
fuzzer.addInputFromAnotherFuzzer(inp) fuzzer.addInputFromAnotherFuzzer(inp)
} }
for _, candidate := range r.Candidates { for _, candidate := range r.Candidates {
p, err := fuzzer.target.Deserialize(candidate.Prog, prog.NonStrict) fuzzer.addCandidateInput(candidate)
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,
})
} }
if needCandidates && len(r.Candidates) == 0 && atomic.LoadUint32(&fuzzer.triagedCandidates) == 0 { if needCandidates && len(r.Candidates) == 0 && atomic.LoadUint32(&fuzzer.triagedCandidates) == 0 {
atomic.StoreUint32(&fuzzer.triagedCandidates, 1) atomic.StoreUint32(&fuzzer.triagedCandidates, 1)
@ -401,15 +387,44 @@ func (fuzzer *Fuzzer) sendInputToManager(inp rpctype.RPCInput) {
} }
func (fuzzer *Fuzzer) addInputFromAnotherFuzzer(inp rpctype.RPCInput) { func (fuzzer *Fuzzer) addInputFromAnotherFuzzer(inp rpctype.RPCInput) {
p, err := fuzzer.target.Deserialize(inp.Prog, prog.NonStrict) p := fuzzer.deserializeInput(inp.Prog)
if err != nil { if p == nil {
log.Fatalf("failed to deserialize prog from another fuzzer: %v", err) return
} }
sig := hash.Hash(inp.Prog) sig := hash.Hash(inp.Prog)
sign := inp.Signal.Deserialize() sign := inp.Signal.Deserialize()
fuzzer.addInputToCorpus(p, sign, sig) 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 { func (fuzzer *FuzzerSnapshot) chooseProgram(r *rand.Rand) *prog.Prog {
randVal := r.Int63n(fuzzer.sumPrios + 1) randVal := r.Int63n(fuzzer.sumPrios + 1)
idx := sort.Search(len(fuzzer.corpusPrios), func(i int) bool { idx := sort.Search(len(fuzzer.corpusPrios), func(i int) bool {

View File

@ -22,10 +22,6 @@ import (
"github.com/google/syzkaller/prog" "github.com/google/syzkaller/prog"
) )
const (
programLength = 30
)
// Proc represents a single fuzzing process (executor). // Proc represents a single fuzzing process (executor).
type Proc struct { type Proc struct {
fuzzer *Fuzzer fuzzer *Fuzzer
@ -90,13 +86,13 @@ func (proc *Proc) loop() {
fuzzerSnapshot := proc.fuzzer.snapshot() fuzzerSnapshot := proc.fuzzer.snapshot()
if len(fuzzerSnapshot.corpus) == 0 || i%generatePeriod == 0 { if len(fuzzerSnapshot.corpus) == 0 || i%generatePeriod == 0 {
// Generate a new prog. // 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) log.Logf(1, "#%v: generated", proc.pid)
proc.execute(proc.execOpts, p, ProgNormal, StatGenerate) proc.execute(proc.execOpts, p, ProgNormal, StatGenerate)
} else { } else {
// Mutate an existing prog. // Mutate an existing prog.
p := fuzzerSnapshot.chooseProgram(proc.rnd).Clone() 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) log.Logf(1, "#%v: mutated", proc.pid)
proc.execute(proc.execOpts, p, ProgNormal, StatFuzz) proc.execute(proc.execOpts, p, ProgNormal, StatFuzz)
} }
@ -214,7 +210,7 @@ func (proc *Proc) smashInput(item *WorkSmash) {
fuzzerSnapshot := proc.fuzzer.snapshot() fuzzerSnapshot := proc.fuzzer.snapshot()
for i := 0; i < 100; i++ { for i := 0; i < 100; i++ {
p := item.p.Clone() 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) log.Logf(1, "#%v: smash mutated", proc.pid)
proc.execute(proc.execOpts, p, ProgNormal, StatSmash) proc.execute(proc.execOpts, p, ProgNormal, StatSmash)
} }

View File

@ -58,8 +58,15 @@ func Make(dir string) (*State, error) {
} }
osutil.MkdirAll(st.dir) osutil.MkdirAll(st.dir)
st.Corpus, st.corpusSeq = loadDB(filepath.Join(st.dir, "corpus.db"), "corpus") var err error
st.Repros, st.reproSeq = loadDB(filepath.Join(st.dir, "repro.db"), "repro") 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") managersDir := filepath.Join(st.dir, "manager")
osutil.MkdirAll(managersDir) osutil.MkdirAll(managersDir)
@ -80,20 +87,26 @@ func Make(dir string) (*State, error) {
return st, err 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) log.Logf(0, "reading %v...", name)
db, err := db.Open(file) db, err := db.Open(file)
if err != nil { 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)) log.Logf(0, "read %v programs", len(db.Records))
var maxSeq uint64 var maxSeq uint64
for key, rec := range db.Records { 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) log.Logf(0, "bad file: can't parse call set: %v", err)
db.Delete(key) db.Delete(key)
continue 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 { if sig := hash.Hash(rec.Val); sig.String() != key {
log.Logf(0, "bad file: hash %v, want hash %v", key, sig.String()) log.Logf(0, "bad file: hash %v, want hash %v", key, sig.String())
db.Delete(key) db.Delete(key)
@ -104,9 +117,9 @@ func loadDB(file, name string) (*db.DB, uint64) {
} }
} }
if err := db.Flush(); err != nil { 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) { 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 { if st.reproSeq < mgr.reproSeq {
st.reproSeq = mgr.reproSeq st.reproSeq = mgr.reproSeq
} }
var err error corpus, _, err := loadDB(mgr.corpusFile, name)
mgr.Corpus, err = db.Open(mgr.corpusFile)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to open manager corpus %v: %v", mgr.corpusFile, err) 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", log.Logf(0, "created manager %v: corpus=%v, corpusSeq=%v, reproSeq=%v",
mgr.name, len(mgr.Corpus.Records), mgr.corpusSeq, mgr.reproSeq) mgr.name, len(mgr.Corpus.Records), mgr.corpusSeq, mgr.reproSeq)
st.Managers[name] = mgr st.Managers[name] = mgr
@ -202,7 +215,7 @@ func (st *State) AddRepro(name string, repro []byte) error {
if mgr == nil || mgr.Connected.IsZero() { if mgr == nil || mgr.Connected.IsZero() {
return fmt.Errorf("unconnected manager %v", name) 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", log.Logf(0, "manager %v: failed to extract call set: %v, program:\n%v",
mgr.name, err, string(repro)) mgr.name, err, string(repro))
return nil return nil
@ -242,7 +255,7 @@ func (st *State) PendingRepro(name string) ([]byte, error) {
if mgr.ownRepros[key] { if mgr.ownRepros[key] {
continue continue
} }
calls, err := prog.CallSet(rec.Val) calls, _, err := prog.CallSet(rec.Val)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to extract call set: %v\nprogram: %s", err, rec.Val) 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 { if _, ok := mgr.Corpus.Records[key]; ok {
continue continue
} }
calls, err := prog.CallSet(rec.Val) calls, _, err := prog.CallSet(rec.Val)
if err != nil { if err != nil {
return nil, 0, fmt.Errorf("failed to extract call set: %v\nprogram: %s", err, rec.Val) 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) { 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)) log.Logf(0, "manager %v: failed to extract call set: %v, program:\n%v", mgr.name, err, string(input))
return 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) sig := hash.String(input)
mgr.Corpus.Save(sig, nil, 0) mgr.Corpus.Save(sig, nil, 0)
if _, ok := st.Corpus.Records[sig]; !ok { if _, ok := st.Corpus.Records[sig]; !ok {

View File

@ -171,7 +171,8 @@ func (hc *HubConnector) processProgs(progs [][]byte) int {
dropped := 0 dropped := 0
candidates := make([][]byte, 0, len(progs)) candidates := make([][]byte, 0, len(progs))
for _, inp := range 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++ dropped++
continue continue
} }

View File

@ -102,7 +102,7 @@ const (
phaseTriagedHub phaseTriagedHub
) )
const currentDBVersion = 3 const currentDBVersion = 4
type Crash struct { type Crash struct {
vmIndex int vmIndex int
@ -463,23 +463,30 @@ func (mgr *Manager) loadCorpus() {
// Version 2->3: big-endian hints. // Version 2->3: big-endian hints.
smashed = false smashed = false
fallthrough fallthrough
case 3:
// Version 3->4: to shake things up.
minimized = false
fallthrough
case currentDBVersion: case currentDBVersion:
} }
syscalls := make(map[int]bool) syscalls := make(map[int]bool)
for _, id := range mgr.checkResult.EnabledCalls[mgr.cfg.Sandbox] { for _, id := range mgr.checkResult.EnabledCalls[mgr.cfg.Sandbox] {
syscalls[id] = true syscalls[id] = true
} }
deleted := 0 broken, tooLong := 0, 0
for key, rec := range mgr.corpusDB.Records { for key, rec := range mgr.corpusDB.Records {
p, err := mgr.target.Deserialize(rec.Val, prog.NonStrict) p, err := mgr.target.Deserialize(rec.Val, prog.NonStrict)
if err != nil { if err != nil {
if deleted < 10 {
log.Logf(0, "deleting broken program: %v\n%s", err, rec.Val)
}
mgr.corpusDB.Delete(key) mgr.corpusDB.Delete(key)
deleted++ broken++
continue continue
} }
if len(p.Calls) > prog.MaxCalls {
mgr.corpusDB.Delete(key)
tooLong++
continue
}
disabled := false disabled := false
for _, c := range p.Calls { for _, c := range p.Calls {
if !syscalls[c.Meta.ID] { if !syscalls[c.Meta.ID] {
@ -501,7 +508,8 @@ func (mgr *Manager) loadCorpus() {
}) })
} }
mgr.fresh = len(mgr.corpusDB.Records) == 0 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. // Now this is ugly.
// We duplicate all inputs in the corpus and shuffle the second part. // We duplicate all inputs in the corpus and shuffle the second part.

View File

@ -165,7 +165,7 @@ func (serv *RPCServer) selectInputs(enabled map[string]bool, inputs0 []rpctype.R
inputs []rpctype.RPCInput, signal signal.Signal) { inputs []rpctype.RPCInput, signal signal.Signal) {
signal = signal0.Copy() signal = signal0.Copy()
for _, inp := range inputs0 { for _, inp := range inputs0 {
calls, err := prog.CallSet(inp.Prog) calls, _, err := prog.CallSet(inp.Prog)
if err != nil { if err != nil {
panic(fmt.Sprintf("rotateInputs: CallSet failed: %v\n%s", err, inp.Prog)) 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() inputSignal := a.Signal.Deserialize()
log.Logf(4, "new input from %v for syscall %v (signal=%v, cover=%v)", log.Logf(4, "new input from %v for syscall %v (signal=%v, cover=%v)",
a.Name, a.Call, inputSignal.Len(), len(a.Cover)) a.Name, a.Call, inputSignal.Len(), len(a.Cover))
if _, err := serv.target.Deserialize(a.RPCInput.Prog, prog.NonStrict); err != nil { p, err := serv.target.Deserialize(a.RPCInput.Prog, prog.NonStrict)
// This should not happen, but we see such cases episodically, reason unknown. 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) log.Logf(0, "failed to deserialize program from fuzzer: %v\n%s", err, a.RPCInput.Prog)
return nil 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() serv.mu.Lock()
defer serv.mu.Unlock() defer serv.mu.Unlock()

View File

@ -24,7 +24,7 @@ var (
flagOS = flag.String("os", runtime.GOOS, "target os") flagOS = flag.String("os", runtime.GOOS, "target os")
flagArch = flag.String("arch", runtime.GOARCH, "target arch") flagArch = flag.String("arch", runtime.GOARCH, "target arch")
flagSeed = flag.Int("seed", -1, "prng seed") 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") flagEnable = flag.String("enable", "", "comma-separated list of enabled syscalls")
flagCorpus = flag.String("corpus", "", "name of the corpus file") flagCorpus = flag.String("corpus", "", "name of the corpus file")
) )

View File

@ -41,8 +41,6 @@ var (
gate *ipc.Gate gate *ipc.Gate
) )
const programLength = 30
func main() { func main() {
flag.Usage = func() { flag.Usage = func() {
flag.PrintDefaults() flag.PrintDefaults()
@ -99,15 +97,15 @@ func main() {
for i := 0; ; i++ { for i := 0; ; i++ {
var p *prog.Prog var p *prog.Prog
if *flagGenerate && len(corpus) == 0 || i%4 != 0 { 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) execute(pid, env, execOpts, p)
p.Mutate(rs, programLength, ct, corpus) p.Mutate(rs, prog.RecommendedCalls, ct, corpus)
execute(pid, env, execOpts, p) execute(pid, env, execOpts, p)
} else { } else {
p = corpus[rnd.Intn(len(corpus))].Clone() 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) execute(pid, env, execOpts, p)
p.Mutate(rs, programLength, ct, corpus) p.Mutate(rs, prog.RecommendedCalls, ct, corpus)
execute(pid, env, execOpts, p) execute(pid, env, execOpts, p)
} }
} }