diff options
| -rw-r--r-- | syz-manager/html.go | 50 | ||||
| -rw-r--r-- | syz-manager/hub.go | 192 | ||||
| -rw-r--r-- | syz-manager/manager.go | 252 | ||||
| -rw-r--r-- | syz-manager/stats.go | 56 |
4 files changed, 346 insertions, 204 deletions
diff --git a/syz-manager/html.go b/syz-manager/html.go index a84b71d01..7bb0741d8 100644 --- a/syz-manager/html.go +++ b/syz-manager/html.go @@ -122,8 +122,16 @@ func (mgr *Manager) collectStats() []UIStat { secs = uint64(time.Since(mgr.firstConnect))/1e9 + 1 } + intStats := convertStats(mgr.stats.all(), secs) + intStats = append(intStats, convertStats(mgr.fuzzerStats, secs)...) + sort.Sort(UIStatArray(intStats)) + stats = append(stats, intStats...) + return stats +} + +func convertStats(stats map[string]uint64, secs uint64) []UIStat { var intStats []UIStat - for k, v := range mgr.stats { + for k, v := range stats { val := fmt.Sprintf("%v", v) if x := v / secs; x >= 10 { val += fmt.Sprintf(" (%v/sec)", x) @@ -135,9 +143,7 @@ func (mgr *Manager) collectStats() []UIStat { } intStats = append(intStats, UIStat{Name: k, Value: val}) } - sort.Sort(UIStatArray(intStats)) - stats = append(stats, intStats...) - return stats + return intStats } func (mgr *Manager) collectSyscallInfo() map[string]*CallCov { @@ -413,11 +419,11 @@ func readCrash(workdir, dir string, repros map[string]bool, full bool) *UICrashT return nil } defer descFile.Close() - desc, err := ioutil.ReadAll(descFile) - if err != nil || len(desc) == 0 { + descBytes, err := ioutil.ReadAll(descFile) + if err != nil || len(descBytes) == 0 { return nil } - desc = trimNewLines(desc) + desc := string(trimNewLines(descBytes)) stat, err := descFile.Stat() if err != nil { return nil @@ -471,20 +477,9 @@ func readCrash(workdir, dir string, repros map[string]bool, full bool) *UICrashT sort.Sort(UICrashArray(crashes)) } - triaged := "" - if hasRepro { - if hasCRepro { - triaged = "has C repro" - } else { - triaged = "has repro" - } - } else if repros[string(desc)] { - triaged = "reproducing" - } else if reproAttempts >= maxReproAttempts { - triaged = "non-reproducible" - } + triaged := reproStatus(hasRepro, hasCRepro, repros[desc], reproAttempts >= maxReproAttempts) return &UICrashType{ - Description: string(desc), + Description: desc, LastTime: modTime.Format(dateFormat), ID: dir, Count: len(crashes), @@ -493,6 +488,21 @@ func readCrash(workdir, dir string, repros map[string]bool, full bool) *UICrashT } } +func reproStatus(hasRepro, hasCRepro, reproducing, nonReproducible bool) string { + status := "" + if hasRepro { + status = "has repro" + if hasCRepro { + status = "has C repro" + } + } else if reproducing { + status = "reproducing" + } else if nonReproducible { + status = "non-reproducible" + } + return status +} + func trimNewLines(data []byte) []byte { for len(data) > 0 && data[len(data)-1] == '\n' { data = data[:len(data)-1] diff --git a/syz-manager/hub.go b/syz-manager/hub.go new file mode 100644 index 000000000..dee4bcfd9 --- /dev/null +++ b/syz-manager/hub.go @@ -0,0 +1,192 @@ +// Copyright 2018 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 ( + "time" + + "github.com/google/syzkaller/pkg/hash" + "github.com/google/syzkaller/pkg/log" + "github.com/google/syzkaller/pkg/mgrconfig" + "github.com/google/syzkaller/pkg/report" + "github.com/google/syzkaller/pkg/rpctype" + "github.com/google/syzkaller/prog" +) + +func (mgr *Manager) hubSyncLoop() { + hc := &HubConnector{ + mgr: mgr, + cfg: mgr.cfg, + target: mgr.target, + stats: mgr.stats, + enabledCalls: mgr.checkResult.EnabledCalls, + fresh: mgr.fresh, + hubReproQueue: mgr.hubReproQueue, + } + if mgr.cfg.Reproduce && mgr.dash != nil { + hc.needMoreRepros = mgr.needMoreRepros + } + hc.loop() +} + +type HubConnector struct { + mgr HubManagerView + cfg *mgrconfig.Config + target *prog.Target + stats *Stats + enabledCalls []int + fresh bool + hubCorpus map[hash.Sig]bool + newRepros [][]byte + hubReproQueue chan *Crash + needMoreRepros chan chan bool +} + +// HubManagerView restricts interface between HubConnector and Manager. +type HubManagerView interface { + getMinimizedCorpus() (corpus, repros [][]byte) + addNewCandidates(progs [][]byte) +} + +func (hc *HubConnector) loop() { + var hub *rpctype.RPCClient + for { + time.Sleep(time.Minute) + corpus, repros := hc.mgr.getMinimizedCorpus() + hc.newRepros = append(hc.newRepros, repros...) + if hub == nil { + var err error + if hub, err = hc.connect(corpus); err != nil { + log.Logf(0, "failed to connect to hub at %v: %v", hc.cfg.HubAddr, err) + continue + } + log.Logf(0, "connected to hub at %v, corpus %v", hc.cfg.HubAddr, len(corpus)) + } + if err := hc.sync(hub, corpus); err != nil { + log.Logf(0, "hub sync failed: %v", err) + hub.Close() + hub = nil + } + } +} + +func (hc *HubConnector) connect(corpus [][]byte) (*rpctype.RPCClient, error) { + a := &rpctype.HubConnectArgs{ + Client: hc.cfg.HubClient, + Key: hc.cfg.HubKey, + Manager: hc.cfg.Name, + Fresh: hc.fresh, + } + for _, id := range hc.enabledCalls { + a.Calls = append(a.Calls, hc.target.Syscalls[id].Name) + } + hubCorpus := make(map[hash.Sig]bool) + for _, inp := range corpus { + hubCorpus[hash.Hash(inp)] = true + a.Corpus = append(a.Corpus, inp) + } + // Hub.Connect request can be very large, so do it on a transient connection + // (rpc connection buffers never shrink). + if err := rpctype.RPCCall(hc.cfg.HubAddr, "Hub.Connect", a, nil); err != nil { + return nil, err + } + hub, err := rpctype.NewRPCClient(hc.cfg.HubAddr) + if err != nil { + return nil, err + } + hc.hubCorpus = hubCorpus + hc.fresh = false + return hub, nil +} + +func (hc *HubConnector) sync(hub *rpctype.RPCClient, corpus [][]byte) error { + a := &rpctype.HubSyncArgs{ + Client: hc.cfg.HubClient, + Key: hc.cfg.HubKey, + Manager: hc.cfg.Name, + } + sigs := make(map[hash.Sig]bool) + for _, inp := range corpus { + sig := hash.Hash(inp) + sigs[sig] = true + if hc.hubCorpus[sig] { + continue + } + hc.hubCorpus[sig] = true + a.Add = append(a.Add, inp) + } + for sig := range hc.hubCorpus { + if sigs[sig] { + continue + } + delete(hc.hubCorpus, sig) + a.Del = append(a.Del, sig.String()) + } + if hc.needMoreRepros != nil { + needReproReply := make(chan bool) + hc.needMoreRepros <- needReproReply + a.NeedRepros = <-needReproReply + } + a.Repros = hc.newRepros + for { + r := new(rpctype.HubSyncRes) + if err := hub.Call("Hub.Sync", a, r); err != nil { + return err + } + progDropped := hc.processProgs(r.Progs) + reproDropped := hc.processRepros(r.Repros) + hc.stats.hubSendProgAdd.add(len(a.Add)) + hc.stats.hubSendProgDel.add(len(a.Del)) + hc.stats.hubSendRepro.add(len(a.Repros)) + hc.stats.hubRecvProg.add(len(r.Progs) - progDropped) + hc.stats.hubRecvProgDrop.add(progDropped) + hc.stats.hubRecvRepro.add(len(r.Repros) - reproDropped) + hc.stats.hubRecvReproDrop.add(reproDropped) + log.Logf(0, "hub sync: send: add %v, del %v, repros %v;"+ + " recv: progs %v, repros %v; more %v", + len(a.Add), len(a.Del), len(a.Repros), + len(r.Progs)-progDropped, len(r.Repros)-reproDropped, r.More) + a.Add = nil + a.Del = nil + a.Repros = nil + a.NeedRepros = false + hc.newRepros = nil + if len(r.Progs)+r.More == 0 { + return nil + } + } +} + +func (hc *HubConnector) processProgs(progs [][]byte) int { + dropped := 0 + candidates := make([][]byte, 0, len(progs)) + for _, inp := range progs { + if _, err := hc.target.Deserialize(inp); err != nil { + dropped++ + continue + } + candidates = append(candidates, inp) + } + hc.mgr.addNewCandidates(candidates) + return dropped +} + +func (hc *HubConnector) processRepros(repros [][]byte) int { + dropped := 0 + for _, repro := range repros { + if _, err := hc.target.Deserialize(repro); err != nil { + dropped++ + continue + } + hc.hubReproQueue <- &Crash{ + vmIndex: -1, + hub: true, + Report: &report.Report{ + Title: "external repro", + Output: repro, + }, + } + } + return dropped +} diff --git a/syz-manager/manager.go b/syz-manager/manager.go index 6f4eae68b..85f0f8096 100644 --- a/syz-manager/manager.go +++ b/syz-manager/manager.go @@ -53,7 +53,8 @@ type Manager struct { startTime time.Time firstConnect time.Time fuzzingTime time.Duration - stats map[string]uint64 + stats *Stats + fuzzerStats map[string]uint64 crashTypes map[string]bool vmStop chan bool checkResult *rpctype.CheckArgs @@ -77,8 +78,6 @@ type Manager struct { newRepros [][]byte fuzzers map[string]*Fuzzer - hub *rpctype.RPCClient - hubCorpus map[hash.Sig]bool needMoreRepros chan chan bool hubReproQueue chan *Crash reproRequest chan chan map[string]bool @@ -171,7 +170,8 @@ func RunManager(cfg *mgrconfig.Config, target *prog.Target, syscalls map[int]boo reporter: reporter, crashdir: crashdir, startTime: time.Now(), - stats: make(map[string]uint64), + stats: new(Stats), + fuzzerStats: make(map[string]uint64), crashTypes: make(map[string]bool), enabledSyscalls: enabledSyscalls, corpus: make(map[string]rpctype.RPCInput), @@ -220,8 +220,8 @@ func RunManager(cfg *mgrconfig.Config, target *prog.Target, syscalls map[int]boo continue } mgr.fuzzingTime += diff * time.Duration(atomic.LoadUint32(&mgr.numFuzzing)) - executed := mgr.stats["exec total"] - crashes := mgr.stats["crashes"] + executed := mgr.stats.execTotal.get() + crashes := mgr.stats.crashes.get() signal := mgr.corpusSignal.Len() mgr.mu.Unlock() numReproducing := atomic.LoadUint32(&mgr.numReproducing) @@ -252,10 +252,13 @@ func RunManager(cfg *mgrconfig.Config, target *prog.Target, syscalls map[int]boo vals["fuzzing"] = uint64(mgr.fuzzingTime) / 1e9 vals["signal"] = uint64(mgr.corpusSignal.Len()) vals["coverage"] = uint64(len(mgr.corpusCover)) - for k, v := range mgr.stats { + for k, v := range mgr.fuzzerStats { vals[k] = v } mgr.mu.Unlock() + for k, v := range mgr.stats.all() { + vals[k] = v + } data, err := json.MarshalIndent(vals, "", " ") if err != nil { @@ -272,15 +275,6 @@ func RunManager(cfg *mgrconfig.Config, target *prog.Target, syscalls map[int]boo go mgr.dashboardReporter() } - if mgr.cfg.HubClient != "" { - go func() { - for { - time.Sleep(time.Minute) - mgr.hubSync() - } - }() - } - osutil.HandleInterrupts(vm.Shutdown) if mgr.vmPool == nil { log.Logf(0, "no VMs started (type=none)") @@ -307,6 +301,8 @@ type ReproResult struct { hub bool // repro came from hub } +// Manager needs to be refactored (#605). +// nolint: gocyclo func (mgr *Manager) vmLoop() { log.Logf(0, "booting test machines...") log.Logf(0, "wait for the connection from test machine...") @@ -600,9 +596,7 @@ func (mgr *Manager) emailCrash(crash *Crash) { func (mgr *Manager) saveCrash(crash *Crash) bool { if crash.Suppressed { log.Logf(0, "vm-%v: suppressed crash %v", crash.vmIndex, crash.Title) - mgr.mu.Lock() - mgr.stats["suppressed"]++ - mgr.mu.Unlock() + mgr.stats.crashSuppressed.inc() return false } corrupted := "" @@ -614,11 +608,11 @@ func (mgr *Manager) saveCrash(crash *Crash) bool { log.Logf(0, "failed to symbolize report: %v", err) } + mgr.stats.crashes.inc() mgr.mu.Lock() - mgr.stats["crashes"]++ if !mgr.crashTypes[crash.Title] { mgr.crashTypes[crash.Title] = true - mgr.stats["crash types"]++ + mgr.stats.crashTypes.inc() } mgr.mu.Unlock() @@ -828,6 +822,36 @@ func saveReproStats(filename string, stats *repro.Stats) { osutil.WriteFile(filename, []byte(text)) } +func (mgr *Manager) getMinimizedCorpus() (corpus, repros [][]byte) { + mgr.mu.Lock() + defer mgr.mu.Unlock() + mgr.minimizeCorpus() + corpus = make([][]byte, 0, len(mgr.corpus)) + for _, inp := range mgr.corpus { + corpus = append(corpus, inp.Prog) + } + repros = mgr.newRepros + mgr.newRepros = nil + return +} + +func (mgr *Manager) addNewCandidates(progs [][]byte) { + candidates := make([]rpctype.RPCCandidate, len(progs)) + for i, inp := range progs { + candidates[i] = rpctype.RPCCandidate{ + Prog: inp, + Minimized: false, // don't trust programs from hub + Smashed: false, + } + } + mgr.mu.Lock() + defer mgr.mu.Unlock() + mgr.candidates = append(mgr.candidates, candidates...) + if mgr.phase == phaseTriagedCorpus { + mgr.phase = phaseQueriedHub + } +} + func (mgr *Manager) minimizeCorpus() { if mgr.phase < phaseLoadedCorpus { return @@ -863,10 +887,10 @@ func (mgr *Manager) minimizeCorpus() { func (mgr *Manager) Connect(a *rpctype.ConnectArgs, r *rpctype.ConnectRes) error { log.Logf(1, "fuzzer %v connected", a.Name) + mgr.stats.vmRestarts.inc() mgr.mu.Lock() defer mgr.mu.Unlock() - mgr.stats["vm restarts"]++ f := &Fuzzer{ name: a.Name, } @@ -938,7 +962,7 @@ func (mgr *Manager) NewInput(a *rpctype.NewInputArgs, r *int) error { if mgr.corpusSignal.Diff(inputSignal).Empty() { return nil } - mgr.stats["manager new inputs"]++ + mgr.stats.newInputs.inc() mgr.corpusSignal.Merge(inputSignal) mgr.corpusCover.Merge(a.Cover) sig := hash.String(a.RPCInput.Prog) @@ -974,7 +998,12 @@ func (mgr *Manager) Poll(a *rpctype.PollArgs, r *rpctype.PollRes) error { defer mgr.mu.Unlock() for k, v := range a.Stats { - mgr.stats[k] += v + switch k { + case "exec total": + mgr.stats.execTotal.add(int(v)) + default: + mgr.fuzzerStats[k] += v + } } f := mgr.fuzzers[a.Name] @@ -1003,16 +1032,6 @@ func (mgr *Manager) Poll(a *rpctype.PollArgs, r *rpctype.PollRes) error { mgr.candidates[last] = rpctype.RPCCandidate{} mgr.candidates = mgr.candidates[:last] } - if len(mgr.candidates) == 0 { - mgr.candidates = nil - if mgr.phase == phaseLoadedCorpus { - if mgr.cfg.HubClient != "" { - mgr.phase = phaseTriagedCorpus - } else { - mgr.phase = phaseTriagedHub - } - } - } } if len(r.Candidates) == 0 { for i := 0; i < maxInputs && len(f.inputs) > 0; i++ { @@ -1025,157 +1044,22 @@ func (mgr *Manager) Poll(a *rpctype.PollArgs, r *rpctype.PollRes) error { f.inputs = nil } } - log.Logf(4, "poll from %v: candidates=%v inputs=%v maxsignal=%v", - a.Name, len(r.Candidates), len(r.NewInputs), len(r.MaxSignal.Elems)) - return nil -} - -func (mgr *Manager) hubSync() { - mgr.mu.Lock() - defer mgr.mu.Unlock() - - switch mgr.phase { - case phaseInit, phaseLoadedCorpus: - return - case phaseTriagedCorpus: - mgr.phase = phaseQueriedHub - case phaseQueriedHub: - if len(mgr.candidates) == 0 { - mgr.phase = phaseTriagedHub - } - case phaseTriagedHub: - default: - panic("unknown phase") - } - - mgr.minimizeCorpus() - if mgr.hub == nil { - a := &rpctype.HubConnectArgs{ - Client: mgr.cfg.HubClient, - Key: mgr.cfg.HubKey, - Manager: mgr.cfg.Name, - Fresh: mgr.fresh, - } - for _, id := range mgr.checkResult.EnabledCalls { - a.Calls = append(a.Calls, mgr.target.Syscalls[id].Name) - } - hubCorpus := make(map[hash.Sig]bool) - for _, inp := range mgr.corpus { - hubCorpus[hash.Hash(inp.Prog)] = true - a.Corpus = append(a.Corpus, inp.Prog) - } - mgr.mu.Unlock() - // Hub.Connect request can be very large, so do it on a transient connection - // (rpc connection buffers never shrink). - // Also don't do hub rpc's under the mutex -- hub can be slow or inaccessible. - if err := rpctype.RPCCall(mgr.cfg.HubAddr, "Hub.Connect", a, nil); err != nil { - mgr.mu.Lock() - log.Logf(0, "Hub.Connect rpc failed: %v", err) - return - } - conn, err := rpctype.NewRPCClient(mgr.cfg.HubAddr) - if err != nil { - mgr.mu.Lock() - log.Logf(0, "failed to connect to hub at %v: %v", mgr.cfg.HubAddr, err) - return - } - mgr.mu.Lock() - mgr.hub = conn - mgr.hubCorpus = hubCorpus - mgr.fresh = false - log.Logf(0, "connected to hub at %v, corpus %v", mgr.cfg.HubAddr, len(mgr.corpus)) - } - - a := &rpctype.HubSyncArgs{ - Client: mgr.cfg.HubClient, - Key: mgr.cfg.HubKey, - Manager: mgr.cfg.Name, - } - corpus := make(map[hash.Sig]bool) - for _, inp := range mgr.corpus { - sig := hash.Hash(inp.Prog) - corpus[sig] = true - if mgr.hubCorpus[sig] { - continue - } - mgr.hubCorpus[sig] = true - a.Add = append(a.Add, inp.Prog) - } - for sig := range mgr.hubCorpus { - if corpus[sig] { - continue - } - delete(mgr.hubCorpus, sig) - a.Del = append(a.Del, sig.String()) - } - for { - a.Repros = mgr.newRepros - - mgr.mu.Unlock() - - if mgr.cfg.Reproduce && mgr.dash != nil { - needReproReply := make(chan bool) - mgr.needMoreRepros <- needReproReply - a.NeedRepros = <-needReproReply - } - - r := new(rpctype.HubSyncRes) - if err := mgr.hub.Call("Hub.Sync", a, r); err != nil { - mgr.mu.Lock() - log.Logf(0, "Hub.Sync rpc failed: %v", err) - mgr.hub.Close() - mgr.hub = nil - return - } - - reproDropped := 0 - for _, repro := range r.Repros { - _, err := mgr.target.Deserialize(repro) - if err != nil { - reproDropped++ - continue - } - mgr.hubReproQueue <- &Crash{ - vmIndex: -1, - hub: true, - Report: &report.Report{ - Title: "external repro", - Output: repro, - }, - } - } - - mgr.mu.Lock() - mgr.newRepros = nil - dropped := 0 - for _, inp := range r.Progs { - _, err := mgr.target.Deserialize(inp) - if err != nil { - dropped++ - continue + if len(mgr.candidates) == 0 { + mgr.candidates = nil + if mgr.phase == phaseLoadedCorpus { + if mgr.cfg.HubClient != "" { + mgr.phase = phaseTriagedCorpus + go mgr.hubSyncLoop() + } else { + mgr.phase = phaseTriagedHub } - mgr.candidates = append(mgr.candidates, rpctype.RPCCandidate{ - Prog: inp, - Minimized: false, // don't trust programs from hub - Smashed: false, - }) - } - mgr.stats["hub add"] += uint64(len(a.Add)) - mgr.stats["hub del"] += uint64(len(a.Del)) - mgr.stats["hub drop"] += uint64(dropped) - mgr.stats["hub new"] += uint64(len(r.Progs) - dropped) - mgr.stats["hub sent repros"] += uint64(len(a.Repros)) - mgr.stats["hub recv repros"] += uint64(len(r.Repros) - reproDropped) - log.Logf(0, "hub sync: send: add %v, del %v, repros %v; recv: progs: drop %v, new %v,"+ - " repros: drop: %v, new %v; more %v", - len(a.Add), len(a.Del), len(a.Repros), dropped, len(r.Progs)-dropped, - reproDropped, len(r.Repros)-reproDropped, r.More) - if len(r.Progs)+r.More == 0 { - break + } else if mgr.phase == phaseQueriedHub { + mgr.phase = phaseTriagedHub } - a.Add = nil - a.Del = nil } + log.Logf(4, "poll from %v: candidates=%v inputs=%v maxsignal=%v", + a.Name, len(r.Candidates), len(r.NewInputs), len(r.MaxSignal.Elems)) + return nil } func (mgr *Manager) collectUsedFiles() { @@ -1235,8 +1119,8 @@ func (mgr *Manager) dashboardReporter() { mgr.mu.Unlock() continue } - crashes := mgr.stats["crashes"] - execs := mgr.stats["exec total"] + crashes := mgr.stats.crashes.get() + execs := mgr.stats.execTotal.get() req := &dashapi.ManagerStatsReq{ Name: mgr.cfg.Name, Addr: webAddr, diff --git a/syz-manager/stats.go b/syz-manager/stats.go new file mode 100644 index 000000000..c6098b832 --- /dev/null +++ b/syz-manager/stats.go @@ -0,0 +1,56 @@ +// Copyright 2018 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 ( + "sync/atomic" +) + +type Stat uint64 + +type Stats struct { + crashes Stat + crashTypes Stat + crashSuppressed Stat + vmRestarts Stat + newInputs Stat + execTotal Stat + hubSendProgAdd Stat + hubSendProgDel Stat + hubSendRepro Stat + hubRecvProg Stat + hubRecvProgDrop Stat + hubRecvRepro Stat + hubRecvReproDrop Stat +} + +func (stats *Stats) all() map[string]uint64 { + return map[string]uint64{ + "crashes": stats.crashes.get(), + "crash types": stats.crashTypes.get(), + "suppressed": stats.crashSuppressed.get(), + "vm restarts": stats.vmRestarts.get(), + "manager new inputs": stats.newInputs.get(), + "exec total": stats.execTotal.get(), + "hub: send prog add": stats.hubSendProgAdd.get(), + "hub: send prog del": stats.hubSendProgDel.get(), + "hub: send repro": stats.hubSendRepro.get(), + "hub: recv prog": stats.hubRecvProg.get(), + "hub: recv prog drop": stats.hubRecvProgDrop.get(), + "hub: recv repro": stats.hubRecvRepro.get(), + "hub: recv repro drop": stats.hubRecvReproDrop.get(), + } +} + +func (s *Stat) get() uint64 { + return atomic.LoadUint64((*uint64)(s)) +} + +func (s *Stat) inc() { + s.add(1) +} + +func (s *Stat) add(v int) { + atomic.AddUint64((*uint64)(s), uint64(v)) +} |
