// 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 := "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 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) } 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, 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[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) 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 } 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 } }