diff --git a/prog/clone.go b/prog/clone.go index fcd6518..c053094 100644 --- a/prog/clone.go +++ b/prog/clone.go @@ -4,14 +4,19 @@ package prog func (p *Prog) Clone() *Prog { + p1, _ := p.cloneImpl(false) + return p1 +} + +func (p *Prog) cloneImpl(full bool) (*Prog, map[Arg]Arg) { p1 := new(Prog) newargs := make(map[Arg]Arg) for _, c := range p.Calls { c1 := new(Call) c1.Meta = c.Meta - c1.Ret = clone(c.Ret, newargs) + c1.Ret = clone(c.Ret, newargs, full) for _, arg := range c.Args { - c1.Args = append(c1.Args, clone(arg, newargs)) + c1.Args = append(c1.Args, clone(arg, newargs, full)) } p1.Calls = append(p1.Calls, c1) } @@ -20,10 +25,10 @@ func (p *Prog) Clone() *Prog { panic(err) } } - return p1 + return p1, newargs } -func clone(arg Arg, newargs map[Arg]Arg) Arg { +func clone(arg Arg, newargs map[Arg]Arg, full bool) Arg { var arg1 Arg switch a := arg.(type) { case *ConstArg: @@ -35,7 +40,7 @@ func clone(arg Arg, newargs map[Arg]Arg) Arg { *a1 = *a arg1 = a1 if a.Res != nil { - a1.Res = clone(a.Res, newargs) + a1.Res = clone(a.Res, newargs, full) } case *DataArg: a1 := new(DataArg) @@ -48,13 +53,13 @@ func clone(arg Arg, newargs map[Arg]Arg) Arg { arg1 = a1 a1.Inner = nil for _, arg2 := range a.Inner { - a1.Inner = append(a1.Inner, clone(arg2, newargs)) + a1.Inner = append(a1.Inner, clone(arg2, newargs, full)) } case *UnionArg: a1 := new(UnionArg) *a1 = *a arg1 = a1 - a1.Option = clone(a.Option, newargs) + a1.Option = clone(a.Option, newargs, full) case *ResultArg: a1 := new(ResultArg) *a1 = *a @@ -78,6 +83,8 @@ func clone(arg Arg, newargs map[Arg]Arg) Arg { if used, ok := arg1.(ArgUsed); ok { *used.Used() = nil // filled when we clone the referent newargs[arg] = arg1 + } else if full { + newargs[arg] = arg1 } return arg1 } diff --git a/prog/hints.go b/prog/hints.go index 50efc6b..ba5681d 100644 --- a/prog/hints.go +++ b/prog/hints.go @@ -28,7 +28,16 @@ type uint64Set map[uint64]bool // }. type CompMap map[uint64]uint64Set -var specialIntsSet uint64Set +var ( + specialIntsSet uint64Set + + // A set of calls for which hints should not be generated. + hintNamesBlackList = map[string]bool{ + "mmap": true, + "open": true, + "close": true, + } +) func (m CompMap) AddComp(arg1, arg2 uint64) { if _, ok := specialIntsSet[arg2]; ok { @@ -42,6 +51,44 @@ func (m CompMap) AddComp(arg1, arg2 uint64) { m[arg1][arg2] = true } +// Mutates the program using the comparison operands stored in compMaps. +// For each of the mutants executes the exec callback. +func (p *Prog) MutateWithHints(compMaps []CompMap, exec func(newP *Prog)) { + for i, c := range p.Calls { + if _, ok := hintNamesBlackList[c.Meta.CallName]; ok { + continue + } + foreachArg(c, func(arg, _ Arg, _ *[]Arg) { + generateHints(p, compMaps[i], c, arg, exec) + }) + } +} + +func generateHints(p *Prog, compMap CompMap, c *Call, arg Arg, exec func(newP *Prog)) { + candidate := func(newArg Arg) { + newP, argMap := p.cloneImpl(true) + oldArg := argMap[arg] + newP.replaceArg(c, oldArg, newArg, nil) + if err := newP.validate(); err != nil { + panic("a program generated with hints did not pass validation: " + + err.Error()) + } + exec(newP) + } + switch a := arg.(type) { + case *ConstArg: + checkConstArg(a, compMap, candidate) + // case *DataArg: + // checkDataArg(a, compMap, candidate) + } +} + +func checkConstArg(arg *ConstArg, compMap CompMap, cb func(newArg Arg)) { + for v, _ := range compMap[arg.Val] { + cb(constArg(arg.typ, v)) + } +} + func init() { specialIntsSet = make(uint64Set) for _, v := range specialInts { diff --git a/syz-fuzzer/fuzzer.go b/syz-fuzzer/fuzzer.go index ca41370..1122a9e 100644 --- a/syz-fuzzer/fuzzer.go +++ b/syz-fuzzer/fuzzer.go @@ -85,6 +85,8 @@ var ( statExecMinimize uint64 statExecSmash uint64 statNewInput uint64 + statExecHints uint64 + statExecHintSeeds uint64 allTriaged uint32 noCover bool @@ -261,7 +263,7 @@ func main() { } } Logf(1, "executing candidate: %s", candidate.p) - execute(pid, env, candidate.p, false, candidate.minimized, true, &statExecCandidate) + execute(pid, env, candidate.p, false, false, candidate.minimized, true, &statExecCandidate) continue } else if len(triage) != 0 { last := len(triage) - 1 @@ -292,14 +294,14 @@ func main() { corpusMu.RUnlock() p := prog.Generate(rnd, programLength, ct) Logf(1, "#%v: generated: %s", i, p) - execute(pid, env, p, false, false, false, &statExecGen) + execute(pid, env, p, false, false, false, false, &statExecGen) } else { // Mutate an existing prog. p := corpus[rnd.Intn(len(corpus))].Clone() corpusMu.RUnlock() p.Mutate(rs, programLength, ct, corpus) Logf(1, "#%v: mutated: %s", i, p) - execute(pid, env, p, false, false, false, &statExecFuzz) + execute(pid, env, p, false, false, false, false, &statExecFuzz) } } }() @@ -477,7 +479,10 @@ func smashInput(pid int, env *ipc.Env, ct *prog.ChoiceTable, rs rand.Source, inp p := inp.p.Clone() p.Mutate(rs, programLength, ct, corpus) Logf(1, "#%v: mutated: %s", pid, p) - execute(pid, env, p, false, false, false, &statExecSmash) + execute(pid, env, p, false, false, false, false, &statExecSmash) + } + if compsSupported { + executeHintSeed(pid, env, inp.p) } } @@ -554,7 +559,7 @@ func triageInput(pid int, env *ipc.Env, inp Input) { } inp.p, inp.call = prog.Minimize(inp.p, inp.call, func(p1 *prog.Prog, call1 int) bool { - info := execute(pid, env, p1, false, false, false, &statExecMinimize) + info := execute(pid, env, p1, false, false, false, false, &statExecMinimize) if len(info) == 0 || len(info[call1].Signal) == 0 { return false // The call was not executed. } @@ -603,8 +608,37 @@ func triageInput(pid int, env *ipc.Env, inp Input) { } } -func execute(pid int, env *ipc.Env, p *prog.Prog, needCover, minimized, candidate bool, stat *uint64) []ipc.CallInfo { +func executeHintSeed(pid int, env *ipc.Env, p *prog.Prog) { + if !compsSupported { + panic("compsSupported==false and executeHintSeed() called") + } + // First execute the original program to dump comparisons from KCOV. + info := execute(pid, env, p, false, true, false, false, &statExecHintSeeds) + + // Then extract the comparisons data. + compMaps := ipc.GetCompMaps(info) + + // Then mutate the initial program for every match between + // a syscall argument and a comparison operand. + // Execute each of such mutants to check if it gives new coverage. + p.MutateWithHints(compMaps, func(p *prog.Prog) { + execute(pid, env, p, false, false, false, false, &statExecHints) + }) +} + +func execute(pid int, env *ipc.Env, p *prog.Prog, needCover, needComps, minimized, candidate bool, stat *uint64) []ipc.CallInfo { opts := &ipc.ExecOpts{} + if needComps { + if !compsSupported { + panic("compsSupported==false and execute() called with needComps") + } + if needCover { + // Currently KCOV is able to dump only the coverage data or only + // the comparisons data. We can't enable both modes at same time. + panic("only one of the needComps and needCover should be true") + } + opts.Flags |= ipc.FlagCollectComps + } if needCover { opts.Flags |= ipc.FlagCollectCover } diff --git a/tools/syz-execprog/execprog.go b/tools/syz-execprog/execprog.go index a2e530f..de2e1f7 100644 --- a/tools/syz-execprog/execprog.go +++ b/tools/syz-execprog/execprog.go @@ -33,6 +33,7 @@ var ( flagOutput = flag.String("output", "none", "write programs to none/stdout") flagFaultCall = flag.Int("fault_call", -1, "inject fault into this call (0-based)") flagFaultNth = flag.Int("fault_nth", 0, "inject fault on n-th operation (0-based)") + flagHints = flag.Bool("hints", false, "do a hints-generation run") ) func main() { @@ -73,6 +74,12 @@ func main() { execOpts.Flags |= ipc.FlagCollectCover execOpts.Flags &^= ipc.FlagDedupCover } + if *flagHints { + if execOpts.Flags&ipc.FlagCollectCover != 0 { + execOpts.Flags ^= ipc.FlagCollectCover + } + execOpts.Flags |= ipc.FlagCollectComps + } if *flagFaultCall >= 0 { config.Flags |= ipc.FlagEnableFault @@ -162,6 +169,13 @@ func main() { } } } + if *flagHints { + compMaps := ipc.GetCompMaps(info) + p.MutateWithHints(compMaps, func(p *prog.Prog) { + fmt.Printf("%v\n", string(p.Serialize())) + }) + } + return true }() { return