diff options
| -rw-r--r-- | Makefile | 22 | ||||
| -rw-r--r-- | master/html.go | 95 | ||||
| -rw-r--r-- | master/master.go | 170 | ||||
| -rw-r--r-- | syz-fuzzer/fuzzer.go (renamed from fuzzer/fuzzer.go) | 0 | ||||
| -rw-r--r-- | syz-manager/cover.go (renamed from manager/cover.go) | 0 | ||||
| -rw-r--r-- | syz-manager/example.cfg (renamed from manager/example.cfg) | 5 | ||||
| -rw-r--r-- | syz-manager/html.go (renamed from manager/html.go) | 55 | ||||
| -rw-r--r-- | syz-manager/main.go (renamed from manager/main.go) | 8 | ||||
| -rw-r--r-- | syz-manager/manager.go (renamed from manager/manager.go) | 147 | ||||
| -rw-r--r-- | syz-manager/persistent.go (renamed from master/persistent.go) | 0 | ||||
| -rw-r--r-- | tools/syz-execprog/execprog.go (renamed from tools/execprog/execprog.go) | 0 | ||||
| -rw-r--r-- | tools/syz-mutate/mutate.go (renamed from tools/mutate/mutate.go) | 0 | ||||
| -rw-r--r-- | tools/syz-prog2c/prog2c.go (renamed from tools/prog2c/prog2c.go) | 0 | ||||
| -rw-r--r-- | tools/syz-stress/stress.go (renamed from tools/stress/stress.go) | 0 | ||||
| -rw-r--r-- | vm/qemu/qemu.go | 6 |
15 files changed, 69 insertions, 439 deletions
@@ -1,24 +1,18 @@ # 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. -.PHONY: all bin format clean master manager fuzzer executor +.PHONY: all format clean master manager fuzzer executor -all: master manager fuzzer executor +all: manager fuzzer executor -bin: - mkdir -p bin +manager: + go build -o ./bin/syz-manager github.com/google/syzkaller/syz-manager -master: bin - go build -o ./bin/master github.com/google/syzkaller/master +fuzzer: + go build -o ./bin/syz-fuzzer github.com/google/syzkaller/syz-fuzzer -manager: bin - go build -o ./bin/manager github.com/google/syzkaller/manager - -fuzzer: bin - go build -o ./bin/fuzzer github.com/google/syzkaller/fuzzer - -executor: bin - gcc executor/executor.cc -o ./bin/executor -lpthread -static -Wall -O1 -g +executor: + gcc -o ./bin/syz-executor executor/executor.cc -lpthread -static -Wall -O1 -g format: go fmt ./... diff --git a/master/html.go b/master/html.go deleted file mode 100644 index 40159d156..000000000 --- a/master/html.go +++ /dev/null @@ -1,95 +0,0 @@ -// 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 ( - "encoding/json" - "fmt" - "html/template" - "io/ioutil" - "net/http" -) - -func (m *Master) httpInfo(w http.ResponseWriter, r *http.Request) { - m.mu.Lock() - defer m.mu.Unlock() - - data := &UIData{ - CorpusLen: len(m.corpus.m), - } - for _, mgr := range m.managers { - data.Managers = append(data.Managers, UIManager{ - Name: mgr.name, - Http: mgr.http, - }) - } - if err := htmlTemplate.Execute(w, data); err != nil { - http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError) - } -} - -func (m *Master) httpMinimize(w http.ResponseWriter, r *http.Request) { - corpus := make(map[string]bool) - for _, mgr := range m.managers { - resp, err := http.Get("http://" + mgr.http + "/current_corpus") - if err != nil { - http.Error(w, fmt.Sprintf("failed to query corpus from %v: %v", mgr.name, err), http.StatusInternalServerError) - return - } - defer resp.Body.Close() - data, err := ioutil.ReadAll(resp.Body) - if err != nil { - http.Error(w, fmt.Sprintf("failed to query corpus from %v: %v", mgr.name, err), http.StatusInternalServerError) - return - } - var hashes []string - err = json.Unmarshal(data, &hashes) - if err != nil || len(hashes) == 0 { - http.Error(w, fmt.Sprintf("failed to parse corpus from %v: %v", mgr.name, err), http.StatusInternalServerError) - return - } - for _, hash := range hashes { - corpus[hash] = true - } - } - - m.mu.Lock() - defer m.mu.Unlock() - - orig := len(m.corpus.m) - m.corpus.minimize(corpus) - fmt.Printf("minimized: %v -> %v -> %v\n", orig, len(corpus), len(m.corpus.m)) - for _, mgr := range m.managers { - mgr.input = 0 - } -} - -type UIData struct { - CorpusLen int - Managers []UIManager -} - -type UIManager struct { - Name string - Http string -} - -var htmlTemplate = template.Must(template.New("").Parse(` -<!doctype html> -<html> -<head> - <title>syzkaller master</title> -</head> -<body> -Corpus: {{.CorpusLen}} <br> -{{if .Managers}} - Managers:<br> - {{range $mgr := $.Managers}} - <a href='http://{{$mgr.Http}}'>{{$mgr.Name}}</a><br> - {{end}} -{{else}} - No managers connected<br> -{{end}} -</body></html> -`)) diff --git a/master/master.go b/master/master.go deleted file mode 100644 index c1a427732..000000000 --- a/master/master.go +++ /dev/null @@ -1,170 +0,0 @@ -// 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" - "log" - "net" - "net/http" - "net/rpc" - "path/filepath" - "sync" - "time" - - "github.com/google/syzkaller/prog" - . "github.com/google/syzkaller/rpctype" -) - -var ( - flagWorkdir = flag.String("workdir", "", "dir with persistent artifacts") - flagAddr = flag.String("addr", "", "RPC listen address to connect managers") - flagHTTP = flag.String("http", "", "HTTP server listen address") - flagV = flag.Int("v", 0, "verbosity") -) - -// Master manages persistent fuzzer state (input corpus and crashers). -type Master struct { - mu sync.Mutex - managers map[string]*Manager - corpus *PersistentSet - crashers *PersistentSet - startTime time.Time - lastInput time.Time -} - -type Manager struct { - name string - http string - input int -} - -func main() { - flag.Parse() - if *flagWorkdir == "" { - fatalf("-workdir is not set") - } - if *flagAddr == "" { - fatalf("-addr is not set") - } - if *flagHTTP == "" { - fatalf("-http is not set") - } - ln, err := net.Listen("tcp", *flagAddr) - if err != nil { - fatalf("failed to listen: %v", err) - } - - m := &Master{} - m.managers = make(map[string]*Manager) - m.startTime = time.Now() - m.lastInput = time.Now() - logf(0, "loading corpus...") - m.corpus = newPersistentSet(filepath.Join(*flagWorkdir, "corpus"), func(data []byte) bool { - if _, err := prog.Deserialize(data); err != nil { - logf(0, "deleting broken program: %v\n%s", err, data) - return false - } - return true - }) - m.crashers = newPersistentSet(filepath.Join(*flagWorkdir, "crashers"), nil) - - http.HandleFunc("/", m.httpInfo) - http.HandleFunc("/minimize", m.httpMinimize) - go func() { - logf(0, "serving http on http://%v", *flagHTTP) - panic(http.ListenAndServe(*flagHTTP, nil)) - }() - - logf(0, "serving rpc on tcp://%v", *flagAddr) - s := rpc.NewServer() - s.Register(m) - go s.Accept(ln) - - m.loop() -} - -func (m *Master) loop() { - for range time.NewTicker(1 * time.Second).C { - } -} - -// Connect attaches new manager to master. -func (m *Master) Connect(a *MasterConnectArgs, r *MasterConnectRes) error { - logf(1, "connect from %v (http://%v)", a.Name, a.Http) - m.mu.Lock() - defer m.mu.Unlock() - - mgr := &Manager{ - name: a.Name, - http: a.Http, - } - m.managers[a.Name] = mgr - r.Http = *flagHTTP - return nil -} - -// NewInput saves new interesting input on master. -func (m *Master) NewInput(a *NewMasterInputArgs, r *int) error { - p, err := prog.Deserialize(a.Prog) - if err != nil { - logf(0, "bogus new input from %v: %v\n%s\n", a.Name, err, a.Prog) - return fmt.Errorf("the program is bogus: %v", err) - } - m.mu.Lock() - defer m.mu.Unlock() - - if !m.corpus.add(a.Prog) { - return nil - } - m.lastInput = time.Now() - logf(1, "new input from %v: %s", a.Name, p) - return nil -} - -type NewCrasherArgs struct { - Name string - Text []byte - Suppression []byte - Prog []byte -} - -// NewCrasher saves new crasher input on master. -func (m *Master) NewCrasher(a *NewCrasherArgs, r *int) error { - logf(0, "new crasher from %v", a.Name) - m.mu.Lock() - defer m.mu.Unlock() - - if !m.crashers.add(a.Text) { - return nil // Already have this. - } - return nil -} - -func (m *Master) PollInputs(a *MasterPollArgs, r *MasterPollRes) error { - logf(2, "poll from %v", a.Name) - m.mu.Lock() - defer m.mu.Unlock() - - mgr := m.managers[a.Name] - if mgr == nil { - return fmt.Errorf("manager is not connected") - } - for i := 0; i < 100 && mgr.input < len(m.corpus.a); i++ { - r.Inputs = append(r.Inputs, m.corpus.a[mgr.input]) - mgr.input++ - } - return nil -} - -func logf(v int, msg string, args ...interface{}) { - if *flagV >= v { - log.Printf(msg, args...) - } -} - -func fatalf(msg string, args ...interface{}) { - log.Fatalf(msg, args...) -} diff --git a/fuzzer/fuzzer.go b/syz-fuzzer/fuzzer.go index 34d99499f..34d99499f 100644 --- a/fuzzer/fuzzer.go +++ b/syz-fuzzer/fuzzer.go diff --git a/manager/cover.go b/syz-manager/cover.go index 190fb6038..190fb6038 100644 --- a/manager/cover.go +++ b/syz-manager/cover.go diff --git a/manager/example.cfg b/syz-manager/example.cfg index 216e09eeb..a9095ef18 100644 --- a/manager/example.cfg +++ b/syz-manager/example.cfg @@ -1,7 +1,5 @@ { - "name": "my-qemu-asan", "http": "myhost.com:56741", - "master": "myhost.com:48342", "workdir": "/syzkaller/manager/workdir", "vmlinux": "/linux/vmlinux", "type": "qemu", @@ -21,5 +19,8 @@ "keyctl", "add_key", "request_key" + ], + "suppressions": [ + "some known bug" ] } diff --git a/manager/html.go b/syz-manager/html.go index 6123160d9..0d7e87687 100644 --- a/manager/html.go +++ b/syz-manager/html.go @@ -4,8 +4,6 @@ package main import ( - "encoding/hex" - "encoding/json" "fmt" "html/template" "net/http" @@ -23,11 +21,8 @@ func (mgr *Manager) initHttp() { http.HandleFunc("/corpus", mgr.httpCorpus) http.HandleFunc("/cover", mgr.httpCover) http.HandleFunc("/prio", mgr.httpPrio) - http.HandleFunc("/current_corpus", mgr.httpCurrentCorpus) - go func() { - logf(0, "serving http on http://%v", mgr.cfg.Http) - panic(http.ListenAndServe(mgr.cfg.Http, nil)) - }() + logf(0, "serving http on http://%v", mgr.cfg.Http) + go http.ListenAndServe(mgr.cfg.Http, nil) } func (mgr *Manager) httpInfo(w http.ResponseWriter, r *http.Request) { @@ -50,12 +45,9 @@ func (mgr *Manager) httpInfo(w http.ResponseWriter, r *http.Request) { uptime := time.Since(mgr.startTime) data := &UIData{ - Name: mgr.cfg.Name, - MasterHttp: mgr.masterHttp, - MasterCorpusSize: len(mgr.masterCorpus), - CorpusSize: len(mgr.corpus), - TriageQueue: len(mgr.candidates), - Uptime: fmt.Sprintf("%v", uptime), + CorpusSize: len(mgr.corpus), + TriageQueue: len(mgr.candidates), + Uptime: fmt.Sprintf("%v", uptime), } secs := uint64(uptime) / 1e9 @@ -164,34 +156,13 @@ func (mgr *Manager) httpPrio(w http.ResponseWriter, r *http.Request) { } } -func (mgr *Manager) httpCurrentCorpus(w http.ResponseWriter, r *http.Request) { - mgr.mu.Lock() - defer mgr.mu.Unlock() - - mgr.minimizeCorpus() - var hashes []string - for _, inp := range mgr.corpus { - hash := hash(inp.Prog) - hashes = append(hashes, hex.EncodeToString(hash[:])) - } - data, err := json.Marshal(&hashes) - if err != nil { - http.Error(w, fmt.Sprintf("failed to marshal corpus: %v", err), http.StatusInternalServerError) - return - } - w.Write(data) -} - type UIData struct { - Name string - MasterHttp string - MasterCorpusSize int - CorpusSize int - TriageQueue int - CoverSize int - Uptime string - Stats []UIStat - Calls []UICallType + CorpusSize int + TriageQueue int + CoverSize int + Uptime string + Stats []UIStat + Calls []UICallType } type UIStat struct { @@ -235,12 +206,10 @@ var htmlTemplate = template.Must(template.New("").Parse(` <!doctype html> <html> <head> - <title>syzkaller {{.Name}}</title> + <title>syzkaller</title> </head> <body> -Manager: {{.Name}} <a href='http://{{.MasterHttp}}'>[master]</a> <br> Uptime: {{.Uptime}}<br> -Master corpus: {{.MasterCorpusSize}} <br> Corpus: {{.CorpusSize}}<br> Triage queue len: {{.TriageQueue}}<br> <a href='/cover'>Cover: {{.CoverSize}}</a> <br> diff --git a/manager/main.go b/syz-manager/main.go index 9a3523cb8..acbba0967 100644 --- a/manager/main.go +++ b/syz-manager/main.go @@ -26,9 +26,7 @@ var ( ) type Config struct { - Name string Http string - Master string Workdir string Vmlinux string Type string @@ -110,15 +108,9 @@ func parseConfig() (*Config, map[int]bool) { if err := json.Unmarshal(data, cfg); err != nil { fatalf("failed to parse config file: %v", err) } - if cfg.Name == "" { - fatalf("config param name is empty") - } if cfg.Http == "" { fatalf("config param http is empty") } - if cfg.Master == "" { - fatalf("config param master is empty") - } if cfg.Workdir == "" { fatalf("config param workdir is empty") } diff --git a/manager/manager.go b/syz-manager/manager.go index ab40bbe0d..44510d7e1 100644 --- a/manager/manager.go +++ b/syz-manager/manager.go @@ -4,10 +4,10 @@ package main import ( - "crypto/sha1" "fmt" "net" "net/rpc" + "path/filepath" "sync" "time" @@ -18,26 +18,17 @@ import ( "github.com/google/syzkaller/vm" ) -type Sig [sha1.Size]byte - -func hash(data []byte) Sig { - return Sig(sha1.Sum(data)) -} - type Manager struct { - cfg *Config - master *rpc.Client - masterHttp string - instances []vm.Instance - startTime time.Time - stats map[string]uint64 - - mu sync.Mutex - masterCorpus [][]byte // mirror of master corpus - masterHashes map[Sig]struct{} // hashes of master corpus - candidates [][]byte // new untriaged inputs from master - syscalls map[int]bool + cfg *Config + persistentCorpus *PersistentSet + instances []vm.Instance + startTime time.Time + stats map[string]uint64 + mu sync.Mutex + syscalls map[int]bool + + candidates [][]byte // untriaged inputs corpus []RpcInput corpusCover []cover.Cover prios [][]float32 @@ -51,30 +42,28 @@ type Fuzzer struct { } func RunManager(cfg *Config, syscalls map[int]bool, instances []vm.Instance) { - // Connect to master. - master, err := rpc.Dial("tcp", cfg.Master) - if err != nil { - fatalf("failed to dial mastger: %v", err) - } - a := &MasterConnectArgs{cfg.Name, cfg.Http} - r := &MasterConnectRes{} - if err := master.Call("Master.Connect", a, r); err != nil { - fatalf("failed to connect to master: %v", err) - } - logf(0, "connected to master at %v", cfg.Master) - mgr := &Manager{ - cfg: cfg, - master: master, - masterHttp: r.Http, - startTime: time.Now(), - stats: make(map[string]uint64), - instances: instances, - masterHashes: make(map[Sig]struct{}), - syscalls: syscalls, - corpusCover: make([]cover.Cover, sys.CallCount), - fuzzers: make(map[string]*Fuzzer), + cfg: cfg, + startTime: time.Now(), + stats: make(map[string]uint64), + instances: instances, + syscalls: syscalls, + corpusCover: make([]cover.Cover, sys.CallCount), + fuzzers: make(map[string]*Fuzzer), + } + + logf(0, "loading corpus...") + mgr.persistentCorpus = newPersistentSet(filepath.Join(cfg.Workdir, "corpus"), func(data []byte) bool { + if _, err := prog.Deserialize(data); err != nil { + logf(0, "deleting broken program: %v\n%s", err, data) + return false + } + return true + }) + for _, prog := range mgr.persistentCorpus.a { + mgr.candidates = append(mgr.candidates, prog) } + logf(0, "loaded %v programs", len(mgr.persistentCorpus.m)) // Create HTTP server. mgr.initHttp() @@ -85,64 +74,15 @@ func RunManager(cfg *Config, syscalls map[int]bool, instances []vm.Instance) { if err != nil { fatalf("failed to listen on port %v: %v", cfg.Port, err) } + logf(0, "serving rpc on tcp://%v", rpcAddr) s := rpc.NewServer() s.Register(mgr) go s.Accept(ln) - logf(0, "serving rpc on tcp://%v", rpcAddr) - - mgr.run() -} -func (mgr *Manager) run() { - mgr.pollMaster() for _, inst := range mgr.instances { go inst.Run() } - pollTicker := time.NewTicker(10 * time.Second).C - for { - select { - case <-pollTicker: - mgr.mu.Lock() - mgr.pollMaster() - mgr.mu.Unlock() - } - } -} - -func (mgr *Manager) pollMaster() { - for { - a := &MasterPollArgs{mgr.cfg.Name} - r := &MasterPollRes{} - if err := mgr.master.Call("Master.PollInputs", a, r); err != nil { - fatalf("failed to poll master: %v", err) - } - logf(3, "polling master, got %v inputs", len(r.Inputs)) - if len(r.Inputs) == 0 { - break - } - nextProg: - for _, prg := range r.Inputs { - p, err := prog.Deserialize(prg) - if err != nil { - logf(0, "failed to deserialize master program: %v", err) - continue - } - if mgr.syscalls != nil { - for _, c := range p.Calls { - if !mgr.syscalls[c.Meta.ID] { - continue nextProg - } - } - } - sig := hash(prg) - if _, ok := mgr.masterHashes[sig]; ok { - continue - } - mgr.masterHashes[sig] = struct{}{} - mgr.masterCorpus = append(mgr.masterCorpus, prg) - mgr.candidates = append(mgr.candidates, prg) - } - } + select {} } func (mgr *Manager) minimizeCorpus() { @@ -178,6 +118,16 @@ func (mgr *Manager) minimizeCorpus() { corpus = append(corpus, p) } mgr.prios = prog.CalculatePriorities(corpus) + + // Don't minimize persistent corpus until fuzzers have triaged all inputs from it. + if len(mgr.candidates) == 0 { + hashes := make(map[string]bool) + for _, inp := range mgr.corpus { + h := hash(inp.Prog) + hashes[string(h[:])] = true + } + mgr.persistentCorpus.minimize(hashes) + } } func (mgr *Manager) Connect(a *ManagerConnectArgs, r *ManagerConnectRes) error { @@ -208,18 +158,7 @@ func (mgr *Manager) NewInput(a *NewManagerInputArgs, r *int) error { mgr.corpusCover[call] = cover.Union(mgr.corpusCover[call], a.Cover) mgr.corpus = append(mgr.corpus, a.RpcInput) mgr.stats["manager new inputs"]++ - - sig := hash(a.Prog) - if _, ok := mgr.masterHashes[sig]; !ok { - mgr.masterHashes[sig] = struct{}{} - mgr.masterCorpus = append(mgr.masterCorpus, a.Prog) - - a1 := &NewMasterInputArgs{mgr.cfg.Name, a.Prog} - if err := mgr.master.Call("Master.NewInput", a1, nil); err != nil { - fatalf("call Master.NewInput failed: %v", err) - } - } - + mgr.persistentCorpus.add(a.RpcInput.Prog) return nil } diff --git a/master/persistent.go b/syz-manager/persistent.go index 12f4bbdc8..12f4bbdc8 100644 --- a/master/persistent.go +++ b/syz-manager/persistent.go diff --git a/tools/execprog/execprog.go b/tools/syz-execprog/execprog.go index dcb2a0b8b..dcb2a0b8b 100644 --- a/tools/execprog/execprog.go +++ b/tools/syz-execprog/execprog.go diff --git a/tools/mutate/mutate.go b/tools/syz-mutate/mutate.go index 353d60390..353d60390 100644 --- a/tools/mutate/mutate.go +++ b/tools/syz-mutate/mutate.go diff --git a/tools/prog2c/prog2c.go b/tools/syz-prog2c/prog2c.go index 9b8e58efa..9b8e58efa 100644 --- a/tools/prog2c/prog2c.go +++ b/tools/syz-prog2c/prog2c.go diff --git a/tools/stress/stress.go b/tools/syz-stress/stress.go index e4f1a506c..e4f1a506c 100644 --- a/tools/stress/stress.go +++ b/tools/syz-stress/stress.go diff --git a/vm/qemu/qemu.go b/vm/qemu/qemu.go index d00e2c050..bb560accb 100644 --- a/vm/qemu/qemu.go +++ b/vm/qemu/qemu.go @@ -258,8 +258,8 @@ func (inst *Instance) Run() { inst.Logf("started vm") // Copy the binaries into the instance. - if !inst.CreateSCPCommand(inst.Fuzzer, "/syzkaller_fuzzer").Wait(1*time.Minute) || - !inst.CreateSCPCommand(inst.Executor, "/syzkaller_executor").Wait(1*time.Minute) { + if !inst.CreateSCPCommand(inst.Fuzzer, "/syz-fuzzer").Wait(1*time.Minute) || + !inst.CreateSCPCommand(inst.Executor, "/syz-executor").Wait(1*time.Minute) { outputMu.Lock() output = append(output, "\nfailed to scp binaries into the instance\n"...) inst.SaveCrasher(output) @@ -280,7 +280,7 @@ func (inst *Instance) Run() { if inst.cfg.NoDropPrivs { dropprivs = "-dropprivs=0" } - cmd := inst.CreateSSHCommand(fmt.Sprintf("/syzkaller_fuzzer -name %v -executor /syzkaller_executor -manager %v:%v -procs %v -leak=%v %v %v %v", + cmd := inst.CreateSSHCommand(fmt.Sprintf("/syz-fuzzer -name %v -executor /syz-executor -manager %v:%v -procs %v -leak=%v %v %v %v", inst.name, hostAddr, inst.cfg.ManagerPort, inst.cfg.Procs, inst.cfg.Leak, cover, dropprivs, inst.callsFlag)) deadline := start.Add(time.Hour) |
