ipc, prog, fuzzer, execprog: add hints generation code

A hint is basically a tuple consisting of a pointer to an argument
in one of the syscalls of a program and a value, which should be
assigned to that argument.

A simplified version of hints workflow looks like this:
    1. Fuzzer launches a program and collects all the comparisons' data
for every syscall in the program.
    2. Next it tries to match the obtained comparison operands' values
vs. the input arguments' values.
    3. For every such match the fuzzer mutates the program by
replacing the pointed argument with the saved value.
    4. If a valid program is obtained, then fuzzer launches it and
checks if new coverage is obtained.

This commit includes:
    1. All the code related to hints generation, parsing and mutations.
    2. Fuzzer functions to launch the process.
    3. Some new stats gathered by fuzzer and manager, related to hints.
    4. An updated version of execprog to test the hints process.
This commit is contained in:
Victor Chibotaru 2017-08-24 17:52:57 +02:00 committed by Dmitry Vyukov
parent 07c84b670b
commit 49c11eb514
4 changed files with 116 additions and 14 deletions

View File

@ -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
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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