mirror of
https://github.com/reactos/syzkaller.git
synced 2025-02-26 14:45:32 +00:00

RPC package does excessive caching per connection, so if a larger object is ever sent in any direction, rpc connection consumes large amount of memory persistently. This makes manager consume gigs of memory with large number of VMs and larger corpus/coverage. Make all communication done in very limited batches.
419 lines
11 KiB
Go
419 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"
|
|
"fmt"
|
|
"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/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")
|
|
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
|
|
)
|
|
flag.Parse()
|
|
var outputType OutputType
|
|
switch *flagOutput {
|
|
case "none":
|
|
outputType = OutputNone
|
|
case "stdout":
|
|
outputType = OutputStdout
|
|
case "dmesg":
|
|
outputType = OutputDmesg
|
|
case "file":
|
|
outputType = OutputFile
|
|
default:
|
|
fmt.Fprintf(os.Stderr, "-output flag must be one of none/stdout/dmesg/file\n")
|
|
os.Exit(1)
|
|
}
|
|
log.Logf(0, "fuzzer started")
|
|
|
|
target, err := prog.GetTarget(runtime.GOOS, *flagArch)
|
|
if err != nil {
|
|
log.Fatalf("%v", err)
|
|
}
|
|
|
|
config, execOpts, err := ipc.DefaultConfig()
|
|
if err != nil {
|
|
log.Fatalf("failed to create default ipc config: %v", err)
|
|
}
|
|
sandbox := "none"
|
|
if config.Flags&ipc.FlagSandboxSetuid != 0 {
|
|
sandbox = "setuid"
|
|
} else if config.Flags&ipc.FlagSandboxNamespace != 0 {
|
|
sandbox = "namespace"
|
|
}
|
|
|
|
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
|
|
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(r.CheckResult.Features)
|
|
if err != nil {
|
|
log.Fatalf("BUG: %v", err)
|
|
}
|
|
if r.CheckResult.Features[host.FeatureNetworkInjection].Enabled {
|
|
config.Flags |= ipc.FlagEnableTun
|
|
}
|
|
if r.CheckResult.Features[host.FeatureFaultInjection].Enabled {
|
|
config.Flags |= ipc.FlagEnableFault
|
|
}
|
|
|
|
needPoll := make(chan struct{}, 1)
|
|
needPoll <- struct{}{}
|
|
fuzzer := &Fuzzer{
|
|
name: *flagName,
|
|
outputType: outputType,
|
|
config: config,
|
|
execOpts: execOpts,
|
|
gate: ipc.NewGate(2**flagProcs, periodicCallback),
|
|
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 {
|
|
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)
|
|
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)
|
|
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.CallInfo) (calls []int) {
|
|
fuzzer.signalMu.RLock()
|
|
defer fuzzer.signalMu.RUnlock()
|
|
for i, inf := range info {
|
|
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
|
|
}
|