// Copyright 2017 syzkaller project authors. All rights reserved.
|
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
|
|
package main
|
|
import (
|
"bytes"
|
"fmt"
|
"math/rand"
|
"os"
|
"runtime/debug"
|
"sync/atomic"
|
"syscall"
|
"time"
|
|
"github.com/google/syzkaller/pkg/cover"
|
"github.com/google/syzkaller/pkg/hash"
|
"github.com/google/syzkaller/pkg/ipc"
|
"github.com/google/syzkaller/pkg/log"
|
"github.com/google/syzkaller/pkg/rpctype"
|
"github.com/google/syzkaller/pkg/signal"
|
"github.com/google/syzkaller/prog"
|
)
|
|
const (
|
programLength = 30
|
)
|
|
// Proc represents a single fuzzing process (executor).
|
type Proc struct {
|
fuzzer *Fuzzer
|
pid int
|
env *ipc.Env
|
rnd *rand.Rand
|
execOpts *ipc.ExecOpts
|
execOptsCover *ipc.ExecOpts
|
execOptsComps *ipc.ExecOpts
|
execOptsNoCollide *ipc.ExecOpts
|
}
|
|
func newProc(fuzzer *Fuzzer, pid int) (*Proc, error) {
|
env, err := ipc.MakeEnv(fuzzer.config, pid)
|
if err != nil {
|
return nil, err
|
}
|
rnd := rand.New(rand.NewSource(time.Now().UnixNano() + int64(pid)*1e12))
|
execOptsNoCollide := *fuzzer.execOpts
|
execOptsNoCollide.Flags &= ^ipc.FlagCollide
|
execOptsCover := execOptsNoCollide
|
execOptsCover.Flags |= ipc.FlagCollectCover
|
execOptsComps := execOptsNoCollide
|
execOptsComps.Flags |= ipc.FlagCollectComps
|
proc := &Proc{
|
fuzzer: fuzzer,
|
pid: pid,
|
env: env,
|
rnd: rnd,
|
execOpts: fuzzer.execOpts,
|
execOptsCover: &execOptsCover,
|
execOptsComps: &execOptsComps,
|
execOptsNoCollide: &execOptsNoCollide,
|
}
|
return proc, nil
|
}
|
|
func (proc *Proc) loop() {
|
generatePeriod := 100
|
if proc.fuzzer.config.Flags&ipc.FlagSignal == 0 {
|
// If we don't have real coverage signal, generate programs more frequently
|
// because fallback signal is weak.
|
generatePeriod = 2
|
}
|
for i := 0; ; i++ {
|
item := proc.fuzzer.workQueue.dequeue()
|
if item != nil {
|
switch item := item.(type) {
|
case *WorkTriage:
|
proc.triageInput(item)
|
case *WorkCandidate:
|
proc.execute(proc.execOpts, item.p, item.flags, StatCandidate)
|
case *WorkSmash:
|
proc.smashInput(item)
|
default:
|
log.Fatalf("unknown work type: %#v", item)
|
}
|
continue
|
}
|
|
ct := proc.fuzzer.choiceTable
|
corpus := proc.fuzzer.corpusSnapshot()
|
if len(corpus) == 0 || i%generatePeriod == 0 {
|
// Generate a new prog.
|
p := proc.fuzzer.target.Generate(proc.rnd, programLength, ct)
|
log.Logf(1, "#%v: generated", proc.pid)
|
proc.execute(proc.execOpts, p, ProgNormal, StatGenerate)
|
} else {
|
// Mutate an existing prog.
|
p := corpus[proc.rnd.Intn(len(corpus))].Clone()
|
p.Mutate(proc.rnd, programLength, ct, corpus)
|
log.Logf(1, "#%v: mutated", proc.pid)
|
proc.execute(proc.execOpts, p, ProgNormal, StatFuzz)
|
}
|
}
|
}
|
|
func (proc *Proc) triageInput(item *WorkTriage) {
|
log.Logf(1, "#%v: triaging type=%x", proc.pid, item.flags)
|
|
call := item.p.Calls[item.call]
|
inputSignal := signal.FromRaw(item.info.Signal, signalPrio(item.p.Target, call, &item.info))
|
newSignal := proc.fuzzer.corpusSignalDiff(inputSignal)
|
if newSignal.Empty() {
|
return
|
}
|
log.Logf(3, "triaging input for %v (new signal=%v)", call.Meta.CallName, newSignal.Len())
|
var inputCover cover.Cover
|
const (
|
signalRuns = 3
|
minimizeAttempts = 3
|
)
|
// Compute input coverage and non-flaky signal for minimization.
|
notexecuted := 0
|
for i := 0; i < signalRuns; i++ {
|
info := proc.executeRaw(proc.execOptsCover, item.p, StatTriage)
|
if len(info) == 0 || len(info[item.call].Signal) == 0 ||
|
item.info.Errno == 0 && info[item.call].Errno != 0 {
|
// The call was not executed or failed.
|
notexecuted++
|
if notexecuted > signalRuns/2+1 {
|
return // if happens too often, give up
|
}
|
continue
|
}
|
inf := info[item.call]
|
thisSignal := signal.FromRaw(inf.Signal, signalPrio(item.p.Target, call, &inf))
|
newSignal = newSignal.Intersection(thisSignal)
|
// Without !minimized check manager starts losing some considerable amount
|
// of coverage after each restart. Mechanics of this are not completely clear.
|
if newSignal.Empty() && item.flags&ProgMinimized == 0 {
|
return
|
}
|
inputCover.Merge(inf.Cover)
|
}
|
if item.flags&ProgMinimized == 0 {
|
item.p, item.call = prog.Minimize(item.p, item.call, false,
|
func(p1 *prog.Prog, call1 int) bool {
|
for i := 0; i < minimizeAttempts; i++ {
|
info := proc.execute(proc.execOptsNoCollide, p1, ProgNormal, StatMinimize)
|
if len(info) == 0 || len(info[call1].Signal) == 0 {
|
continue // The call was not executed.
|
}
|
inf := info[call1]
|
if item.info.Errno == 0 && inf.Errno != 0 {
|
// Don't minimize calls from successful to unsuccessful.
|
// Successful calls are much more valuable.
|
return false
|
}
|
prio := signalPrio(p1.Target, p1.Calls[call1], &inf)
|
thisSignal := signal.FromRaw(inf.Signal, prio)
|
if newSignal.Intersection(thisSignal).Len() == newSignal.Len() {
|
return true
|
}
|
}
|
return false
|
})
|
}
|
|
data := item.p.Serialize()
|
sig := hash.Hash(data)
|
|
log.Logf(2, "added new input for %v to corpus:\n%s", call.Meta.CallName, data)
|
proc.fuzzer.sendInputToManager(rpctype.RPCInput{
|
Call: call.Meta.CallName,
|
Prog: data,
|
Signal: inputSignal.Serialize(),
|
Cover: inputCover.Serialize(),
|
})
|
|
proc.fuzzer.addInputToCorpus(item.p, inputSignal, sig)
|
|
if item.flags&ProgSmashed == 0 {
|
proc.fuzzer.workQueue.enqueue(&WorkSmash{item.p, item.call})
|
}
|
}
|
|
func (proc *Proc) smashInput(item *WorkSmash) {
|
if proc.fuzzer.faultInjectionEnabled {
|
proc.failCall(item.p, item.call)
|
}
|
if proc.fuzzer.comparisonTracingEnabled {
|
proc.executeHintSeed(item.p, item.call)
|
}
|
corpus := proc.fuzzer.corpusSnapshot()
|
for i := 0; i < 100; i++ {
|
p := item.p.Clone()
|
p.Mutate(proc.rnd, programLength, proc.fuzzer.choiceTable, corpus)
|
log.Logf(1, "#%v: smash mutated", proc.pid)
|
proc.execute(proc.execOpts, p, ProgNormal, StatSmash)
|
}
|
}
|
|
func (proc *Proc) failCall(p *prog.Prog, call int) {
|
for nth := 0; nth < 100; nth++ {
|
log.Logf(1, "#%v: injecting fault into call %v/%v", proc.pid, call, nth)
|
opts := *proc.execOpts
|
opts.Flags |= ipc.FlagInjectFault
|
opts.FaultCall = call
|
opts.FaultNth = nth
|
info := proc.executeRaw(&opts, p, StatSmash)
|
if info != nil && len(info) > call && info[call].Flags&ipc.CallFaultInjected == 0 {
|
break
|
}
|
}
|
}
|
|
func (proc *Proc) executeHintSeed(p *prog.Prog, call int) {
|
log.Logf(1, "#%v: collecting comparisons", proc.pid)
|
// First execute the original program to dump comparisons from KCOV.
|
info := proc.execute(proc.execOptsComps, p, ProgNormal, StatSeed)
|
if info == nil {
|
return
|
}
|
|
// 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(call, info[call].Comps, func(p *prog.Prog) {
|
log.Logf(1, "#%v: executing comparison hint", proc.pid)
|
proc.execute(proc.execOpts, p, ProgNormal, StatHint)
|
})
|
}
|
|
func (proc *Proc) execute(execOpts *ipc.ExecOpts, p *prog.Prog, flags ProgTypes, stat Stat) []ipc.CallInfo {
|
info := proc.executeRaw(execOpts, p, stat)
|
for _, callIndex := range proc.fuzzer.checkNewSignal(p, info) {
|
info := info[callIndex]
|
// info.Signal points to the output shmem region, detach it before queueing.
|
info.Signal = append([]uint32{}, info.Signal...)
|
// None of the caller use Cover, so just nil it instead of detaching.
|
// Note: triage input uses executeRaw to get coverage.
|
info.Cover = nil
|
proc.fuzzer.workQueue.enqueue(&WorkTriage{
|
p: p.Clone(),
|
call: callIndex,
|
info: info,
|
flags: flags,
|
})
|
}
|
return info
|
}
|
|
func (proc *Proc) executeRaw(opts *ipc.ExecOpts, p *prog.Prog, stat Stat) []ipc.CallInfo {
|
if opts.Flags&ipc.FlagDedupCover == 0 {
|
log.Fatalf("dedup cover is not enabled")
|
}
|
|
// Limit concurrency window and do leak checking once in a while.
|
ticket := proc.fuzzer.gate.Enter()
|
defer proc.fuzzer.gate.Leave(ticket)
|
|
proc.logProgram(opts, p)
|
try := 0
|
retry:
|
atomic.AddUint64(&proc.fuzzer.stats[stat], 1)
|
output, info, failed, hanged, err := proc.env.Exec(opts, p)
|
if failed {
|
// BUG in output should be recognized by manager.
|
log.Logf(0, "BUG: executor-detected bug:\n%s", output)
|
// Don't return any cover so that the input is not added to corpus.
|
return nil
|
}
|
if err != nil {
|
if _, ok := err.(ipc.ExecutorFailure); ok || try > 10 {
|
log.Fatalf("executor %v failed %v times:\n%v", proc.pid, try, err)
|
}
|
try++
|
log.Logf(4, "fuzzer detected executor failure='%v', retrying #%d\n", err, (try + 1))
|
debug.FreeOSMemory()
|
time.Sleep(time.Second)
|
goto retry
|
}
|
log.Logf(2, "result failed=%v hanged=%v: %s\n", failed, hanged, output)
|
return info
|
}
|
|
func (proc *Proc) logProgram(opts *ipc.ExecOpts, p *prog.Prog) {
|
if proc.fuzzer.outputType == OutputNone {
|
return
|
}
|
|
data := p.Serialize()
|
strOpts := ""
|
if opts.Flags&ipc.FlagInjectFault != 0 {
|
strOpts = fmt.Sprintf(" (fault-call:%v fault-nth:%v)", opts.FaultCall, opts.FaultNth)
|
}
|
|
// The following output helps to understand what program crashed kernel.
|
// It must not be intermixed.
|
switch proc.fuzzer.outputType {
|
case OutputStdout:
|
now := time.Now()
|
proc.fuzzer.logMu.Lock()
|
fmt.Printf("%02v:%02v:%02v executing program %v%v:\n%s\n",
|
now.Hour(), now.Minute(), now.Second(),
|
proc.pid, strOpts, data)
|
proc.fuzzer.logMu.Unlock()
|
case OutputDmesg:
|
fd, err := syscall.Open("/dev/kmsg", syscall.O_WRONLY, 0)
|
if err == nil {
|
buf := new(bytes.Buffer)
|
fmt.Fprintf(buf, "syzkaller: executing program %v%v:\n%s\n",
|
proc.pid, strOpts, data)
|
syscall.Write(fd, buf.Bytes())
|
syscall.Close(fd)
|
}
|
case OutputFile:
|
f, err := os.Create(fmt.Sprintf("%v-%v.prog", proc.fuzzer.name, proc.pid))
|
if err == nil {
|
if strOpts != "" {
|
fmt.Fprintf(f, "#%v\n", strOpts)
|
}
|
f.Write(data)
|
f.Close()
|
}
|
default:
|
log.Fatalf("unknown output type: %v", proc.fuzzer.outputType)
|
}
|
}
|