// 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)
}
}