diff options
| author | Dmitry Vyukov <dvyukov@google.com> | 2017-12-19 10:53:39 +0100 |
|---|---|---|
| committer | Dmitry Vyukov <dvyukov@google.com> | 2017-12-19 10:53:39 +0100 |
| commit | 25793abb59e8bdfb808bfce274553ede6ae28a47 (patch) | |
| tree | bb785ac7dbffa5d84b669ee89b74879202be47ad | |
| parent | 8d5ba3821f70c961f6b42e64dbd6aaaace399b5e (diff) | |
syz-fuzzer: wipe all global state
| -rw-r--r-- | syz-fuzzer/fuzzer.go | 179 | ||||
| -rw-r--r-- | syz-fuzzer/fuzzer_freebsd.go | 4 | ||||
| -rw-r--r-- | syz-fuzzer/fuzzer_fuchsia.go | 4 | ||||
| -rw-r--r-- | syz-fuzzer/fuzzer_linux.go | 9 | ||||
| -rw-r--r-- | syz-fuzzer/fuzzer_netbsd.go | 4 | ||||
| -rw-r--r-- | syz-fuzzer/fuzzer_windows.go | 4 | ||||
| -rw-r--r-- | syz-fuzzer/proc.go | 34 |
7 files changed, 123 insertions, 115 deletions
diff --git a/syz-fuzzer/fuzzer.go b/syz-fuzzer/fuzzer.go index 31522e97f..85dd2a7e1 100644 --- a/syz-fuzzer/fuzzer.go +++ b/syz-fuzzer/fuzzer.go @@ -29,27 +29,6 @@ import ( "github.com/google/syzkaller/sys" ) -var ( - flagName = flag.String("name", "", "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") - flagLeak = flag.Bool("leak", false, "detect memory leaks") - 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 -) - -var ( - manager *RpcClient - target *prog.Target - - allTriaged uint32 - noCover bool - faultInjectionEnabled bool - compsSupported bool -) - type Fuzzer struct { name string outputType OutputType @@ -58,8 +37,17 @@ type Fuzzer struct { procs []*Proc gate *ipc.Gate workQueue *WorkQueue + needPoll chan struct{} choiceTable *prog.ChoiceTable stats [StatCount]uint64 + manager *RpcClient + target *prog.Target + + faultInjectionEnabled bool + comparisonTracingEnabled bool + coverageEnabled bool + leakCheckEnabled bool + leakCheckReady uint32 corpusMu sync.RWMutex corpus []*prog.Prog @@ -85,6 +73,17 @@ const ( 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 ( @@ -96,6 +95,17 @@ const ( func main() { debug.SetGCPercent(50) + + var ( + flagName = flag.String("name", "", "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") + flagLeak = flag.Bool("leak", false, "detect memory leaks") + 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 { @@ -113,8 +123,7 @@ func main() { } Logf(0, "fuzzer started") - var err error - target, err = prog.GetTarget(runtime.GOOS, *flagArch) + target, err := prog.GetTarget(runtime.GOOS, *flagArch) if err != nil { Fatalf("%v", err) } @@ -152,6 +161,7 @@ func main() { ct := target.BuildChoiceTable(r.Prios, calls) // This requires "fault-inject: support systematic fault injection" kernel commit. + faultInjectionEnabled := false if fd, err := syscall.Open("/proc/self/fail-nth", syscall.O_RDWR, 0); err == nil { syscall.Close(fd) faultInjectionEnabled = true @@ -168,11 +178,10 @@ func main() { if faultInjectionEnabled { config.Flags |= ipc.FlagEnableFault } - noCover = config.Flags&ipc.FlagSignal == 0 + coverageEnabled := config.Flags&ipc.FlagSignal != 0 - kcov := false - kcov, compsSupported = checkCompsSupported() - Logf(0, "kcov=%v, comps=%v", kcov, compsSupported) + kcov, comparisonTracingEnabled := checkCompsSupported() + Logf(0, "kcov=%v, comps=%v", kcov, comparisonTracingEnabled) if r.NeedCheck { out, err := osutil.RunCmd(time.Minute, "", config.Executor, "version") if err != nil { @@ -197,7 +206,7 @@ func main() { a.Leak = true } a.Fault = faultInjectionEnabled - a.CompsSupported = compsSupported + a.CompsSupported = comparisonTracingEnabled for c := range calls { a.Calls = append(a.Calls, c.Name) } @@ -210,56 +219,53 @@ func main() { // So we do the call on a transient connection, free all memory and reconnect. // The rest of rpc requests have bounded size. debug.FreeOSMemory() - if conn, err := NewRpcClient(*flagManager); err != nil { + manager, err := NewRpcClient(*flagManager) + if err != nil { panic(err) - } else { - manager = conn } - kmemleakInit() + kmemleakInit(*flagLeak) - leakCallback := func() { - if atomic.LoadUint32(&allTriaged) != 0 { - // Scan for leaks once in a while (it is damn slow). - kmemleakScan(true) - } - } - if !*flagLeak { - leakCallback = nil - } needPoll := make(chan struct{}, 1) needPoll <- struct{}{} fuzzer := &Fuzzer{ - name: *flagName, - outputType: outputType, - config: config, - execOpts: execOpts, - gate: ipc.NewGate(2**flagProcs, leakCallback), - workQueue: newWorkQueue(*flagProcs, needPoll), - choiceTable: ct, - corpusHashes: make(map[hash.Sig]struct{}), - corpusSignal: make(map[uint32]struct{}), - maxSignal: make(map[uint32]struct{}), - newSignal: make(map[uint32]struct{}), + name: *flagName, + outputType: outputType, + config: config, + execOpts: execOpts, + workQueue: newWorkQueue(*flagProcs, needPoll), + needPoll: needPoll, + choiceTable: ct, + manager: manager, + target: target, + faultInjectionEnabled: faultInjectionEnabled, + comparisonTracingEnabled: comparisonTracingEnabled, + coverageEnabled: coverageEnabled, + leakCheckEnabled: *flagLeak, + corpusHashes: make(map[hash.Sig]struct{}), + corpusSignal: make(map[uint32]struct{}), + maxSignal: make(map[uint32]struct{}), + newSignal: make(map[uint32]struct{}), } + fuzzer.gate = ipc.NewGate(2**flagProcs, fuzzer.leakCheckCallback) for _, inp := range r.Inputs { fuzzer.addInputFromAnotherFuzzer(inp) } fuzzer.addMaxSignal(r.MaxSignal) for _, candidate := range r.Candidates { - p, err := target.Deserialize(candidate.Prog) + p, err := fuzzer.target.Deserialize(candidate.Prog) if err != nil { panic(err) } - if noCover { - fuzzer.addInputToCorpus(p, nil, hash.Hash(candidate.Prog)) - } else { + if coverageEnabled { fuzzer.workQueue.enqueue(&WorkCandidate{ p: p, minimized: candidate.Minimized, smashed: candidate.Smashed, }) + } else { + fuzzer.addInputToCorpus(p, nil, hash.Hash(candidate.Prog)) } } @@ -272,6 +278,10 @@ func main() { go proc.loop() } + fuzzer.pollLoop() +} + +func (fuzzer *Fuzzer) pollLoop() { var execTotal uint64 var lastPoll time.Time var lastPrint time.Time @@ -280,10 +290,10 @@ func main() { poll := false select { case <-ticker: - case <-needPoll: + case <-fuzzer.needPoll: poll = true } - if *flagOutput != "stdout" && time.Since(lastPrint) > 10*time.Second { + if fuzzer.outputType != OutputStdout && time.Since(lastPrint) > 10*time.Second { // Keep-alive for manager. Logf(0, "alive, executed %v", execTotal) lastPrint = time.Now() @@ -304,22 +314,15 @@ func main() { a.Stats["exec total"] += atomic.SwapUint64(&proc.env.StatExecs, 0) a.Stats["executor restarts"] += atomic.SwapUint64(&proc.env.StatRestarts, 0) } - stat := func(stat Stat, name string) { + + for stat := Stat(0); stat < StatCount; stat++ { v := atomic.SwapUint64(&fuzzer.stats[stat], 0) - a.Stats[name] = v + a.Stats[statNames[stat]] = v execTotal += v } - stat(StatGenerate, "exec gen") - stat(StatFuzz, "exec fuzz") - stat(StatCandidate, "exec candidate") - stat(StatTriage, "exec triage") - stat(StatMinimize, "exec minimize") - stat(StatSmash, "exec smash") - stat(StatHint, "exec hints") - stat(StatSeed, "exec seeds") r := &PollRes{} - if err := manager.Call("Manager.Poll", a, r); err != nil { + if err := fuzzer.manager.Call("Manager.Poll", a, r); err != nil { panic(err) } Logf(1, "poll: candidates=%v inputs=%v signal=%v", @@ -329,25 +332,24 @@ func main() { fuzzer.addInputFromAnotherFuzzer(inp) } for _, candidate := range r.Candidates { - p, err := target.Deserialize(candidate.Prog) + p, err := fuzzer.target.Deserialize(candidate.Prog) if err != nil { panic(err) } - if noCover { - fuzzer.addInputToCorpus(p, nil, hash.Hash(candidate.Prog)) - } else { + if fuzzer.coverageEnabled { fuzzer.workQueue.enqueue(&WorkCandidate{ p: p, minimized: candidate.Minimized, smashed: candidate.Smashed, }) + } else { + fuzzer.addInputToCorpus(p, nil, hash.Hash(candidate.Prog)) } } - if len(r.Candidates) == 0 && atomic.LoadUint32(&allTriaged) == 0 { - if *flagLeak { - kmemleakScan(false) - } - atomic.StoreUint32(&allTriaged, 1) + if len(r.Candidates) == 0 && fuzzer.leakCheckEnabled && + atomic.LoadUint32(&fuzzer.leakCheckReady) == 0 { + kmemleakScan(false) // ignore boot leaks + atomic.StoreUint32(&fuzzer.leakCheckReady, 1) } if len(r.NewInputs) == 0 && len(r.Candidates) == 0 { lastPoll = time.Now() @@ -393,11 +395,21 @@ func buildCallList(target *prog.Target, enabledCalls string) map[*prog.Syscall]b return calls } +func (fuzzer *Fuzzer) sendInputToManager(inp RpcInput) { + a := &NewInputArgs{ + Name: fuzzer.name, + RpcInput: inp, + } + if err := fuzzer.manager.Call("Manager.NewInput", a, nil); err != nil { + panic(err) + } +} + func (fuzzer *Fuzzer) addInputFromAnotherFuzzer(inp RpcInput) { - if noCover { + if !fuzzer.coverageEnabled { panic("should not be called when coverage is disabled") } - p, err := target.Deserialize(inp.Prog) + p, err := fuzzer.target.Deserialize(inp.Prog) if err != nil { panic(err) } @@ -482,3 +494,10 @@ func (fuzzer *Fuzzer) checkNewSignal(info []ipc.CallInfo) (calls []int) { } return } + +func (fuzzer *Fuzzer) leakCheckCallback() { + if atomic.LoadUint32(&fuzzer.leakCheckReady) != 0 { + // Scan for leaks once in a while (it is damn slow). + kmemleakScan(true) + } +} diff --git a/syz-fuzzer/fuzzer_freebsd.go b/syz-fuzzer/fuzzer_freebsd.go index 0f3beb420..7f569e5fa 100644 --- a/syz-fuzzer/fuzzer_freebsd.go +++ b/syz-fuzzer/fuzzer_freebsd.go @@ -7,8 +7,8 @@ import ( "github.com/google/syzkaller/pkg/log" ) -func kmemleakInit() { - if *flagLeak { +func kmemleakInit(enable bool) { + if enable { log.Fatalf("leak checking is not supported on freebsd") } } diff --git a/syz-fuzzer/fuzzer_fuchsia.go b/syz-fuzzer/fuzzer_fuchsia.go index 834f256e1..190635629 100644 --- a/syz-fuzzer/fuzzer_fuchsia.go +++ b/syz-fuzzer/fuzzer_fuchsia.go @@ -9,8 +9,8 @@ import ( "github.com/google/syzkaller/pkg/log" ) -func kmemleakInit() { - if *flagLeak { +func kmemleakInit(enable bool) { + if enable { log.Fatalf("leak checking is not supported on fuchsia") } } diff --git a/syz-fuzzer/fuzzer_linux.go b/syz-fuzzer/fuzzer_linux.go index caec22821..32983ff57 100644 --- a/syz-fuzzer/fuzzer_linux.go +++ b/syz-fuzzer/fuzzer_linux.go @@ -11,18 +11,17 @@ import ( "github.com/google/syzkaller/sys/linux" ) -func kmemleakInit() { +func kmemleakInit(enable bool) { fd, err := syscall.Open("/sys/kernel/debug/kmemleak", syscall.O_RDWR, 0) if err != nil { - if *flagLeak { - log.Fatalf("BUG: /sys/kernel/debug/kmemleak is missing (%v). Enable CONFIG_KMEMLEAK and mount debugfs.", err) - } else { + if !enable { return } + log.Fatalf("BUG: /sys/kernel/debug/kmemleak is missing (%v). Enable CONFIG_KMEMLEAK and mount debugfs.", err) } defer syscall.Close(fd) what := "scan=off" - if !*flagLeak { + if !enable { what = "off" } if _, err := syscall.Write(fd, []byte(what)); err != nil { diff --git a/syz-fuzzer/fuzzer_netbsd.go b/syz-fuzzer/fuzzer_netbsd.go index b0dec2548..8921abe5f 100644 --- a/syz-fuzzer/fuzzer_netbsd.go +++ b/syz-fuzzer/fuzzer_netbsd.go @@ -7,8 +7,8 @@ import ( "github.com/google/syzkaller/pkg/log" ) -func kmemleakInit() { - if *flagLeak { +func kmemleakInit(enable bool) { + if enable { log.Fatalf("leak checking is not supported on netbsd") } } diff --git a/syz-fuzzer/fuzzer_windows.go b/syz-fuzzer/fuzzer_windows.go index 98fdad425..e09b389ac 100644 --- a/syz-fuzzer/fuzzer_windows.go +++ b/syz-fuzzer/fuzzer_windows.go @@ -7,8 +7,8 @@ import ( "github.com/google/syzkaller/pkg/log" ) -func kmemleakInit() { - if *flagLeak { +func kmemleakInit(enable bool) { + if enable { log.Fatalf("leak checking is not supported on windows") } } diff --git a/syz-fuzzer/proc.go b/syz-fuzzer/proc.go index 0faa7189d..fb678efef 100644 --- a/syz-fuzzer/proc.go +++ b/syz-fuzzer/proc.go @@ -74,7 +74,7 @@ func (proc *Proc) loop() { corpus := proc.fuzzer.corpusSnapshot() if len(corpus) == 0 || i%100 == 0 { // Generate a new prog. - p := target.Generate(proc.rnd, programLength, ct) + p := proc.fuzzer.target.Generate(proc.rnd, programLength, ct) Logf(1, "#%v: generated", pid) proc.execute(execOpts, p, false, false, false, false, false, StatGenerate) } else { @@ -91,7 +91,7 @@ func (proc *Proc) triageInput(item *WorkTriage) { Logf(1, "#%v: triaging minimized=%v candidate=%v", proc.pid, item.minimized, item.candidate) execOpts := proc.fuzzer.execOpts - if noCover { + if !proc.fuzzer.coverageEnabled { panic("should not be called when coverage is disabled") } @@ -158,18 +158,12 @@ func (proc *Proc) triageInput(item *WorkTriage) { sig := hash.Hash(data) Logf(2, "added new input for %v to corpus:\n%s", call.CallName, data) - a := &NewInputArgs{ - Name: proc.fuzzer.name, - RpcInput: RpcInput{ - Call: call.CallName, - Prog: data, - Signal: []uint32(cover.Canonicalize(item.signal)), - Cover: []uint32(inputCover), - }, - } - if err := manager.Call("Manager.NewInput", a, nil); err != nil { - panic(err) - } + proc.fuzzer.sendInputToManager(RpcInput{ + Call: call.CallName, + Prog: data, + Signal: []uint32(cover.Canonicalize(item.signal)), + Cover: []uint32(inputCover), + }) proc.fuzzer.addInputToCorpus(item.p, item.signal, sig) @@ -179,9 +173,12 @@ func (proc *Proc) triageInput(item *WorkTriage) { } func (proc *Proc) smashInput(item *WorkSmash) { - if faultInjectionEnabled { + 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() @@ -189,9 +186,6 @@ func (proc *Proc) smashInput(item *WorkSmash) { Logf(1, "#%v: smash mutated", proc.pid) proc.execute(proc.fuzzer.execOpts, p, false, false, false, false, false, StatSmash) } - if compsSupported { - proc.executeHintSeed(item.p, item.call) - } } func (proc *Proc) failCall(p *prog.Prog, call int) { @@ -227,12 +221,8 @@ func (proc *Proc) executeHintSeed(p *prog.Prog, call int) { func (proc *Proc) execute(execOpts *ipc.ExecOpts, p *prog.Prog, needComps, minimized, smashed, candidate, noCollide bool, stat Stat) []ipc.CallInfo { - opts := *execOpts if needComps { - if !compsSupported { - panic("compsSupported==false and execute() called with needComps") - } opts.Flags |= ipc.FlagCollectComps } if noCollide { |
