aboutsummaryrefslogtreecommitdiffstats
path: root/manager
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2015-10-12 10:16:57 +0200
committerDmitry Vyukov <dvyukov@google.com>2015-10-12 10:16:57 +0200
commit874c5754bb22dbf77d6b600ff91f0f4f1fc5073a (patch)
tree0075fbd088046ad5c86e6e972235701d68b3ce7c /manager
initial commit
Diffstat (limited to 'manager')
-rw-r--r--manager/cover.go286
-rw-r--r--manager/example.cfg25
-rw-r--r--manager/html.go188
-rw-r--r--manager/main.go139
-rw-r--r--manager/manager.go238
5 files changed, 876 insertions, 0 deletions
diff --git a/manager/cover.go b/manager/cover.go
new file mode 100644
index 000000000..accc230f9
--- /dev/null
+++ b/manager/cover.go
@@ -0,0 +1,286 @@
+// 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 (
+ "bufio"
+ "bytes"
+ "fmt"
+ "html/template"
+ "io"
+ "io/ioutil"
+ "os/exec"
+ "sort"
+ "strconv"
+ "strings"
+ "sync"
+)
+
+type LineInfo struct {
+ file string
+ line int
+}
+
+var (
+ mu sync.Mutex
+ pcLines = make(map[uint32][]LineInfo)
+ parsedFiles = make(map[string][][]byte)
+ htmlReplacer = strings.NewReplacer(">", "&gt;", "<", "&lt;", "&", "&amp;", "\t", " ")
+ sourcePrefix string
+)
+
+func generateCoverHtml(w io.Writer, vmlinux string, cov []uint32) error {
+ mu.Lock()
+ defer mu.Unlock()
+
+ info, err := covToLineInfo(vmlinux, cov)
+ if err != nil {
+ return err
+ }
+ files := fileSet(info)
+ for f := range files {
+ if _, ok := parsedFiles[f]; ok {
+ continue
+ }
+ if err := parseFile(f); err != nil {
+ return err
+ }
+ }
+
+ var d templateData
+ for f, covered := range files {
+ lines := parsedFiles[f]
+ coverage := len(covered)
+ var buf bytes.Buffer
+ for i, ln := range lines {
+ if len(covered) > 0 && covered[0] == i+1 {
+ buf.Write([]byte("<span id='covered'>"))
+ buf.Write(ln)
+ buf.Write([]byte("</span>\n"))
+ covered = covered[1:]
+ } else {
+ buf.Write(ln)
+ buf.Write([]byte("\n"))
+ }
+ }
+ stripped := f
+ if len(stripped) > len(sourcePrefix) {
+ stripped = stripped[len(sourcePrefix):]
+ }
+ d.Files = append(d.Files, &templateFile{
+ Name: stripped,
+ Body: template.HTML(buf.String()),
+ Coverage: coverage,
+ })
+ }
+
+ sort.Sort(templateFileArray(d.Files))
+ if err := coverTemplate.Execute(w, d); err != nil {
+ return err
+ }
+ return nil
+}
+
+func covToLineInfo(vmlinux string, cov []uint32) ([]LineInfo, error) {
+ var missing []uint32
+ for _, pc := range cov {
+ if _, ok := pcLines[pc]; !ok {
+ missing = append(missing, pc)
+ }
+ }
+ if len(missing) > 0 {
+ if err := symbolize(vmlinux, missing); err != nil {
+ return nil, err
+ }
+ }
+ var info []LineInfo
+ for _, pc := range cov {
+ info = append(info, pcLines[pc]...)
+ }
+ return info, nil
+}
+
+func fileSet(info []LineInfo) map[string][]int {
+ files := make(map[string]map[int]struct{})
+ for _, li := range info {
+ if files[li.file] == nil {
+ files[li.file] = make(map[int]struct{})
+ }
+ files[li.file][li.line] = struct{}{}
+ }
+ res := make(map[string][]int)
+ for f, lines := range files {
+ sorted := make([]int, 0, len(lines))
+ for ln := range lines {
+ sorted = append(sorted, ln)
+ }
+ sort.Ints(sorted)
+ res[f] = sorted
+ }
+ return res
+}
+
+func parseFile(fn string) error {
+ data, err := ioutil.ReadFile(fn)
+ if err != nil {
+ return err
+ }
+ var lines [][]byte
+ for {
+ idx := bytes.IndexByte(data, '\n')
+ if idx == -1 {
+ break
+ }
+ lines = append(lines, []byte(htmlReplacer.Replace(string(data[:idx]))))
+ data = data[idx+1:]
+ }
+ if len(data) != 0 {
+ lines = append(lines, data)
+ }
+ parsedFiles[fn] = lines
+ if sourcePrefix == "" {
+ sourcePrefix = fn
+ } else {
+ i := 0
+ for ; i < len(sourcePrefix) && i < len(fn); i++ {
+ if sourcePrefix[i] != fn[i] {
+ break
+ }
+ }
+ sourcePrefix = sourcePrefix[:i]
+ }
+ return nil
+}
+
+func symbolize(vmlinux string, cov []uint32) error {
+ cmd := exec.Command("addr2line", "-a", "-i", "-e", vmlinux)
+ stdin, err := cmd.StdinPipe()
+ if err != nil {
+ return err
+ }
+ defer stdin.Close()
+ stdout, err := cmd.StdoutPipe()
+ if err != nil {
+ return err
+ }
+ defer stdout.Close()
+ if err := cmd.Start(); err != nil {
+ return err
+ }
+ defer cmd.Wait()
+ go func() {
+ for _, pc := range cov {
+ fmt.Fprintf(stdin, "0xffffffff%x\n", pc-1)
+ }
+ stdin.Close()
+ }()
+ s := bufio.NewScanner(stdout)
+ var pc uint32
+ for s.Scan() {
+ ln := s.Text()
+ if len(ln) > 3 && ln[0] == '0' && ln[1] == 'x' {
+ v, err := strconv.ParseUint(ln, 0, 64)
+ if err != nil {
+ return fmt.Errorf("failed to parse pc in addr2line output: %v", err)
+ }
+ pc = uint32(v) + 1
+ continue
+ }
+ colon := strings.IndexByte(ln, ':')
+ if colon == -1 {
+ continue
+ }
+ file := ln[:colon]
+ line, err := strconv.Atoi(ln[colon+1:])
+ if err != nil || pc == 0 || file == "" || file == "??" || line <= 0 {
+ continue
+ }
+ pcLines[pc] = append(pcLines[pc], LineInfo{file, line})
+ }
+ if err := s.Err(); err != nil {
+ return err
+ }
+ return nil
+}
+
+type templateData struct {
+ Files []*templateFile
+}
+
+type templateFile struct {
+ Name string
+ Body template.HTML
+ Coverage int
+}
+
+type templateFileArray []*templateFile
+
+func (a templateFileArray) Len() int { return len(a) }
+func (a templateFileArray) Less(i, j int) bool { return a[i].Name < a[j].Name }
+func (a templateFileArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+
+var coverTemplate = template.Must(template.New("").Parse(
+ `
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <style>
+ body {
+ background: white;
+ }
+ #topbar {
+ background: black;
+ position: fixed;
+ top: 0; left: 0; right: 0;
+ height: 42px;
+ border-bottom: 1px solid rgb(70, 70, 70);
+ }
+ #nav {
+ float: left;
+ margin-left: 10px;
+ margin-top: 10px;
+ }
+ #content {
+ font-family: 'Courier New', Courier, monospace;
+ color: rgb(70, 70, 70);
+ margin-top: 50px;
+ }
+ #covered {
+ color: rgb(0, 0, 0);
+ font-weight: bold;
+ }
+ </style>
+ </head>
+ <body>
+ <div id="topbar">
+ <div id="nav">
+ <select id="files">
+ {{range $i, $f := .Files}}
+ <option value="file{{$i}}">{{$f.Name}} ({{$f.Coverage}})</option>
+ {{end}}
+ </select>
+ </div>
+ </div>
+ <div id="content">
+ {{range $i, $f := .Files}}
+ <pre class="file" id="file{{$i}}" {{if $i}}style="display: none"{{end}}>{{$f.Body}}</pre>
+ {{end}}
+ </div>
+ </body>
+ <script>
+ (function() {
+ var files = document.getElementById('files');
+ var visible = document.getElementById('file0');
+ files.addEventListener('change', onChange, false);
+ function onChange() {
+ visible.style.display = 'none';
+ visible = document.getElementById(files.value);
+ visible.style.display = 'block';
+ window.scrollTo(0, 0);
+ }
+ })();
+ </script>
+</html>
+`))
diff --git a/manager/example.cfg b/manager/example.cfg
new file mode 100644
index 000000000..216e09eeb
--- /dev/null
+++ b/manager/example.cfg
@@ -0,0 +1,25 @@
+{
+ "name": "my-qemu-asan",
+ "http": "myhost.com:56741",
+ "master": "myhost.com:48342",
+ "workdir": "/syzkaller/manager/workdir",
+ "vmlinux": "/linux/vmlinux",
+ "type": "qemu",
+ "count": 16,
+ "port": 23504,
+ "params": {
+ "kernel": "/linux/arch/x86/boot/bzImage",
+ "image": "/linux_image/wheezy.img",
+ "sshkey": "/linux_image/ssh/id_rsa",
+ "fuzzer": "/syzkaller/fuzzer/fuzzer",
+ "executor": "/syzkaller/executor/executor",
+ "port": 23505,
+ "cpu": 2,
+ "mem": 2048
+ },
+ "disable_syscalls": [
+ "keyctl",
+ "add_key",
+ "request_key"
+ ]
+}
diff --git a/manager/html.go b/manager/html.go
new file mode 100644
index 000000000..242492e38
--- /dev/null
+++ b/manager/html.go
@@ -0,0 +1,188 @@
+// 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/hex"
+ "encoding/json"
+ "fmt"
+ "html/template"
+ "net/http"
+ "sort"
+ "strconv"
+
+ "github.com/google/syzkaller/cover"
+ "github.com/google/syzkaller/prog"
+)
+
+func (mgr *Manager) httpInfo(w http.ResponseWriter, r *http.Request) {
+ mgr.mu.Lock()
+ defer mgr.mu.Unlock()
+
+ type CallCov struct {
+ count int
+ cov cover.Cover
+ }
+ calls := make(map[string]*CallCov)
+ for _, inp := range mgr.corpus {
+ if calls[inp.Call] == nil {
+ calls[inp.Call] = new(CallCov)
+ }
+ cc := calls[inp.Call]
+ cc.count++
+ cc.cov = cover.Union(cc.cov, cover.Cover(inp.Cover))
+ }
+
+ data := &UIData{
+ Name: mgr.cfg.Name,
+ MasterHttp: mgr.masterHttp,
+ MasterCorpusSize: len(mgr.masterCorpus),
+ CorpusSize: len(mgr.corpus),
+ }
+
+ var cov cover.Cover
+ for c, cc := range calls {
+ cov = cover.Union(cov, cc.cov)
+ data.Calls = append(data.Calls, UICallType{c, cc.count, len(cc.cov)})
+ }
+ sort.Sort(UICallTypeArray(data.Calls))
+ data.CoverSize = len(cov)
+
+ if err := htmlTemplate.Execute(w, data); err != nil {
+ http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError)
+ }
+}
+
+func (mgr *Manager) httpCorpus(w http.ResponseWriter, r *http.Request) {
+ mgr.mu.Lock()
+ defer mgr.mu.Unlock()
+
+ var data []UIInput
+ call := r.FormValue("call")
+ for i, inp := range mgr.corpus {
+ if call != inp.Call {
+ continue
+ }
+ p, err := prog.Deserialize(inp.Prog)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("failed to deserialize program: %v", err), http.StatusInternalServerError)
+ }
+ data = append(data, UIInput{
+ Short: p.String(),
+ Full: string(inp.Prog),
+ Cover: len(inp.Cover),
+ N: i,
+ })
+ }
+ sort.Sort(UIInputArray(data))
+
+ if err := corpusTemplate.Execute(w, data); err != nil {
+ http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError)
+ }
+}
+
+func (mgr *Manager) httpCover(w http.ResponseWriter, r *http.Request) {
+ mgr.mu.Lock()
+ defer mgr.mu.Unlock()
+
+ var cov cover.Cover
+ call := r.FormValue("call")
+ if n, err := strconv.Atoi(call); err == nil && n < len(mgr.corpus) {
+ cov = mgr.corpus[n].Cover
+ } else {
+ for _, inp := range mgr.corpus {
+ if call == "" || call == inp.Call {
+ cov = cover.Union(cov, cover.Cover(inp.Cover))
+ }
+ }
+ }
+
+ if err := generateCoverHtml(w, mgr.cfg.Vmlinux, cov); err != nil {
+ http.Error(w, fmt.Sprintf("failed to generate coverage profile: %v", err), http.StatusInternalServerError)
+ }
+}
+
+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
+ CoverSize int
+ Calls []UICallType
+}
+
+type UICallType struct {
+ Name string
+ Inputs int
+ Cover int
+}
+
+type UIInput struct {
+ Short string
+ Full string
+ Calls int
+ Cover int
+ N int
+}
+
+type UICallTypeArray []UICallType
+
+func (a UICallTypeArray) Len() int { return len(a) }
+func (a UICallTypeArray) Less(i, j int) bool { return a[i].Name < a[j].Name }
+func (a UICallTypeArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+
+type UIInputArray []UIInput
+
+func (a UIInputArray) Len() int { return len(a) }
+func (a UIInputArray) Less(i, j int) bool { return a[i].Cover > a[j].Cover }
+func (a UIInputArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+
+var htmlTemplate = template.Must(template.New("").Parse(`
+<!doctype html>
+<html>
+<head>
+ <title>syzkaller {{.Name}}</title>
+</head>
+<body>
+Manager: {{.Name}} <a href='http://{{.MasterHttp}}'>[master]</a> <br>
+Master corpus: {{.MasterCorpusSize}} <br>
+Corpus: {{.CorpusSize}}<br>
+<a href='/cover'>Cover: {{.CoverSize}}</a> <br>
+<br>
+{{range $c := $.Calls}}
+ {{$c.Name}} <a href='/corpus?call={{$c.Name}}'>inputs:{{$c.Inputs}}</a> <a href='/cover?call={{$c.Name}}'>cover:{{$c.Cover}}</a><br>
+{{end}}
+</body></html>
+`))
+
+var corpusTemplate = template.Must(template.New("").Parse(`
+<!doctype html>
+<html>
+<head>
+ <title>syzkaller corpus</title>
+</head>
+<body>
+{{range $c := $}}
+ <span title="{{$c.Full}}">{{$c.Short}}</span> <a href='/cover?call={{$c.N}}'>cover:{{$c.Cover}}</a> <br>
+{{end}}
+</body></html>
+`))
diff --git a/manager/main.go b/manager/main.go
new file mode 100644
index 000000000..f83338918
--- /dev/null
+++ b/manager/main.go
@@ -0,0 +1,139 @@
+// 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"
+ "flag"
+ "io/ioutil"
+ "log"
+
+ "github.com/google/syzkaller/sys"
+ "github.com/google/syzkaller/vm"
+ _ "github.com/google/syzkaller/vm/local"
+ _ "github.com/google/syzkaller/vm/qemu"
+)
+
+var (
+ flagConfig = flag.String("config", "", "configuration file")
+ flagV = flag.Int("v", 0, "verbosity")
+)
+
+type Config struct {
+ Name string
+ Http string
+ Master string
+ Workdir string
+ Vmlinux string
+ Type string
+ Count int
+ Port int
+ Nocover bool
+ Params map[string]interface{}
+ Enable_Syscalls []string
+ Disable_Syscalls []string
+}
+
+func main() {
+ flag.Parse()
+ cfg, syscalls := parseConfig()
+ var instances []vm.Instance
+ for i := 0; i < cfg.Count; i++ {
+ params, err := json.Marshal(cfg.Params)
+ if err != nil {
+ fatalf("failed to marshal config params: %v", err)
+ }
+ inst, err := vm.Create(cfg.Type, cfg.Workdir, syscalls, cfg.Port, i, params)
+ if err != nil {
+ fatalf("failed to create an instance: %v", err)
+ }
+ instances = append(instances, inst)
+ }
+ RunManager(cfg, syscalls, instances)
+}
+
+func parseConfig() (*Config, map[int]bool) {
+ if *flagConfig == "" {
+ fatalf("supply config file name in -config flag")
+ }
+ data, err := ioutil.ReadFile(*flagConfig)
+ if err != nil {
+ fatalf("failed to read config file: %v", err)
+ }
+ cfg := new(Config)
+ 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")
+ }
+ if cfg.Vmlinux == "" {
+ fatalf("config param vmlinux is empty")
+ }
+ if cfg.Type == "" {
+ fatalf("config param type is empty")
+ }
+ if cfg.Count <= 0 || cfg.Count > 1000 {
+ fatalf("invalid config param count: %v, want (1, 1000]", cfg.Count)
+ }
+
+ var syscalls map[int]bool
+ if len(cfg.Enable_Syscalls) != 0 || len(cfg.Disable_Syscalls) != 0 {
+ syscalls = make(map[int]bool)
+ if len(cfg.Enable_Syscalls) != 0 {
+ for _, c := range cfg.Enable_Syscalls {
+ n := 0
+ for _, call := range sys.Calls {
+ if call.CallName == c {
+ syscalls[call.ID] = true
+ n++
+ }
+ }
+ if n == 0 {
+ fatalf("unknown enabled syscall: %v", c)
+ }
+ }
+ } else {
+ for _, call := range sys.Calls {
+ syscalls[call.ID] = true
+ }
+ }
+ for _, c := range cfg.Disable_Syscalls {
+ n := 0
+ for _, call := range sys.Calls {
+ if call.CallName == c {
+ delete(syscalls, call.ID)
+ n++
+ }
+ }
+ if n == 0 {
+ fatalf("unknown disabled syscall: %v", c)
+ }
+ }
+ // They will be generated anyway.
+ syscalls[sys.CallMap["mmap"].ID] = true
+ syscalls[sys.CallMap["clock_gettime"].ID] = true
+ }
+
+ return cfg, syscalls
+}
+
+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/manager/manager.go b/manager/manager.go
new file mode 100644
index 000000000..0c6841db0
--- /dev/null
+++ b/manager/manager.go
@@ -0,0 +1,238 @@
+// 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 (
+ "crypto/sha1"
+ "fmt"
+ "net"
+ "net/http"
+ "net/rpc"
+ "sync"
+ "time"
+
+ "github.com/google/syzkaller/cover"
+ "github.com/google/syzkaller/prog"
+ . "github.com/google/syzkaller/rpctype"
+ "github.com/google/syzkaller/sys"
+ "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
+
+ 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
+
+ corpus []RpcInput
+ corpusCover []cover.Cover
+
+ fuzzers map[string]*Fuzzer
+}
+
+type Fuzzer struct {
+ name string
+ input int
+}
+
+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,
+ instances: instances,
+ masterHashes: make(map[Sig]struct{}),
+ syscalls: syscalls,
+ corpusCover: make([]cover.Cover, sys.CallCount),
+ fuzzers: make(map[string]*Fuzzer),
+ }
+
+ http.HandleFunc("/", mgr.httpInfo)
+ http.HandleFunc("/corpus", mgr.httpCorpus)
+ http.HandleFunc("/cover", mgr.httpCover)
+ http.HandleFunc("/current_corpus", mgr.httpCurrentCorpus)
+ go func() {
+ logf(0, "serving http on http://%v", cfg.Http)
+ panic(http.ListenAndServe(cfg.Http, nil))
+ }()
+
+ // Create RPC server for fuzzers.
+ rpcAddr := fmt.Sprintf("localhost:%v", cfg.Port)
+ ln, err := net.Listen("tcp", rpcAddr)
+ if err != nil {
+ fatalf("failed to listen on port %v: %v", cfg.Port, err)
+ }
+ 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)
+ }
+ }
+}
+
+func (mgr *Manager) minimizeCorpus() {
+ if len(mgr.corpus) == 0 {
+ return
+ }
+ // First, sort corpus per call.
+ type Call struct {
+ inputs []RpcInput
+ cov []cover.Cover
+ }
+ calls := make(map[string]Call)
+ for _, inp := range mgr.corpus {
+ c := calls[inp.Call]
+ c.inputs = append(c.inputs, inp)
+ c.cov = append(c.cov, inp.Cover)
+ calls[inp.Call] = c
+ }
+ // Now minimize and build new corpus.
+ var newCorpus []RpcInput
+ for _, c := range calls {
+ for _, idx := range cover.Minimize(c.cov) {
+ newCorpus = append(newCorpus, c.inputs[idx])
+ }
+ }
+ logf(1, "minimized corpus: %v -> %v", len(mgr.corpus), len(newCorpus))
+ mgr.corpus = newCorpus
+}
+
+func (mgr *Manager) Connect(a *ManagerConnectArgs, r *ManagerConnectRes) error {
+ logf(1, "fuzzer %v connected", a.Name)
+ mgr.mu.Lock()
+ defer mgr.mu.Unlock()
+
+ mgr.minimizeCorpus()
+ mgr.fuzzers[a.Name] = &Fuzzer{
+ name: a.Name,
+ input: 0,
+ }
+ return nil
+}
+
+func (mgr *Manager) NewInput(a *NewManagerInputArgs, r *int) error {
+ logf(2, "new input from fuzzer %v", a.Name)
+ mgr.mu.Lock()
+ defer mgr.mu.Unlock()
+
+ call := sys.CallID[a.Call]
+ if len(cover.Difference(a.Cover, mgr.corpusCover[call])) == 0 {
+ return nil
+ }
+ mgr.corpusCover[call] = cover.Union(mgr.corpusCover[call], a.Cover)
+ mgr.corpus = append(mgr.corpus, a.RpcInput)
+
+ 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)
+ }
+ }
+
+ return nil
+}
+
+func (mgr *Manager) Poll(a *ManagerPollArgs, r *ManagerPollRes) error {
+ logf(2, "poll from %v", a.Name)
+ mgr.mu.Lock()
+ defer mgr.mu.Unlock()
+
+ f := mgr.fuzzers[a.Name]
+ if f == nil {
+ fatalf("fuzzer %v is not connected", a.Name)
+ }
+
+ for i := 0; i < 100 && f.input < len(mgr.corpus); i++ {
+ r.NewInputs = append(r.NewInputs, mgr.corpus[f.input])
+ f.input++
+ }
+
+ for i := 0; i < 10 && len(mgr.candidates) > 0; i++ {
+ last := len(mgr.candidates) - 1
+ r.Candidates = append(r.Candidates, mgr.candidates[last])
+ mgr.candidates = mgr.candidates[:last]
+ }
+
+ return nil
+}