syzkaller/syz-fuzzer/fuzzer.go
Dmitry Vyukov 593b260b02 pkg/ipc: move sandbox helpers from ipcconfig
Currently syz-runtest fails to start because -debug flag is defined
both in syz-runtest and ipcconfig.
But moving sandbox functions we prevent ipcconfig from being imported into syz-runtest.
2018-12-10 16:37:02 +01:00

431 lines
11 KiB
Go

// Copyright 2015 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 (
"flag"
"net/http"
_ "net/http/pprof"
"os"
"runtime"
"runtime/debug"
"sync"
"sync/atomic"
"time"
"github.com/google/syzkaller/pkg/hash"
"github.com/google/syzkaller/pkg/host"
"github.com/google/syzkaller/pkg/ipc"
"github.com/google/syzkaller/pkg/ipc/ipcconfig"
"github.com/google/syzkaller/pkg/log"
"github.com/google/syzkaller/pkg/osutil"
"github.com/google/syzkaller/pkg/rpctype"
"github.com/google/syzkaller/pkg/signal"
"github.com/google/syzkaller/prog"
_ "github.com/google/syzkaller/sys"
)
type Fuzzer struct {
name string
outputType OutputType
config *ipc.Config
execOpts *ipc.ExecOpts
procs []*Proc
gate *ipc.Gate
workQueue *WorkQueue
needPoll chan struct{}
choiceTable *prog.ChoiceTable
stats [StatCount]uint64
manager *rpctype.RPCClient
target *prog.Target
faultInjectionEnabled bool
comparisonTracingEnabled bool
corpusMu sync.RWMutex
corpus []*prog.Prog
corpusHashes map[hash.Sig]struct{}
signalMu sync.RWMutex
corpusSignal signal.Signal // signal of inputs in corpus
maxSignal signal.Signal // max signal ever observed including flakes
newSignal signal.Signal // diff of maxSignal since last sync with master
logMu sync.Mutex
}
type Stat int
const (
StatGenerate Stat = iota
StatFuzz
StatCandidate
StatTriage
StatMinimize
StatSmash
StatHint
StatSeed
StatCount
)
var statNames = [StatCount]string{
StatGenerate: "exec gen",
StatFuzz: "exec fuzz",
StatCandidate: "exec candidate",
StatTriage: "exec triage",
StatMinimize: "exec minimize",
StatSmash: "exec smash",
StatHint: "exec hints",
StatSeed: "exec seeds",
}
type OutputType int
const (
OutputNone OutputType = iota
OutputStdout
OutputDmesg
OutputFile
)
func main() {
debug.SetGCPercent(50)
var (
flagName = flag.String("name", "test", "unique name for manager")
flagOS = flag.String("os", runtime.GOOS, "target OS")
flagArch = flag.String("arch", runtime.GOARCH, "target arch")
flagManager = flag.String("manager", "", "manager rpc address")
flagProcs = flag.Int("procs", 1, "number of parallel test processes")
flagOutput = flag.String("output", "stdout", "write programs to none/stdout/dmesg/file")
flagPprof = flag.String("pprof", "", "address to serve pprof profiles")
flagTest = flag.Bool("test", false, "enable image testing mode") // used by syz-ci
flagRunTest = flag.Bool("runtest", false, "enable program testing mode") // used by pkg/runtest
)
flag.Parse()
outputType := parseOutputType(*flagOutput)
log.Logf(0, "fuzzer started")
target, err := prog.GetTarget(*flagOS, *flagArch)
if err != nil {
log.Fatalf("%v", err)
}
config, execOpts, err := ipcconfig.Default(target)
if err != nil {
log.Fatalf("failed to create default ipc config: %v", err)
}
sandbox := ipc.FlagsToSandbox(config.Flags)
shutdown := make(chan struct{})
osutil.HandleInterrupts(shutdown)
go func() {
// Handles graceful preemption on GCE.
<-shutdown
log.Logf(0, "SYZ-FUZZER: PREEMPTED")
os.Exit(1)
}()
checkArgs := &checkArgs{
target: target,
sandbox: sandbox,
ipcConfig: config,
ipcExecOpts: execOpts,
}
if *flagTest {
testImage(*flagManager, checkArgs)
return
}
if *flagPprof != "" {
go func() {
err := http.ListenAndServe(*flagPprof, nil)
log.Fatalf("failed to serve pprof profiles: %v", err)
}()
} else {
runtime.MemProfileRate = 0
}
log.Logf(0, "dialing manager at %v", *flagManager)
manager, err := rpctype.NewRPCClient(*flagManager)
if err != nil {
log.Fatalf("failed to connect to manager: %v ", err)
}
a := &rpctype.ConnectArgs{Name: *flagName}
r := &rpctype.ConnectRes{}
if err := manager.Call("Manager.Connect", a, r); err != nil {
log.Fatalf("failed to connect to manager: %v ", err)
}
if r.CheckResult == nil {
checkArgs.gitRevision = r.GitRevision
checkArgs.targetRevision = r.TargetRevision
checkArgs.enabledCalls = r.EnabledCalls
checkArgs.allSandboxes = r.AllSandboxes
r.CheckResult, err = checkMachine(checkArgs)
if err != nil {
r.CheckResult = &rpctype.CheckArgs{
Error: err.Error(),
}
}
r.CheckResult.Name = *flagName
if err := manager.Call("Manager.Check", r.CheckResult, nil); err != nil {
log.Fatalf("Manager.Check call failed: %v", err)
}
if r.CheckResult.Error != "" {
log.Fatalf("%v", r.CheckResult.Error)
}
}
log.Logf(0, "syscalls: %v", len(r.CheckResult.EnabledCalls))
for _, feat := range r.CheckResult.Features {
log.Logf(0, "%v: %v", feat.Name, feat.Reason)
}
periodicCallback, err := host.Setup(target, r.CheckResult.Features)
if err != nil {
log.Fatalf("BUG: %v", err)
}
var gateCallback func()
if periodicCallback != nil {
gateCallback = func() { periodicCallback(r.MemoryLeakFrames) }
}
if r.CheckResult.Features[host.FeatureNetworkInjection].Enabled {
config.Flags |= ipc.FlagEnableTun
}
if r.CheckResult.Features[host.FeatureNetworkDevices].Enabled {
config.Flags |= ipc.FlagEnableNetDev
}
if r.CheckResult.Features[host.FeatureFaultInjection].Enabled {
config.Flags |= ipc.FlagEnableFault
}
if *flagRunTest {
runTest(target, manager, *flagName, config.Executor)
return
}
needPoll := make(chan struct{}, 1)
needPoll <- struct{}{}
fuzzer := &Fuzzer{
name: *flagName,
outputType: outputType,
config: config,
execOpts: execOpts,
gate: ipc.NewGate(2**flagProcs, gateCallback),
workQueue: newWorkQueue(*flagProcs, needPoll),
needPoll: needPoll,
manager: manager,
target: target,
faultInjectionEnabled: r.CheckResult.Features[host.FeatureFaultInjection].Enabled,
comparisonTracingEnabled: r.CheckResult.Features[host.FeatureComparisons].Enabled,
corpusHashes: make(map[hash.Sig]struct{}),
}
for i := 0; fuzzer.poll(i == 0, nil); i++ {
}
calls := make(map[*prog.Syscall]bool)
for _, id := range r.CheckResult.EnabledCalls[sandbox] {
calls[target.Syscalls[id]] = true
}
prios := target.CalculatePriorities(fuzzer.corpus)
fuzzer.choiceTable = target.BuildChoiceTable(prios, calls)
for pid := 0; pid < *flagProcs; pid++ {
proc, err := newProc(fuzzer, pid)
if err != nil {
log.Fatalf("failed to create proc: %v", err)
}
fuzzer.procs = append(fuzzer.procs, proc)
go proc.loop()
}
fuzzer.pollLoop()
}
func (fuzzer *Fuzzer) pollLoop() {
var execTotal uint64
var lastPoll time.Time
var lastPrint time.Time
ticker := time.NewTicker(3 * time.Second).C
for {
poll := false
select {
case <-ticker:
case <-fuzzer.needPoll:
poll = true
}
if fuzzer.outputType != OutputStdout && time.Since(lastPrint) > 10*time.Second {
// Keep-alive for manager.
log.Logf(0, "alive, executed %v", execTotal)
lastPrint = time.Now()
}
if poll || time.Since(lastPoll) > 10*time.Second {
needCandidates := fuzzer.workQueue.wantCandidates()
if poll && !needCandidates {
continue
}
stats := make(map[string]uint64)
for _, proc := range fuzzer.procs {
stats["exec total"] += atomic.SwapUint64(&proc.env.StatExecs, 0)
stats["executor restarts"] += atomic.SwapUint64(&proc.env.StatRestarts, 0)
}
for stat := Stat(0); stat < StatCount; stat++ {
v := atomic.SwapUint64(&fuzzer.stats[stat], 0)
stats[statNames[stat]] = v
execTotal += v
}
if !fuzzer.poll(needCandidates, stats) {
lastPoll = time.Now()
}
}
}
}
func (fuzzer *Fuzzer) poll(needCandidates bool, stats map[string]uint64) bool {
a := &rpctype.PollArgs{
Name: fuzzer.name,
NeedCandidates: needCandidates,
MaxSignal: fuzzer.grabNewSignal().Serialize(),
Stats: stats,
}
r := &rpctype.PollRes{}
if err := fuzzer.manager.Call("Manager.Poll", a, r); err != nil {
log.Fatalf("Manager.Poll call failed: %v", err)
}
maxSignal := r.MaxSignal.Deserialize()
log.Logf(1, "poll: candidates=%v inputs=%v signal=%v",
len(r.Candidates), len(r.NewInputs), maxSignal.Len())
fuzzer.addMaxSignal(maxSignal)
for _, inp := range r.NewInputs {
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,
})
}
return len(r.NewInputs) != 0 || len(r.Candidates) != 0 || maxSignal.Len() != 0
}
func (fuzzer *Fuzzer) sendInputToManager(inp rpctype.RPCInput) {
a := &rpctype.NewInputArgs{
Name: fuzzer.name,
RPCInput: inp,
}
if err := fuzzer.manager.Call("Manager.NewInput", a, nil); err != nil {
log.Fatalf("Manager.NewInput call failed: %v", err)
}
}
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)
}
sig := hash.Hash(inp.Prog)
sign := inp.Signal.Deserialize()
fuzzer.addInputToCorpus(p, sign, sig)
}
func (fuzzer *Fuzzer) addInputToCorpus(p *prog.Prog, sign signal.Signal, sig hash.Sig) {
fuzzer.corpusMu.Lock()
if _, ok := fuzzer.corpusHashes[sig]; !ok {
fuzzer.corpus = append(fuzzer.corpus, p)
fuzzer.corpusHashes[sig] = struct{}{}
}
fuzzer.corpusMu.Unlock()
if !sign.Empty() {
fuzzer.signalMu.Lock()
fuzzer.corpusSignal.Merge(sign)
fuzzer.maxSignal.Merge(sign)
fuzzer.signalMu.Unlock()
}
}
func (fuzzer *Fuzzer) corpusSnapshot() []*prog.Prog {
fuzzer.corpusMu.RLock()
defer fuzzer.corpusMu.RUnlock()
return fuzzer.corpus
}
func (fuzzer *Fuzzer) addMaxSignal(sign signal.Signal) {
if sign.Len() == 0 {
return
}
fuzzer.signalMu.Lock()
defer fuzzer.signalMu.Unlock()
fuzzer.maxSignal.Merge(sign)
}
func (fuzzer *Fuzzer) grabNewSignal() signal.Signal {
fuzzer.signalMu.Lock()
defer fuzzer.signalMu.Unlock()
sign := fuzzer.newSignal
if sign.Empty() {
return nil
}
fuzzer.newSignal = nil
return sign
}
func (fuzzer *Fuzzer) corpusSignalDiff(sign signal.Signal) signal.Signal {
fuzzer.signalMu.RLock()
defer fuzzer.signalMu.RUnlock()
return fuzzer.corpusSignal.Diff(sign)
}
func (fuzzer *Fuzzer) checkNewSignal(p *prog.Prog, info *ipc.ProgInfo) (calls []int) {
fuzzer.signalMu.RLock()
defer fuzzer.signalMu.RUnlock()
for i, inf := range info.Calls {
diff := fuzzer.maxSignal.DiffRaw(inf.Signal, signalPrio(p.Target, p.Calls[i], &inf))
if diff.Empty() {
continue
}
calls = append(calls, i)
fuzzer.signalMu.RUnlock()
fuzzer.signalMu.Lock()
fuzzer.maxSignal.Merge(diff)
fuzzer.newSignal.Merge(diff)
fuzzer.signalMu.Unlock()
fuzzer.signalMu.RLock()
}
return
}
func signalPrio(target *prog.Target, c *prog.Call, ci *ipc.CallInfo) (prio uint8) {
if ci.Errno == 0 {
prio |= 1 << 1
}
if !target.CallContainsAny(c) {
prio |= 1 << 0
}
return
}
func parseOutputType(str string) OutputType {
switch str {
case "none":
return OutputNone
case "stdout":
return OutputStdout
case "dmesg":
return OutputDmesg
case "file":
return OutputFile
default:
log.Fatalf("-output flag must be one of none/stdout/dmesg/file")
return OutputNone
}
}