From 9f48e03d80af4edfc99cf01811234cf5bf562aa2 Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Mon, 18 Dec 2017 10:29:40 +0100 Subject: syz-fuzzer: encapsulate corpus in fuzzer Make corpus a fuzzer member rather than global var. This resolves existing races on corpus. --- syz-fuzzer/fuzzer.go | 73 ++++++++++++++++++++++++++++------------------------ syz-fuzzer/proc.go | 28 +++++--------------- 2 files changed, 46 insertions(+), 55 deletions(-) diff --git a/syz-fuzzer/fuzzer.go b/syz-fuzzer/fuzzer.go index e907f715a..774326a15 100644 --- a/syz-fuzzer/fuzzer.go +++ b/syz-fuzzer/fuzzer.go @@ -49,10 +49,6 @@ var ( maxSignal map[uint32]struct{} newSignal map[uint32]struct{} - corpusMu sync.RWMutex - corpus []*prog.Prog - corpusHashes map[hash.Sig]struct{} - allTriaged uint32 noCover bool faultInjectionEnabled bool @@ -67,6 +63,10 @@ type Fuzzer struct { workQueue *WorkQueue choiceTable *prog.ChoiceTable stats [StatCount]uint64 + + corpusMu sync.RWMutex + corpus []*prog.Prog + corpusHashes map[hash.Sig]struct{} } type Stat int @@ -126,7 +126,6 @@ func main() { corpusSignal = make(map[uint32]struct{}) maxSignal = make(map[uint32]struct{}) newSignal = make(map[uint32]struct{}) - corpusHashes = make(map[hash.Sig]struct{}) Logf(0, "dialing manager at %v", *flagManager) a := &ConnectArgs{*flagName} @@ -136,12 +135,6 @@ func main() { } calls := buildCallList(target, r.EnabledCalls) ct := target.BuildChoiceTable(r.Prios, calls) - for _, inp := range r.Inputs { - addInput(inp) - } - for _, s := range r.MaxSignal { - maxSignal[s] = struct{}{} - } // This requires "fault-inject: support systematic fault injection" kernel commit. if fd, err := syscall.Open("/proc/self/fail-nth", syscall.O_RDWR, 0); err == nil { @@ -222,22 +215,27 @@ func main() { needPoll := make(chan struct{}, 1) needPoll <- struct{}{} fuzzer := &Fuzzer{ - config: config, - execOpts: execOpts, - gate: ipc.NewGate(2**flagProcs, leakCallback), - workQueue: newWorkQueue(*flagProcs, needPoll), - choiceTable: ct, + config: config, + execOpts: execOpts, + gate: ipc.NewGate(2**flagProcs, leakCallback), + workQueue: newWorkQueue(*flagProcs, needPoll), + choiceTable: ct, + corpusHashes: make(map[hash.Sig]struct{}), } + for _, inp := range r.Inputs { + fuzzer.addInput(inp) + } + for _, s := range r.MaxSignal { + maxSignal[s] = struct{}{} + } for _, candidate := range r.Candidates { p, err := target.Deserialize(candidate.Prog) if err != nil { panic(err) } if noCover { - corpusMu.Lock() - corpus = append(corpus, p) - corpusMu.Unlock() + fuzzer.addInputToCorpus(p, hash.Hash(candidate.Prog)) } else { fuzzer.workQueue.enqueue(&WorkCandidate{p, candidate.Minimized}) } @@ -318,7 +316,7 @@ func main() { signalMu.Unlock() } for _, inp := range r.NewInputs { - addInput(inp) + fuzzer.addInput(inp) } for _, candidate := range r.Candidates { p, err := target.Deserialize(candidate.Prog) @@ -326,9 +324,7 @@ func main() { panic(err) } if noCover { - corpusMu.Lock() - corpus = append(corpus, p) - corpusMu.Unlock() + fuzzer.addInputToCorpus(p, hash.Hash(candidate.Prog)) } else { fuzzer.workQueue.enqueue(&WorkCandidate{p, candidate.Minimized}) } @@ -383,12 +379,7 @@ func buildCallList(target *prog.Target, enabledCalls string) map[*prog.Syscall]b return calls } -func addInput(inp RpcInput) { - corpusMu.Lock() - defer corpusMu.Unlock() - signalMu.Lock() - defer signalMu.Unlock() - +func (fuzzer *Fuzzer) addInput(inp RpcInput) { if noCover { panic("should not be called when coverage is disabled") } @@ -396,13 +387,27 @@ func addInput(inp RpcInput) { if err != nil { panic(err) } - sig := hash.Hash(inp.Prog) - if _, ok := corpusHashes[sig]; !ok { - corpus = append(corpus, p) - corpusHashes[sig] = struct{}{} - } + fuzzer.addInputToCorpus(p, hash.Hash(inp.Prog)) + + signalMu.Lock() + defer signalMu.Unlock() if diff := cover.SignalDiff(maxSignal, inp.Signal); len(diff) != 0 { cover.SignalAdd(corpusSignal, diff) cover.SignalAdd(maxSignal, diff) } } + +func (fuzzer *Fuzzer) addInputToCorpus(p *prog.Prog, sig hash.Sig) { + fuzzer.corpusMu.Lock() + defer fuzzer.corpusMu.Unlock() + if _, ok := fuzzer.corpusHashes[sig]; !ok { + fuzzer.corpus = append(fuzzer.corpus, p) + fuzzer.corpusHashes[sig] = struct{}{} + } +} + +func (fuzzer *Fuzzer) corpusSnapshot() []*prog.Prog { + fuzzer.corpusMu.RLock() + defer fuzzer.corpusMu.RUnlock() + return fuzzer.corpus +} diff --git a/syz-fuzzer/proc.go b/syz-fuzzer/proc.go index 9d4b86f8e..70e86c130 100644 --- a/syz-fuzzer/proc.go +++ b/syz-fuzzer/proc.go @@ -71,18 +71,15 @@ func (proc *Proc) loop() { continue } - corpusMu.RLock() + corpus := proc.fuzzer.corpusSnapshot() if len(corpus) == 0 || i%100 == 0 { // Generate a new prog. - corpusMu.RUnlock() p := target.Generate(proc.rnd, programLength, ct) Logf(1, "#%v: generated", pid) proc.execute(execOpts, p, false, false, false, false, StatGenerate) } else { // Mutate an existing prog. p := corpus[proc.rnd.Intn(len(corpus))].Clone() - corpusMu.RUnlock() - // TODO: it seems that access to corpus is not proceted here. p.Mutate(proc.rnd, programLength, ct, corpus) Logf(1, "#%v: mutated", pid) proc.execute(execOpts, p, false, false, false, false, StatFuzz) @@ -107,10 +104,8 @@ func (proc *Proc) triageInput(item *WorkTriage) { newSignal = cover.Canonicalize(newSignal) call := item.p.Calls[item.call].Meta - data := item.p.Serialize() - sig := hash.Hash(data) - Logf(3, "triaging input for %v (new signal=%v):\n%s", call.CallName, len(newSignal), data) + Logf(3, "triaging input for %v (new signal=%v)", call.CallName, len(newSignal)) var inputCover cover.Cover opts := *execOpts opts.Flags |= ipc.FlagCollectCover @@ -166,6 +161,9 @@ func (proc *Proc) triageInput(item *WorkTriage) { }, false) } + data := item.p.Serialize() + sig := hash.Hash(data) + Logf(2, "added new input for %v to corpus:\n%s", call.CallName, data) a := &NewInputArgs{ Name: *flagName, @@ -184,12 +182,7 @@ func (proc *Proc) triageInput(item *WorkTriage) { cover.SignalAdd(corpusSignal, item.signal) signalMu.Unlock() - corpusMu.Lock() - if _, ok := corpusHashes[sig]; !ok { - corpus = append(corpus, item.p) - corpusHashes[sig] = struct{}{} - } - corpusMu.Unlock() + proc.fuzzer.addInputToCorpus(item.p, sig) if !item.minimized { proc.fuzzer.workQueue.enqueue(&WorkSmash{item.p, item.call}) @@ -200,9 +193,9 @@ func (proc *Proc) smashInput(item *WorkSmash) { if faultInjectionEnabled { proc.failCall(item.p, item.call) } + corpus := proc.fuzzer.corpusSnapshot() for i := 0; i < 100; i++ { p := item.p.Clone() - // TODO: it seems that access to corpus is not proceted here. p.Mutate(proc.rnd, programLength, proc.fuzzer.choiceTable, corpus) Logf(1, "#%v: smash mutated", proc.pid) proc.execute(proc.fuzzer.execOpts, p, false, false, false, false, StatSmash) @@ -287,13 +280,6 @@ func (proc *Proc) execute(execOpts *ipc.ExecOpts, p *prog.Prog, var logMu sync.Mutex func (proc *Proc) executeRaw(opts *ipc.ExecOpts, p *prog.Prog, stat Stat) []ipc.CallInfo { - if false { - // For debugging, this function must not be executed with locks held. - corpusMu.Lock() - corpusMu.Unlock() - signalMu.Lock() - signalMu.Unlock() - } pid := proc.pid if opts.Flags&ipc.FlagDedupCover == 0 { panic("dedup cover is not enabled") -- cgit mrf-deployment