mirror of
https://github.com/reactos/syzkaller.git
synced 2024-11-23 03:19:51 +00:00
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:
parent
05359321bb
commit
9b1f3e6653
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
93
prog/rand.go
93
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) {
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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")
|
||||
)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user