From 8e7ca7c5ff18e17cab7b6b3ae569565224f95fcc Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Thu, 17 Dec 2015 16:06:33 +0100 Subject: remove master and naming overhaul Remove master process entirely, it is not useful in its current form. We first need to understand what we want from it, and them re-implement it. Prefix all binaries with syz- to avoid name clashes. --- Makefile | 22 +- fuzzer/fuzzer.go | 523 ----------------------------------------- manager/cover.go | 288 ----------------------- manager/example.cfg | 25 -- manager/html.go | 300 ----------------------- manager/main.go | 197 ---------------- manager/manager.go | 252 -------------------- master/html.go | 95 -------- master/master.go | 170 -------------- master/persistent.go | 129 ---------- syz-fuzzer/fuzzer.go | 523 +++++++++++++++++++++++++++++++++++++++++ syz-manager/cover.go | 288 +++++++++++++++++++++++ syz-manager/example.cfg | 26 ++ syz-manager/html.go | 269 +++++++++++++++++++++ syz-manager/main.go | 189 +++++++++++++++ syz-manager/manager.go | 191 +++++++++++++++ syz-manager/persistent.go | 129 ++++++++++ tools/execprog/execprog.go | 169 ------------- tools/mutate/mutate.go | 49 ---- tools/prog2c/prog2c.go | 31 --- tools/stress/stress.go | 153 ------------ tools/syz-execprog/execprog.go | 169 +++++++++++++ tools/syz-mutate/mutate.go | 49 ++++ tools/syz-prog2c/prog2c.go | 31 +++ tools/syz-stress/stress.go | 153 ++++++++++++ vm/qemu/qemu.go | 6 +- 26 files changed, 2028 insertions(+), 2398 deletions(-) delete mode 100644 fuzzer/fuzzer.go delete mode 100644 manager/cover.go delete mode 100644 manager/example.cfg delete mode 100644 manager/html.go delete mode 100644 manager/main.go delete mode 100644 manager/manager.go delete mode 100644 master/html.go delete mode 100644 master/master.go delete mode 100644 master/persistent.go create mode 100644 syz-fuzzer/fuzzer.go create mode 100644 syz-manager/cover.go create mode 100644 syz-manager/example.cfg create mode 100644 syz-manager/html.go create mode 100644 syz-manager/main.go create mode 100644 syz-manager/manager.go create mode 100644 syz-manager/persistent.go delete mode 100644 tools/execprog/execprog.go delete mode 100644 tools/mutate/mutate.go delete mode 100644 tools/prog2c/prog2c.go delete mode 100644 tools/stress/stress.go create mode 100644 tools/syz-execprog/execprog.go create mode 100644 tools/syz-mutate/mutate.go create mode 100644 tools/syz-prog2c/prog2c.go create mode 100644 tools/syz-stress/stress.go diff --git a/Makefile b/Makefile index 0e429e4de..dc4918fff 100644 --- a/Makefile +++ b/Makefile @@ -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/fuzzer/fuzzer.go b/fuzzer/fuzzer.go deleted file mode 100644 index 34d99499f..000000000 --- a/fuzzer/fuzzer.go +++ /dev/null @@ -1,523 +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 - -// TODO: implement some form of smashing of new inputs. -// E.g. alter arguments while the program still gives the new coverage, -// i.e. aim at cracking new branches and triggering bugs in that new piece of code. - -import ( - "crypto/sha1" - "flag" - "fmt" - "log" - "math/rand" - "net/rpc" - "os" - "runtime/debug" - "strconv" - "strings" - "sync" - "sync/atomic" - "syscall" - "time" - - "github.com/google/syzkaller/cover" - "github.com/google/syzkaller/ipc" - "github.com/google/syzkaller/prog" - . "github.com/google/syzkaller/rpctype" - "github.com/google/syzkaller/sys" -) - -var ( - flagName = flag.String("name", "", "unique name for manager") - flagExecutor = flag.String("executor", "", "path to executor binary") - flagManager = flag.String("manager", "", "manager rpc address") - flagStrace = flag.Bool("strace", false, "run executor under strace") - flagSaveProg = flag.Bool("saveprog", false, "save programs into local file before executing") - flagSyscalls = flag.String("calls", "", "comma-delimited list of enabled syscall IDs (empty string for all syscalls)") - flagNoCover = flag.Bool("nocover", false, "disable coverage collection/handling") - flagDropPrivs = flag.Bool("dropprivs", true, "impersonate into nobody") - flagProcs = flag.Int("procs", 1, "number of parallel test processes") - flagLeak = flag.Bool("leak", false, "detect memory leaks") - flagV = flag.Int("v", 0, "verbosity") -) - -const ( - programLength = 30 -) - -type Sig [sha1.Size]byte - -func hash(data []byte) Sig { - return Sig(sha1.Sum(data)) -} - -type Input struct { - p *prog.Prog - call int - cover cover.Cover -} - -var ( - manager *rpc.Client - - coverMu sync.RWMutex - corpusCover []cover.Cover - maxCover []cover.Cover - flakes cover.Cover - - corpusMu sync.RWMutex - corpus []Input - corpusHashes map[Sig]struct{} - - triageMu sync.RWMutex - triage []Input - candidates []*prog.Prog - - gate *ipc.Gate - - statExecGen uint64 - statExecFuzz uint64 - statExecCandidate uint64 - statExecTriage uint64 - statExecMinimize uint64 - statNewInput uint64 - - allTriaged uint32 -) - -func main() { - debug.SetGCPercent(50) - flag.Parse() - logf(0, "started") - - var calls []*sys.Call - if *flagSyscalls != "" { - for _, id := range strings.Split(*flagSyscalls, ",") { - n, err := strconv.ParseUint(id, 10, 64) - if err != nil || n >= uint64(len(sys.Calls)) { - panic(fmt.Sprintf("invalid syscall in -calls flag: '%v", id)) - } - calls = append(calls, sys.Calls[n]) - } - } - - corpusCover = make([]cover.Cover, sys.CallCount) - maxCover = make([]cover.Cover, sys.CallCount) - corpusHashes = make(map[Sig]struct{}) - - conn, err := rpc.Dial("tcp", *flagManager) - if err != nil { - panic(err) - } - manager = conn - a := &ManagerConnectArgs{*flagName} - r := &ManagerConnectRes{} - if err := manager.Call("Manager.Connect", a, r); err != nil { - panic(err) - } - ct := prog.BuildChoiceTable(r.Prios, calls) - - kmemleakInit() - - flags := ipc.FlagThreaded | ipc.FlagCollide - if *flagStrace { - flags |= ipc.FlagStrace - } - if !*flagNoCover { - flags |= ipc.FlagCover | ipc.FlagDedupCover - } - if *flagDropPrivs { - flags |= ipc.FlagDropPrivs - } - if *flagProcs <= 0 { - *flagProcs = 1 - } - - gate = ipc.NewGate(2 * *flagProcs) - envs := make([]*ipc.Env, *flagProcs) - for pid := 0; pid < *flagProcs; pid++ { - env, err := ipc.MakeEnv(*flagExecutor, 10*time.Second, flags) - if err != nil { - panic(err) - } - envs[pid] = env - - pid := pid - go func() { - rs := rand.NewSource(time.Now().UnixNano() + int64(pid)*1e12) - rnd := rand.New(rs) - - for i := 0; ; i++ { - triageMu.RLock() - if len(triage) != 0 || len(candidates) != 0 { - triageMu.RUnlock() - triageMu.Lock() - if len(triage) != 0 { - last := len(triage) - 1 - inp := triage[last] - triage = triage[:last] - triageMu.Unlock() - logf(1, "triaging : %s", inp.p) - triageInput(pid, env, inp) - continue - } else if len(candidates) != 0 { - last := len(candidates) - 1 - p := candidates[last] - candidates = candidates[:last] - triageMu.Unlock() - execute(pid, env, p, &statExecCandidate) - continue - } else { - triageMu.Unlock() - } - } else { - triageMu.RUnlock() - } - - corpusMu.RLock() - if len(corpus) == 0 || i%10 == 0 { - corpusMu.RUnlock() - p := prog.Generate(rnd, programLength, ct) - logf(1, "#%v: generated: %s", i, p) - execute(pid, env, p, &statExecGen) - p.Mutate(rnd, programLength, ct) - logf(1, "#%v: mutated: %s", i, p) - execute(pid, env, p, &statExecFuzz) - } else { - inp := corpus[rnd.Intn(len(corpus))] - corpusMu.RUnlock() - p := inp.p.Clone() - p.Mutate(rs, programLength, ct) - logf(1, "#%v: mutated: %s <- %s", i, p, inp.p) - execute(pid, env, p, &statExecFuzz) - } - } - }() - } - - var lastPoll time.Time - var lastPrint time.Time - for range time.NewTicker(3 * time.Second).C { - if !*flagSaveProg && time.Since(lastPrint) > 10*time.Second { - // Keep-alive for manager. - logf(0, "alive") - lastPrint = time.Now() - } - if time.Since(lastPoll) > 10*time.Second { - triageMu.RLock() - if len(candidates) != 0 { - triageMu.RUnlock() - continue - } - triageMu.RUnlock() - - a := &ManagerPollArgs{ - Name: *flagName, - Stats: make(map[string]uint64), - } - for _, env := range envs { - a.Stats["exec total"] += atomic.SwapUint64(&env.StatExecs, 0) - a.Stats["executor restarts"] += atomic.SwapUint64(&env.StatRestarts, 0) - } - a.Stats["exec gen"] = atomic.SwapUint64(&statExecGen, 0) - a.Stats["exec fuzz"] = atomic.SwapUint64(&statExecFuzz, 0) - a.Stats["exec candidate"] = atomic.SwapUint64(&statExecCandidate, 0) - a.Stats["exec triage"] = atomic.SwapUint64(&statExecTriage, 0) - a.Stats["exec minimize"] = atomic.SwapUint64(&statExecMinimize, 0) - a.Stats["fuzzer new inputs"] = atomic.SwapUint64(&statNewInput, 0) - r := &ManagerPollRes{} - if err := manager.Call("Manager.Poll", a, r); err != nil { - panic(err) - } - for _, inp := range r.NewInputs { - addInput(inp) - } - for _, data := range r.Candidates { - p, err := prog.Deserialize(data) - if err != nil { - panic(err) - } - if *flagNoCover { - inp := Input{p, 0, nil} - corpusMu.Lock() - corpus = append(corpus, inp) - corpusMu.Unlock() - } else { - triageMu.Lock() - candidates = append(candidates, p) - triageMu.Unlock() - } - } - if len(r.Candidates) == 0 { - if atomic.LoadUint32(&allTriaged) == 0 { - if *flagLeak { - kmemleakScan(false) - } - atomic.StoreUint32(&allTriaged, 1) - } - } - if len(r.NewInputs) == 0 && len(r.Candidates) == 0 { - lastPoll = time.Now() - } - } - } -} - -func addInput(inp RpcInput) { - corpusMu.Lock() - defer corpusMu.Unlock() - coverMu.Lock() - defer coverMu.Unlock() - - if *flagNoCover { - panic("should not be called when coverage is disabled") - } - p, err := prog.Deserialize(inp.Prog) - if err != nil { - panic(err) - } - if inp.CallIndex < 0 || inp.CallIndex >= len(p.Calls) { - panic("bad call index") - } - call := p.Calls[inp.CallIndex].Meta - sig := hash(inp.Prog) - if _, ok := corpusHashes[sig]; ok { - return - } - cov := cover.Canonicalize(inp.Cover) - diff := cover.Difference(cov, maxCover[call.CallID]) - diff = cover.Difference(diff, flakes) - if len(diff) == 0 { - return - } - inp1 := Input{p, inp.CallIndex, cov} - corpus = append(corpus, inp1) - corpusCover[call.CallID] = cover.Union(corpusCover[call.CallID], cov) - maxCover[call.CallID] = cover.Union(maxCover[call.CallID], cov) - corpusHashes[hash(inp.Prog)] = struct{}{} -} - -func triageInput(pid int, env *ipc.Env, inp Input) { - if *flagNoCover { - panic("should not be called when coverage is disabled") - } - - call := inp.p.Calls[inp.call].Meta - coverMu.RLock() - newCover := cover.Difference(inp.cover, corpusCover[call.CallID]) - newCover = cover.Difference(newCover, flakes) - coverMu.RUnlock() - if len(newCover) == 0 { - return - } - - corpusMu.RLock() - if _, ok := corpusHashes[hash(inp.p.Serialize())]; ok { - corpusMu.RUnlock() - return - } - corpusMu.RUnlock() - - minCover := inp.cover - for i := 0; i < 3; i++ { - allCover := execute1(pid, env, inp.p, &statExecTriage) - if len(allCover[inp.call]) == 0 { - // The call was not executed. Happens sometimes, reason unknown. - continue - } - coverMu.RLock() - cov := allCover[inp.call] - diff := cover.SymmetricDifference(inp.cover, cov) - minCover = cover.Intersection(minCover, cov) - updateFlakes := len(diff) != 0 && len(cover.Difference(diff, flakes)) != 0 - coverMu.RUnlock() - if updateFlakes { - coverMu.Lock() - flakes = cover.Union(flakes, diff) - coverMu.Unlock() - } - } - stableNewCover := cover.Intersection(newCover, minCover) - if len(stableNewCover) == 0 { - return - } - inp.p, inp.call = prog.Minimize(inp.p, inp.call, func(p1 *prog.Prog, call1 int) bool { - allCover := execute1(pid, env, p1, &statExecMinimize) - coverMu.RLock() - defer coverMu.RUnlock() - - if len(allCover[call1]) == 0 { - return false // The call was not executed. - } - cov := allCover[call1] - if len(cover.Intersection(stableNewCover, cov)) != len(stableNewCover) { - return false - } - minCover = cover.Intersection(minCover, cov) - return true - }) - inp.cover = minCover - - atomic.AddUint64(&statNewInput, 1) - data := inp.p.Serialize() - logf(2, "added new input for %v to corpus:\n%s", call.CallName, data) - a := &NewManagerInputArgs{*flagName, RpcInput{call.CallName, data, inp.call, []uint32(inp.cover)}} - if err := manager.Call("Manager.NewInput", a, nil); err != nil { - panic(err) - } - - corpusMu.Lock() - defer corpusMu.Unlock() - coverMu.Lock() - defer coverMu.Unlock() - - corpusCover[call.CallID] = cover.Union(corpusCover[call.CallID], minCover) - corpus = append(corpus, inp) - corpusHashes[hash(data)] = struct{}{} -} - -func execute(pid int, env *ipc.Env, p *prog.Prog, stat *uint64) { - allCover := execute1(pid, env, p, stat) - coverMu.RLock() - defer coverMu.RUnlock() - for i, cov := range allCover { - if len(cov) == 0 { - continue - } - c := p.Calls[i].Meta - diff := cover.Difference(cov, maxCover[c.CallID]) - diff = cover.Difference(diff, flakes) - if len(diff) != 0 { - coverMu.RUnlock() - coverMu.Lock() - maxCover[c.CallID] = cover.Union(maxCover[c.CallID], diff) - coverMu.Unlock() - coverMu.RLock() - - inp := Input{p.Clone(), i, cover.Copy(cov)} - triageMu.Lock() - triage = append(triage, inp) - triageMu.Unlock() - } - } -} - -var logMu sync.Mutex - -func execute1(pid int, env *ipc.Env, p *prog.Prog, stat *uint64) []cover.Cover { - if false { - // For debugging, this function must not be executed with locks held. - corpusMu.Lock() - corpusMu.Unlock() - coverMu.Lock() - coverMu.Unlock() - triageMu.Lock() - triageMu.Unlock() - } - - // Limit concurrency window and do leak checking once in a while. - idx := gate.Enter() - defer gate.Leave(idx, func() { - if idx == 0 && *flagLeak && atomic.LoadUint32(&allTriaged) != 0 { - // Scan for leaks once in a while (it is damn slow). - kmemleakScan(true) - } - }) - - if *flagSaveProg { - f, err := os.Create(fmt.Sprintf("%v-%v.prog", *flagName, pid)) - if err == nil { - f.Write(p.Serialize()) - f.Close() - } - } else { - // The following output helps to understand what program crashed kernel. - // It must not be intermixed. - data := p.Serialize() - logMu.Lock() - log.Printf("executing program %v:\n%s", pid, data) - logMu.Unlock() - } - - try := 0 -retry: - atomic.AddUint64(stat, 1) - output, strace, rawCover, failed, hanged, err := env.Exec(p) - if failed { - // BUG in output should be recognized by manager. - logf(0, "BUG: executor-detected bug:\n%s", output) - // Don't return any cover so that the input is not added to corpus. - return make([]cover.Cover, len(p.Calls)) - } - if err != nil { - if try > 10 { - panic(err) - } - try++ - debug.FreeOSMemory() - time.Sleep(time.Second) - goto retry - } - logf(4, "result failed=%v hanged=%v:\n%v\n", failed, hanged, string(output)) - if len(strace) != 0 { - logf(4, "strace:\n%s\n", strace) - } - cov := make([]cover.Cover, len(p.Calls)) - for i, c := range rawCover { - cov[i] = cover.Cover(c) - } - return cov -} - -func logf(v int, msg string, args ...interface{}) { - if *flagV >= v { - log.Printf(msg, args...) - } -} - -func kmemleakInit() { - fd, err := syscall.Open("/sys/kernel/debug/kmemleak", syscall.O_RDWR, 0) - if err != nil { - if *flagLeak { - panic(err) - } else { - return - } - } - defer syscall.Close(fd) - if _, err := syscall.Write(fd, []byte("scan=off")); err != nil { - panic(err) - } -} - -var kmemleakBuf []byte - -func kmemleakScan(report bool) { - fd, err := syscall.Open("/sys/kernel/debug/kmemleak", syscall.O_RDWR, 0) - if err != nil { - panic(err) - } - defer syscall.Close(fd) - if _, err := syscall.Write(fd, []byte("scan")); err != nil { - panic(err) - } - if report { - if kmemleakBuf == nil { - kmemleakBuf = make([]byte, 128<<10) - } - n, err := syscall.Read(fd, kmemleakBuf) - if err != nil { - panic(err) - } - if n != 0 { - // BUG in output should be recognized by manager. - logf(0, "BUG: memory leak:\n%s\n", kmemleakBuf[:n]) - } - } - if _, err := syscall.Write(fd, []byte("clear")); err != nil { - panic(err) - } -} diff --git a/manager/cover.go b/manager/cover.go deleted file mode 100644 index 190fb6038..000000000 --- a/manager/cover.go +++ /dev/null @@ -1,288 +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 ( - "bufio" - "bytes" - "fmt" - "html/template" - "io" - "io/ioutil" - "os/exec" - "sort" - "strconv" - "strings" - "sync" - - "github.com/google/syzkaller/cover" -) - -type LineInfo struct { - file string - line int -} - -var ( - mu sync.Mutex - pcLines = make(map[uint32][]LineInfo) - parsedFiles = make(map[string][][]byte) - htmlReplacer = strings.NewReplacer(">", ">", "<", "<", "&", "&", "\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("")) - buf.Write(ln) - buf.Write([]byte("\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, "0x%x\n", cover.RestorePC(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( - ` - - - - - - - -
- -
-
- {{range $i, $f := .Files}} -
{{$f.Body}}
- {{end}} -
- - - -`)) diff --git a/manager/example.cfg b/manager/example.cfg deleted file mode 100644 index 216e09eeb..000000000 --- a/manager/example.cfg +++ /dev/null @@ -1,25 +0,0 @@ -{ - "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 deleted file mode 100644 index 6123160d9..000000000 --- a/manager/html.go +++ /dev/null @@ -1,300 +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/hex" - "encoding/json" - "fmt" - "html/template" - "net/http" - "sort" - "strconv" - "time" - - "github.com/google/syzkaller/cover" - "github.com/google/syzkaller/prog" - "github.com/google/syzkaller/sys" -) - -func (mgr *Manager) initHttp() { - http.HandleFunc("/", mgr.httpInfo) - 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)) - }() -} - -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)) - } - - 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), - } - - secs := uint64(uptime) / 1e9 - for k, v := range mgr.stats { - val := "" - if x := v / secs; x >= 10 { - val = fmt.Sprintf("%v/sec", x) - } else if x := v * 60 / secs; x >= 10 { - val = fmt.Sprintf("%v/min", x) - } else { - x := v * 60 * 60 / secs - val = fmt.Sprintf("%v/hour", x) - } - data.Stats = append(data.Stats, UIStat{Name: k, Value: val}) - } - sort.Sort(UIStatArray(data.Stats)) - - 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) httpPrio(w http.ResponseWriter, r *http.Request) { - mgr.mu.Lock() - defer mgr.mu.Unlock() - - mgr.minimizeCorpus() - call := r.FormValue("call") - idx := -1 - for i, c := range sys.Calls { - if c.CallName == call { - idx = i - break - } - } - if idx == -1 { - http.Error(w, fmt.Sprintf("unknown call: %v", call), http.StatusInternalServerError) - return - } - - data := &UIPrioData{Call: call} - for i, p := range mgr.prios[idx] { - data.Prios = append(data.Prios, UIPrio{sys.Calls[i].Name, p}) - } - sort.Sort(UIPrioArray(data.Prios)) - - if err := prioTemplate.Execute(w, data); err != nil { - http.Error(w, fmt.Sprintf("failed to execute template: %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 - TriageQueue int - CoverSize int - Uptime string - Stats []UIStat - Calls []UICallType -} - -type UIStat struct { - Name string - Value string -} - -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] } - -type UIStatArray []UIStat - -func (a UIStatArray) Len() int { return len(a) } -func (a UIStatArray) Less(i, j int) bool { return a[i].Name < a[j].Name } -func (a UIStatArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] } - -var htmlTemplate = template.Must(template.New("").Parse(` - - - - syzkaller {{.Name}} - - -Manager: {{.Name}} [master]
-Uptime: {{.Uptime}}
-Master corpus: {{.MasterCorpusSize}}
-Corpus: {{.CorpusSize}}
-Triage queue len: {{.TriageQueue}}
-Cover: {{.CoverSize}}
-
-Stats:
-{{range $stat := $.Stats}} - {{$stat.Name}}: {{$stat.Value}}
-{{end}} -
-{{range $c := $.Calls}} - {{$c.Name}} inputs:{{$c.Inputs}} cover:{{$c.Cover}} prio
-{{end}} - -`)) - -var corpusTemplate = template.Must(template.New("").Parse(` - - - - syzkaller corpus - - -{{range $c := $}} - {{$c.Short}} cover:{{$c.Cover}}
-{{end}} - -`)) - -type UIPrioData struct { - Call string - Prios []UIPrio -} - -type UIPrio struct { - Call string - Prio float32 -} - -type UIPrioArray []UIPrio - -func (a UIPrioArray) Len() int { return len(a) } -func (a UIPrioArray) Less(i, j int) bool { return a[i].Prio > a[j].Prio } -func (a UIPrioArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] } - -var prioTemplate = template.Must(template.New("").Parse(` - - - - syzkaller priorities - - -Priorities for {{$.Call}}

-{{range $p := $.Prios}} - {{printf "%.4f\t%s" $p.Prio $p.Call}}
-{{end}} - -`)) diff --git a/manager/main.go b/manager/main.go deleted file mode 100644 index 9a3523cb8..000000000 --- a/manager/main.go +++ /dev/null @@ -1,197 +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 ( - "bytes" - "encoding/json" - "flag" - "fmt" - "io/ioutil" - "log" - "regexp" - "strings" - - "github.com/google/syzkaller/sys" - "github.com/google/syzkaller/vm" - _ "github.com/google/syzkaller/vm/kvm" - _ "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 // number of VMs - Procs int // number of parallel processes inside of every VM - Port int - Nocover bool - NoDropPrivs bool - Leak bool // do memory leak checking - Params map[string]interface{} - Enable_Syscalls []string - Disable_Syscalls []string - Suppressions []string -} - -func main() { - flag.Parse() - cfg, syscalls := parseConfig() - params, err := json.Marshal(cfg.Params) - if err != nil { - fatalf("failed to marshal config params: %v", err) - } - enabledSyscalls := "" - if len(syscalls) != 0 { - buf := new(bytes.Buffer) - for c := range syscalls { - fmt.Fprintf(buf, ",%v", c) - } - enabledSyscalls = buf.String()[1:] - logf(1, "enabled syscalls: %v", enabledSyscalls) - } - vmCfg := &vm.Config{ - Workdir: cfg.Workdir, - ManagerPort: cfg.Port, - Params: params, - EnabledSyscalls: enabledSyscalls, - NoCover: cfg.Nocover, - NoDropPrivs: cfg.NoDropPrivs, - Leak: cfg.Leak, - Procs: cfg.Procs, - } - - // Add some builtin suppressions. - cfg.Suppressions = append(cfg.Suppressions, []string{ - "panic: failed to start executor binary", - "panic: executor failed: pthread_create failed", - "panic: failed to create temp dir", - "Out of memory: Kill process .* \\(syzkaller_fuzze\\)", - "WARNING: KASAN doesn't support memory hot-add", - }...) - for _, s := range cfg.Suppressions { - re, err := regexp.Compile(s) - if err != nil { - fatalf("failed to compile suppression '%v': %v", s, err) - } - vmCfg.Suppressions = append(vmCfg.Suppressions, re) - } - - var instances []vm.Instance - for i := 0; i < cfg.Count; i++ { - inst, err := vm.Create(cfg.Type, vmCfg, i) - 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) - } - if cfg.Procs <= 0 { - cfg.Procs = 1 - } - - match := func(call *sys.Call, str string) bool { - if str == call.CallName || str == call.Name { - return true - } - if len(str) > 1 && str[len(str)-1] == '*' && strings.HasPrefix(call.Name, str[:len(str)-1]) { - return true - } - return false - } - - 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 match(call, 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 match(call, 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 deleted file mode 100644 index ab40bbe0d..000000000 --- a/manager/manager.go +++ /dev/null @@ -1,252 +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 ( - "crypto/sha1" - "fmt" - "net" - "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 - 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 - - corpus []RpcInput - corpusCover []cover.Cover - prios [][]float32 - - 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, - 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), - } - - // Create HTTP server. - mgr.initHttp() - - // 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 !mgr.cfg.Nocover && len(mgr.corpus) != 0 { - // 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 - } - var corpus []*prog.Prog - for _, inp := range mgr.corpus { - p, err := prog.Deserialize(inp.Prog) - if err != nil { - panic(err) - } - corpus = append(corpus, p) - } - mgr.prios = prog.CalculatePriorities(corpus) -} - -func (mgr *Manager) Connect(a *ManagerConnectArgs, r *ManagerConnectRes) error { - logf(1, "fuzzer %v connected", a.Name) - mgr.mu.Lock() - defer mgr.mu.Unlock() - - mgr.stats["vm restarts"]++ - mgr.minimizeCorpus() - mgr.fuzzers[a.Name] = &Fuzzer{ - name: a.Name, - input: 0, - } - r.Prios = mgr.prios - - 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) - 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) - } - } - - 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() - - for k, v := range a.Stats { - mgr.stats[k] += v - } - - 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 -} 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(` - - - - syzkaller master - - -Corpus: {{.CorpusLen}}
-{{if .Managers}} - Managers:
- {{range $mgr := $.Managers}} - {{$mgr.Name}}
- {{end}} -{{else}} - No managers connected
-{{end}} - -`)) 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/master/persistent.go b/master/persistent.go deleted file mode 100644 index 12f4bbdc8..000000000 --- a/master/persistent.go +++ /dev/null @@ -1,129 +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 ( - "crypto/sha1" - "encoding/hex" - "fmt" - "io/ioutil" - "log" - "os" - "path/filepath" -) - -type Sig [sha1.Size]byte - -// PersistentSet is a set of binary blobs with a persistent mirror on disk. -type PersistentSet struct { - dir string - m map[Sig][]byte - a [][]byte -} - -func hash(data []byte) Sig { - return Sig(sha1.Sum(data)) -} - -func newPersistentSet(dir string, verify func(data []byte) bool) *PersistentSet { - ps := &PersistentSet{ - dir: dir, - m: make(map[Sig][]byte), - } - os.MkdirAll(dir, 0770) - filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { - if err != nil { - log.Fatalf("error during dir walk: %v\n", err) - } - if info.IsDir() { - return nil - } - data, err := ioutil.ReadFile(path) - if err != nil { - log.Fatalf("error during file read: %v\n", err) - return nil - } - sig := hash(data) - if _, ok := ps.m[sig]; ok { - return nil - } - name := info.Name() - if len(data) == 0 { - // This can happen is master runs on machine-under-test, - // and it has crashed midway. - log.Printf("removing empty file %v", name) - os.Remove(path) - return nil - } - const hexLen = 2 * sha1.Size - if len(name) > hexLen+1 && isHexString(name[:hexLen]) && name[hexLen] == '.' { - return nil // description file - } - if len(name) != hexLen || !isHexString(name) { - log.Fatalf("unknown file in persistent dir %v: %v", dir, name) - } - if verify != nil && !verify(data) { - os.Remove(path) - return nil - } - if name != hex.EncodeToString(sig[:]) { - log.Printf("bad hash in persistent dir %v for file %v, expect %v", dir, name, hex.EncodeToString(sig[:])) - if err := ioutil.WriteFile(filepath.Join(ps.dir, hex.EncodeToString(sig[:])), data, 0660); err != nil { - log.Fatalf("failed to write file: %v", err) - } - os.Remove(path) - } - ps.m[sig] = data - ps.a = append(ps.a, data) - return nil - }) - return ps -} - -func isHexString(s string) bool { - for _, v := range []byte(s) { - if v >= '0' && v <= '9' || v >= 'a' && v <= 'f' { - continue - } - return false - } - return true -} - -func (ps *PersistentSet) add(data []byte) bool { - sig := hash(data) - if _, ok := ps.m[sig]; ok { - return false - } - data = append([]byte{}, data...) - ps.m[sig] = data - ps.a = append(ps.a, data) - fname := filepath.Join(ps.dir, hex.EncodeToString(sig[:])) - if err := ioutil.WriteFile(fname, data, 0660); err != nil { - log.Fatalf("failed to write file: %v", err) - } - return true -} - -// addDescription creates a complementary to data file on disk. -func (ps *PersistentSet) addDescription(data []byte, desc []byte, typ string) { - sig := hash(data) - fname := filepath.Join(ps.dir, fmt.Sprintf("%v.%v", hex.EncodeToString(sig[:]), typ)) - if err := ioutil.WriteFile(fname, desc, 0660); err != nil { - log.Fatalf("failed to write file: %v", err) - } -} - -func (ps *PersistentSet) minimize(set map[string]bool) { - ps.a = nil - for sig, data := range ps.m { - s := hex.EncodeToString(sig[:]) - if set[s] { - ps.a = append(ps.a, data) - } else { - delete(ps.m, sig) - os.Remove(filepath.Join(ps.dir, s)) - } - } -} diff --git a/syz-fuzzer/fuzzer.go b/syz-fuzzer/fuzzer.go new file mode 100644 index 000000000..34d99499f --- /dev/null +++ b/syz-fuzzer/fuzzer.go @@ -0,0 +1,523 @@ +// 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 + +// TODO: implement some form of smashing of new inputs. +// E.g. alter arguments while the program still gives the new coverage, +// i.e. aim at cracking new branches and triggering bugs in that new piece of code. + +import ( + "crypto/sha1" + "flag" + "fmt" + "log" + "math/rand" + "net/rpc" + "os" + "runtime/debug" + "strconv" + "strings" + "sync" + "sync/atomic" + "syscall" + "time" + + "github.com/google/syzkaller/cover" + "github.com/google/syzkaller/ipc" + "github.com/google/syzkaller/prog" + . "github.com/google/syzkaller/rpctype" + "github.com/google/syzkaller/sys" +) + +var ( + flagName = flag.String("name", "", "unique name for manager") + flagExecutor = flag.String("executor", "", "path to executor binary") + flagManager = flag.String("manager", "", "manager rpc address") + flagStrace = flag.Bool("strace", false, "run executor under strace") + flagSaveProg = flag.Bool("saveprog", false, "save programs into local file before executing") + flagSyscalls = flag.String("calls", "", "comma-delimited list of enabled syscall IDs (empty string for all syscalls)") + flagNoCover = flag.Bool("nocover", false, "disable coverage collection/handling") + flagDropPrivs = flag.Bool("dropprivs", true, "impersonate into nobody") + flagProcs = flag.Int("procs", 1, "number of parallel test processes") + flagLeak = flag.Bool("leak", false, "detect memory leaks") + flagV = flag.Int("v", 0, "verbosity") +) + +const ( + programLength = 30 +) + +type Sig [sha1.Size]byte + +func hash(data []byte) Sig { + return Sig(sha1.Sum(data)) +} + +type Input struct { + p *prog.Prog + call int + cover cover.Cover +} + +var ( + manager *rpc.Client + + coverMu sync.RWMutex + corpusCover []cover.Cover + maxCover []cover.Cover + flakes cover.Cover + + corpusMu sync.RWMutex + corpus []Input + corpusHashes map[Sig]struct{} + + triageMu sync.RWMutex + triage []Input + candidates []*prog.Prog + + gate *ipc.Gate + + statExecGen uint64 + statExecFuzz uint64 + statExecCandidate uint64 + statExecTriage uint64 + statExecMinimize uint64 + statNewInput uint64 + + allTriaged uint32 +) + +func main() { + debug.SetGCPercent(50) + flag.Parse() + logf(0, "started") + + var calls []*sys.Call + if *flagSyscalls != "" { + for _, id := range strings.Split(*flagSyscalls, ",") { + n, err := strconv.ParseUint(id, 10, 64) + if err != nil || n >= uint64(len(sys.Calls)) { + panic(fmt.Sprintf("invalid syscall in -calls flag: '%v", id)) + } + calls = append(calls, sys.Calls[n]) + } + } + + corpusCover = make([]cover.Cover, sys.CallCount) + maxCover = make([]cover.Cover, sys.CallCount) + corpusHashes = make(map[Sig]struct{}) + + conn, err := rpc.Dial("tcp", *flagManager) + if err != nil { + panic(err) + } + manager = conn + a := &ManagerConnectArgs{*flagName} + r := &ManagerConnectRes{} + if err := manager.Call("Manager.Connect", a, r); err != nil { + panic(err) + } + ct := prog.BuildChoiceTable(r.Prios, calls) + + kmemleakInit() + + flags := ipc.FlagThreaded | ipc.FlagCollide + if *flagStrace { + flags |= ipc.FlagStrace + } + if !*flagNoCover { + flags |= ipc.FlagCover | ipc.FlagDedupCover + } + if *flagDropPrivs { + flags |= ipc.FlagDropPrivs + } + if *flagProcs <= 0 { + *flagProcs = 1 + } + + gate = ipc.NewGate(2 * *flagProcs) + envs := make([]*ipc.Env, *flagProcs) + for pid := 0; pid < *flagProcs; pid++ { + env, err := ipc.MakeEnv(*flagExecutor, 10*time.Second, flags) + if err != nil { + panic(err) + } + envs[pid] = env + + pid := pid + go func() { + rs := rand.NewSource(time.Now().UnixNano() + int64(pid)*1e12) + rnd := rand.New(rs) + + for i := 0; ; i++ { + triageMu.RLock() + if len(triage) != 0 || len(candidates) != 0 { + triageMu.RUnlock() + triageMu.Lock() + if len(triage) != 0 { + last := len(triage) - 1 + inp := triage[last] + triage = triage[:last] + triageMu.Unlock() + logf(1, "triaging : %s", inp.p) + triageInput(pid, env, inp) + continue + } else if len(candidates) != 0 { + last := len(candidates) - 1 + p := candidates[last] + candidates = candidates[:last] + triageMu.Unlock() + execute(pid, env, p, &statExecCandidate) + continue + } else { + triageMu.Unlock() + } + } else { + triageMu.RUnlock() + } + + corpusMu.RLock() + if len(corpus) == 0 || i%10 == 0 { + corpusMu.RUnlock() + p := prog.Generate(rnd, programLength, ct) + logf(1, "#%v: generated: %s", i, p) + execute(pid, env, p, &statExecGen) + p.Mutate(rnd, programLength, ct) + logf(1, "#%v: mutated: %s", i, p) + execute(pid, env, p, &statExecFuzz) + } else { + inp := corpus[rnd.Intn(len(corpus))] + corpusMu.RUnlock() + p := inp.p.Clone() + p.Mutate(rs, programLength, ct) + logf(1, "#%v: mutated: %s <- %s", i, p, inp.p) + execute(pid, env, p, &statExecFuzz) + } + } + }() + } + + var lastPoll time.Time + var lastPrint time.Time + for range time.NewTicker(3 * time.Second).C { + if !*flagSaveProg && time.Since(lastPrint) > 10*time.Second { + // Keep-alive for manager. + logf(0, "alive") + lastPrint = time.Now() + } + if time.Since(lastPoll) > 10*time.Second { + triageMu.RLock() + if len(candidates) != 0 { + triageMu.RUnlock() + continue + } + triageMu.RUnlock() + + a := &ManagerPollArgs{ + Name: *flagName, + Stats: make(map[string]uint64), + } + for _, env := range envs { + a.Stats["exec total"] += atomic.SwapUint64(&env.StatExecs, 0) + a.Stats["executor restarts"] += atomic.SwapUint64(&env.StatRestarts, 0) + } + a.Stats["exec gen"] = atomic.SwapUint64(&statExecGen, 0) + a.Stats["exec fuzz"] = atomic.SwapUint64(&statExecFuzz, 0) + a.Stats["exec candidate"] = atomic.SwapUint64(&statExecCandidate, 0) + a.Stats["exec triage"] = atomic.SwapUint64(&statExecTriage, 0) + a.Stats["exec minimize"] = atomic.SwapUint64(&statExecMinimize, 0) + a.Stats["fuzzer new inputs"] = atomic.SwapUint64(&statNewInput, 0) + r := &ManagerPollRes{} + if err := manager.Call("Manager.Poll", a, r); err != nil { + panic(err) + } + for _, inp := range r.NewInputs { + addInput(inp) + } + for _, data := range r.Candidates { + p, err := prog.Deserialize(data) + if err != nil { + panic(err) + } + if *flagNoCover { + inp := Input{p, 0, nil} + corpusMu.Lock() + corpus = append(corpus, inp) + corpusMu.Unlock() + } else { + triageMu.Lock() + candidates = append(candidates, p) + triageMu.Unlock() + } + } + if len(r.Candidates) == 0 { + if atomic.LoadUint32(&allTriaged) == 0 { + if *flagLeak { + kmemleakScan(false) + } + atomic.StoreUint32(&allTriaged, 1) + } + } + if len(r.NewInputs) == 0 && len(r.Candidates) == 0 { + lastPoll = time.Now() + } + } + } +} + +func addInput(inp RpcInput) { + corpusMu.Lock() + defer corpusMu.Unlock() + coverMu.Lock() + defer coverMu.Unlock() + + if *flagNoCover { + panic("should not be called when coverage is disabled") + } + p, err := prog.Deserialize(inp.Prog) + if err != nil { + panic(err) + } + if inp.CallIndex < 0 || inp.CallIndex >= len(p.Calls) { + panic("bad call index") + } + call := p.Calls[inp.CallIndex].Meta + sig := hash(inp.Prog) + if _, ok := corpusHashes[sig]; ok { + return + } + cov := cover.Canonicalize(inp.Cover) + diff := cover.Difference(cov, maxCover[call.CallID]) + diff = cover.Difference(diff, flakes) + if len(diff) == 0 { + return + } + inp1 := Input{p, inp.CallIndex, cov} + corpus = append(corpus, inp1) + corpusCover[call.CallID] = cover.Union(corpusCover[call.CallID], cov) + maxCover[call.CallID] = cover.Union(maxCover[call.CallID], cov) + corpusHashes[hash(inp.Prog)] = struct{}{} +} + +func triageInput(pid int, env *ipc.Env, inp Input) { + if *flagNoCover { + panic("should not be called when coverage is disabled") + } + + call := inp.p.Calls[inp.call].Meta + coverMu.RLock() + newCover := cover.Difference(inp.cover, corpusCover[call.CallID]) + newCover = cover.Difference(newCover, flakes) + coverMu.RUnlock() + if len(newCover) == 0 { + return + } + + corpusMu.RLock() + if _, ok := corpusHashes[hash(inp.p.Serialize())]; ok { + corpusMu.RUnlock() + return + } + corpusMu.RUnlock() + + minCover := inp.cover + for i := 0; i < 3; i++ { + allCover := execute1(pid, env, inp.p, &statExecTriage) + if len(allCover[inp.call]) == 0 { + // The call was not executed. Happens sometimes, reason unknown. + continue + } + coverMu.RLock() + cov := allCover[inp.call] + diff := cover.SymmetricDifference(inp.cover, cov) + minCover = cover.Intersection(minCover, cov) + updateFlakes := len(diff) != 0 && len(cover.Difference(diff, flakes)) != 0 + coverMu.RUnlock() + if updateFlakes { + coverMu.Lock() + flakes = cover.Union(flakes, diff) + coverMu.Unlock() + } + } + stableNewCover := cover.Intersection(newCover, minCover) + if len(stableNewCover) == 0 { + return + } + inp.p, inp.call = prog.Minimize(inp.p, inp.call, func(p1 *prog.Prog, call1 int) bool { + allCover := execute1(pid, env, p1, &statExecMinimize) + coverMu.RLock() + defer coverMu.RUnlock() + + if len(allCover[call1]) == 0 { + return false // The call was not executed. + } + cov := allCover[call1] + if len(cover.Intersection(stableNewCover, cov)) != len(stableNewCover) { + return false + } + minCover = cover.Intersection(minCover, cov) + return true + }) + inp.cover = minCover + + atomic.AddUint64(&statNewInput, 1) + data := inp.p.Serialize() + logf(2, "added new input for %v to corpus:\n%s", call.CallName, data) + a := &NewManagerInputArgs{*flagName, RpcInput{call.CallName, data, inp.call, []uint32(inp.cover)}} + if err := manager.Call("Manager.NewInput", a, nil); err != nil { + panic(err) + } + + corpusMu.Lock() + defer corpusMu.Unlock() + coverMu.Lock() + defer coverMu.Unlock() + + corpusCover[call.CallID] = cover.Union(corpusCover[call.CallID], minCover) + corpus = append(corpus, inp) + corpusHashes[hash(data)] = struct{}{} +} + +func execute(pid int, env *ipc.Env, p *prog.Prog, stat *uint64) { + allCover := execute1(pid, env, p, stat) + coverMu.RLock() + defer coverMu.RUnlock() + for i, cov := range allCover { + if len(cov) == 0 { + continue + } + c := p.Calls[i].Meta + diff := cover.Difference(cov, maxCover[c.CallID]) + diff = cover.Difference(diff, flakes) + if len(diff) != 0 { + coverMu.RUnlock() + coverMu.Lock() + maxCover[c.CallID] = cover.Union(maxCover[c.CallID], diff) + coverMu.Unlock() + coverMu.RLock() + + inp := Input{p.Clone(), i, cover.Copy(cov)} + triageMu.Lock() + triage = append(triage, inp) + triageMu.Unlock() + } + } +} + +var logMu sync.Mutex + +func execute1(pid int, env *ipc.Env, p *prog.Prog, stat *uint64) []cover.Cover { + if false { + // For debugging, this function must not be executed with locks held. + corpusMu.Lock() + corpusMu.Unlock() + coverMu.Lock() + coverMu.Unlock() + triageMu.Lock() + triageMu.Unlock() + } + + // Limit concurrency window and do leak checking once in a while. + idx := gate.Enter() + defer gate.Leave(idx, func() { + if idx == 0 && *flagLeak && atomic.LoadUint32(&allTriaged) != 0 { + // Scan for leaks once in a while (it is damn slow). + kmemleakScan(true) + } + }) + + if *flagSaveProg { + f, err := os.Create(fmt.Sprintf("%v-%v.prog", *flagName, pid)) + if err == nil { + f.Write(p.Serialize()) + f.Close() + } + } else { + // The following output helps to understand what program crashed kernel. + // It must not be intermixed. + data := p.Serialize() + logMu.Lock() + log.Printf("executing program %v:\n%s", pid, data) + logMu.Unlock() + } + + try := 0 +retry: + atomic.AddUint64(stat, 1) + output, strace, rawCover, failed, hanged, err := env.Exec(p) + if failed { + // BUG in output should be recognized by manager. + logf(0, "BUG: executor-detected bug:\n%s", output) + // Don't return any cover so that the input is not added to corpus. + return make([]cover.Cover, len(p.Calls)) + } + if err != nil { + if try > 10 { + panic(err) + } + try++ + debug.FreeOSMemory() + time.Sleep(time.Second) + goto retry + } + logf(4, "result failed=%v hanged=%v:\n%v\n", failed, hanged, string(output)) + if len(strace) != 0 { + logf(4, "strace:\n%s\n", strace) + } + cov := make([]cover.Cover, len(p.Calls)) + for i, c := range rawCover { + cov[i] = cover.Cover(c) + } + return cov +} + +func logf(v int, msg string, args ...interface{}) { + if *flagV >= v { + log.Printf(msg, args...) + } +} + +func kmemleakInit() { + fd, err := syscall.Open("/sys/kernel/debug/kmemleak", syscall.O_RDWR, 0) + if err != nil { + if *flagLeak { + panic(err) + } else { + return + } + } + defer syscall.Close(fd) + if _, err := syscall.Write(fd, []byte("scan=off")); err != nil { + panic(err) + } +} + +var kmemleakBuf []byte + +func kmemleakScan(report bool) { + fd, err := syscall.Open("/sys/kernel/debug/kmemleak", syscall.O_RDWR, 0) + if err != nil { + panic(err) + } + defer syscall.Close(fd) + if _, err := syscall.Write(fd, []byte("scan")); err != nil { + panic(err) + } + if report { + if kmemleakBuf == nil { + kmemleakBuf = make([]byte, 128<<10) + } + n, err := syscall.Read(fd, kmemleakBuf) + if err != nil { + panic(err) + } + if n != 0 { + // BUG in output should be recognized by manager. + logf(0, "BUG: memory leak:\n%s\n", kmemleakBuf[:n]) + } + } + if _, err := syscall.Write(fd, []byte("clear")); err != nil { + panic(err) + } +} diff --git a/syz-manager/cover.go b/syz-manager/cover.go new file mode 100644 index 000000000..190fb6038 --- /dev/null +++ b/syz-manager/cover.go @@ -0,0 +1,288 @@ +// 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" + + "github.com/google/syzkaller/cover" +) + +type LineInfo struct { + file string + line int +} + +var ( + mu sync.Mutex + pcLines = make(map[uint32][]LineInfo) + parsedFiles = make(map[string][][]byte) + htmlReplacer = strings.NewReplacer(">", ">", "<", "<", "&", "&", "\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("")) + buf.Write(ln) + buf.Write([]byte("\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, "0x%x\n", cover.RestorePC(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( + ` + + + + + + + +
+ +
+
+ {{range $i, $f := .Files}} +
{{$f.Body}}
+ {{end}} +
+ + + +`)) diff --git a/syz-manager/example.cfg b/syz-manager/example.cfg new file mode 100644 index 000000000..a9095ef18 --- /dev/null +++ b/syz-manager/example.cfg @@ -0,0 +1,26 @@ +{ + "http": "myhost.com:56741", + "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" + ], + "suppressions": [ + "some known bug" + ] +} diff --git a/syz-manager/html.go b/syz-manager/html.go new file mode 100644 index 000000000..0d7e87687 --- /dev/null +++ b/syz-manager/html.go @@ -0,0 +1,269 @@ +// 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 ( + "fmt" + "html/template" + "net/http" + "sort" + "strconv" + "time" + + "github.com/google/syzkaller/cover" + "github.com/google/syzkaller/prog" + "github.com/google/syzkaller/sys" +) + +func (mgr *Manager) initHttp() { + http.HandleFunc("/", mgr.httpInfo) + http.HandleFunc("/corpus", mgr.httpCorpus) + http.HandleFunc("/cover", mgr.httpCover) + http.HandleFunc("/prio", mgr.httpPrio) + 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) { + 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)) + } + + uptime := time.Since(mgr.startTime) + data := &UIData{ + CorpusSize: len(mgr.corpus), + TriageQueue: len(mgr.candidates), + Uptime: fmt.Sprintf("%v", uptime), + } + + secs := uint64(uptime) / 1e9 + for k, v := range mgr.stats { + val := "" + if x := v / secs; x >= 10 { + val = fmt.Sprintf("%v/sec", x) + } else if x := v * 60 / secs; x >= 10 { + val = fmt.Sprintf("%v/min", x) + } else { + x := v * 60 * 60 / secs + val = fmt.Sprintf("%v/hour", x) + } + data.Stats = append(data.Stats, UIStat{Name: k, Value: val}) + } + sort.Sort(UIStatArray(data.Stats)) + + 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) httpPrio(w http.ResponseWriter, r *http.Request) { + mgr.mu.Lock() + defer mgr.mu.Unlock() + + mgr.minimizeCorpus() + call := r.FormValue("call") + idx := -1 + for i, c := range sys.Calls { + if c.CallName == call { + idx = i + break + } + } + if idx == -1 { + http.Error(w, fmt.Sprintf("unknown call: %v", call), http.StatusInternalServerError) + return + } + + data := &UIPrioData{Call: call} + for i, p := range mgr.prios[idx] { + data.Prios = append(data.Prios, UIPrio{sys.Calls[i].Name, p}) + } + sort.Sort(UIPrioArray(data.Prios)) + + if err := prioTemplate.Execute(w, data); err != nil { + http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError) + } +} + +type UIData struct { + CorpusSize int + TriageQueue int + CoverSize int + Uptime string + Stats []UIStat + Calls []UICallType +} + +type UIStat struct { + Name string + Value string +} + +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] } + +type UIStatArray []UIStat + +func (a UIStatArray) Len() int { return len(a) } +func (a UIStatArray) Less(i, j int) bool { return a[i].Name < a[j].Name } +func (a UIStatArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +var htmlTemplate = template.Must(template.New("").Parse(` + + + + syzkaller + + +Uptime: {{.Uptime}}
+Corpus: {{.CorpusSize}}
+Triage queue len: {{.TriageQueue}}
+Cover: {{.CoverSize}}
+
+Stats:
+{{range $stat := $.Stats}} + {{$stat.Name}}: {{$stat.Value}}
+{{end}} +
+{{range $c := $.Calls}} + {{$c.Name}} inputs:{{$c.Inputs}} cover:{{$c.Cover}} prio
+{{end}} + +`)) + +var corpusTemplate = template.Must(template.New("").Parse(` + + + + syzkaller corpus + + +{{range $c := $}} + {{$c.Short}} cover:{{$c.Cover}}
+{{end}} + +`)) + +type UIPrioData struct { + Call string + Prios []UIPrio +} + +type UIPrio struct { + Call string + Prio float32 +} + +type UIPrioArray []UIPrio + +func (a UIPrioArray) Len() int { return len(a) } +func (a UIPrioArray) Less(i, j int) bool { return a[i].Prio > a[j].Prio } +func (a UIPrioArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +var prioTemplate = template.Must(template.New("").Parse(` + + + + syzkaller priorities + + +Priorities for {{$.Call}}

+{{range $p := $.Prios}} + {{printf "%.4f\t%s" $p.Prio $p.Call}}
+{{end}} + +`)) diff --git a/syz-manager/main.go b/syz-manager/main.go new file mode 100644 index 000000000..acbba0967 --- /dev/null +++ b/syz-manager/main.go @@ -0,0 +1,189 @@ +// 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 ( + "bytes" + "encoding/json" + "flag" + "fmt" + "io/ioutil" + "log" + "regexp" + "strings" + + "github.com/google/syzkaller/sys" + "github.com/google/syzkaller/vm" + _ "github.com/google/syzkaller/vm/kvm" + _ "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 { + Http string + Workdir string + Vmlinux string + Type string + Count int // number of VMs + Procs int // number of parallel processes inside of every VM + Port int + Nocover bool + NoDropPrivs bool + Leak bool // do memory leak checking + Params map[string]interface{} + Enable_Syscalls []string + Disable_Syscalls []string + Suppressions []string +} + +func main() { + flag.Parse() + cfg, syscalls := parseConfig() + params, err := json.Marshal(cfg.Params) + if err != nil { + fatalf("failed to marshal config params: %v", err) + } + enabledSyscalls := "" + if len(syscalls) != 0 { + buf := new(bytes.Buffer) + for c := range syscalls { + fmt.Fprintf(buf, ",%v", c) + } + enabledSyscalls = buf.String()[1:] + logf(1, "enabled syscalls: %v", enabledSyscalls) + } + vmCfg := &vm.Config{ + Workdir: cfg.Workdir, + ManagerPort: cfg.Port, + Params: params, + EnabledSyscalls: enabledSyscalls, + NoCover: cfg.Nocover, + NoDropPrivs: cfg.NoDropPrivs, + Leak: cfg.Leak, + Procs: cfg.Procs, + } + + // Add some builtin suppressions. + cfg.Suppressions = append(cfg.Suppressions, []string{ + "panic: failed to start executor binary", + "panic: executor failed: pthread_create failed", + "panic: failed to create temp dir", + "Out of memory: Kill process .* \\(syzkaller_fuzze\\)", + "WARNING: KASAN doesn't support memory hot-add", + }...) + for _, s := range cfg.Suppressions { + re, err := regexp.Compile(s) + if err != nil { + fatalf("failed to compile suppression '%v': %v", s, err) + } + vmCfg.Suppressions = append(vmCfg.Suppressions, re) + } + + var instances []vm.Instance + for i := 0; i < cfg.Count; i++ { + inst, err := vm.Create(cfg.Type, vmCfg, i) + 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.Http == "" { + fatalf("config param http 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) + } + if cfg.Procs <= 0 { + cfg.Procs = 1 + } + + match := func(call *sys.Call, str string) bool { + if str == call.CallName || str == call.Name { + return true + } + if len(str) > 1 && str[len(str)-1] == '*' && strings.HasPrefix(call.Name, str[:len(str)-1]) { + return true + } + return false + } + + 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 match(call, 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 match(call, 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/syz-manager/manager.go b/syz-manager/manager.go new file mode 100644 index 000000000..44510d7e1 --- /dev/null +++ b/syz-manager/manager.go @@ -0,0 +1,191 @@ +// 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 ( + "fmt" + "net" + "net/rpc" + "path/filepath" + "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 Manager struct { + 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 + + fuzzers map[string]*Fuzzer +} + +type Fuzzer struct { + name string + input int +} + +func RunManager(cfg *Config, syscalls map[int]bool, instances []vm.Instance) { + mgr := &Manager{ + 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() + + // 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) + } + logf(0, "serving rpc on tcp://%v", rpcAddr) + s := rpc.NewServer() + s.Register(mgr) + go s.Accept(ln) + + for _, inst := range mgr.instances { + go inst.Run() + } + select {} +} + +func (mgr *Manager) minimizeCorpus() { + if !mgr.cfg.Nocover && len(mgr.corpus) != 0 { + // 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 + } + var corpus []*prog.Prog + for _, inp := range mgr.corpus { + p, err := prog.Deserialize(inp.Prog) + if err != nil { + panic(err) + } + 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 { + logf(1, "fuzzer %v connected", a.Name) + mgr.mu.Lock() + defer mgr.mu.Unlock() + + mgr.stats["vm restarts"]++ + mgr.minimizeCorpus() + mgr.fuzzers[a.Name] = &Fuzzer{ + name: a.Name, + input: 0, + } + r.Prios = mgr.prios + + 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) + mgr.stats["manager new inputs"]++ + mgr.persistentCorpus.add(a.RpcInput.Prog) + 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() + + for k, v := range a.Stats { + mgr.stats[k] += v + } + + 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 +} diff --git a/syz-manager/persistent.go b/syz-manager/persistent.go new file mode 100644 index 000000000..12f4bbdc8 --- /dev/null +++ b/syz-manager/persistent.go @@ -0,0 +1,129 @@ +// 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" + "encoding/hex" + "fmt" + "io/ioutil" + "log" + "os" + "path/filepath" +) + +type Sig [sha1.Size]byte + +// PersistentSet is a set of binary blobs with a persistent mirror on disk. +type PersistentSet struct { + dir string + m map[Sig][]byte + a [][]byte +} + +func hash(data []byte) Sig { + return Sig(sha1.Sum(data)) +} + +func newPersistentSet(dir string, verify func(data []byte) bool) *PersistentSet { + ps := &PersistentSet{ + dir: dir, + m: make(map[Sig][]byte), + } + os.MkdirAll(dir, 0770) + filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if err != nil { + log.Fatalf("error during dir walk: %v\n", err) + } + if info.IsDir() { + return nil + } + data, err := ioutil.ReadFile(path) + if err != nil { + log.Fatalf("error during file read: %v\n", err) + return nil + } + sig := hash(data) + if _, ok := ps.m[sig]; ok { + return nil + } + name := info.Name() + if len(data) == 0 { + // This can happen is master runs on machine-under-test, + // and it has crashed midway. + log.Printf("removing empty file %v", name) + os.Remove(path) + return nil + } + const hexLen = 2 * sha1.Size + if len(name) > hexLen+1 && isHexString(name[:hexLen]) && name[hexLen] == '.' { + return nil // description file + } + if len(name) != hexLen || !isHexString(name) { + log.Fatalf("unknown file in persistent dir %v: %v", dir, name) + } + if verify != nil && !verify(data) { + os.Remove(path) + return nil + } + if name != hex.EncodeToString(sig[:]) { + log.Printf("bad hash in persistent dir %v for file %v, expect %v", dir, name, hex.EncodeToString(sig[:])) + if err := ioutil.WriteFile(filepath.Join(ps.dir, hex.EncodeToString(sig[:])), data, 0660); err != nil { + log.Fatalf("failed to write file: %v", err) + } + os.Remove(path) + } + ps.m[sig] = data + ps.a = append(ps.a, data) + return nil + }) + return ps +} + +func isHexString(s string) bool { + for _, v := range []byte(s) { + if v >= '0' && v <= '9' || v >= 'a' && v <= 'f' { + continue + } + return false + } + return true +} + +func (ps *PersistentSet) add(data []byte) bool { + sig := hash(data) + if _, ok := ps.m[sig]; ok { + return false + } + data = append([]byte{}, data...) + ps.m[sig] = data + ps.a = append(ps.a, data) + fname := filepath.Join(ps.dir, hex.EncodeToString(sig[:])) + if err := ioutil.WriteFile(fname, data, 0660); err != nil { + log.Fatalf("failed to write file: %v", err) + } + return true +} + +// addDescription creates a complementary to data file on disk. +func (ps *PersistentSet) addDescription(data []byte, desc []byte, typ string) { + sig := hash(data) + fname := filepath.Join(ps.dir, fmt.Sprintf("%v.%v", hex.EncodeToString(sig[:]), typ)) + if err := ioutil.WriteFile(fname, desc, 0660); err != nil { + log.Fatalf("failed to write file: %v", err) + } +} + +func (ps *PersistentSet) minimize(set map[string]bool) { + ps.a = nil + for sig, data := range ps.m { + s := hex.EncodeToString(sig[:]) + if set[s] { + ps.a = append(ps.a, data) + } else { + delete(ps.m, sig) + os.Remove(filepath.Join(ps.dir, s)) + } + } +} diff --git a/tools/execprog/execprog.go b/tools/execprog/execprog.go deleted file mode 100644 index dcb2a0b8b..000000000 --- a/tools/execprog/execprog.go +++ /dev/null @@ -1,169 +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. - -// execprog executes a single program or a set of programs -// and optinally prints information about execution. -package main - -import ( - "bufio" - "bytes" - "encoding/binary" - "flag" - "fmt" - "io/ioutil" - "log" - "os" - "sync" - "time" - - "github.com/google/syzkaller/cover" - "github.com/google/syzkaller/ipc" - "github.com/google/syzkaller/prog" -) - -var ( - flagExecutor = flag.String("executor", "", "path to executor binary") - flagThreaded = flag.Bool("threaded", true, "use threaded mode in executor") - flagCollide = flag.Bool("collide", true, "collide syscalls to provoke data races") - flagDebug = flag.Bool("debug", false, "debug output from executor") - flagStrace = flag.Bool("strace", false, "run executor under strace") - flagCover = flag.Bool("cover", true, "collect coverage") - flagCoverFile = flag.String("coverfile", "", "write coverage to the file") - flagNobody = flag.Bool("nobody", true, "impersonate into nobody") - flagDedup = flag.Bool("dedup", false, "deduplicate coverage in executor") - flagLoop = flag.Bool("loop", false, "execute programs in a loop") - flagProcs = flag.Int("procs", 1, "number of parallel processes to execute programs") - flagTimeout = flag.Duration("timeout", 10*time.Second, "execution timeout") -) - -func main() { - flag.Parse() - if len(flag.Args()) == 0 { - fmt.Fprintf(os.Stderr, "usage: execprog [flags] file-with-programs*\n") - flag.PrintDefaults() - os.Exit(1) - } - - var progs []*prog.Prog - for _, fn := range flag.Args() { - progs = append(progs, parseFile(fn)...) - } - log.Printf("parsed %v programs", len(progs)) - if len(progs) == 0 { - return - } - - var flags uint64 - if *flagThreaded { - flags |= ipc.FlagThreaded - } - if *flagCollide { - flags |= ipc.FlagCollide - } - if *flagDebug { - flags |= ipc.FlagDebug - } - if *flagStrace { - flags |= ipc.FlagStrace - } - if *flagCover || *flagCoverFile != "" { - flags |= ipc.FlagCover - } - if *flagDedup { - flags |= ipc.FlagDedupCover - } - if *flagNobody { - flags |= ipc.FlagDropPrivs - } - - var wg sync.WaitGroup - wg.Add(*flagProcs) - var posMu sync.Mutex - var pos int - var lastPrint time.Time - for p := 0; p < *flagProcs; p++ { - go func() { - env, err := ipc.MakeEnv(*flagExecutor, *flagTimeout, flags) - if err != nil { - log.Fatalf("failed to create ipc env: %v", err) - } - for { - posMu.Lock() - idx := pos - pos++ - if idx%len(progs) == 0 && time.Since(lastPrint) > 5*time.Second { - log.Printf("executed %v programs\n", idx) - lastPrint = time.Now() - } - posMu.Unlock() - if !*flagLoop && idx >= len(progs) { - env.Close() - wg.Done() - return - } - p := progs[idx%len(progs)] - output, strace, cov, failed, hanged, err := env.Exec(p) - if *flagDebug || err != nil { - fmt.Printf("result: failed=%v hanged=%v err=%v\n\n%s", failed, hanged, err, output) - } - if *flagStrace { - fmt.Printf("strace output:\n%s", strace) - } - if *flagCoverFile != "" { - // Coverage is dumped in sanitizer format. - // github.com/google/sanitizers/tools/sancov command can be used to dump PCs, - // then they can be piped via addr2line to symbolize. - for i, c := range cov { - fmt.Printf("call #%v: coverage %v\n", i, len(c)) - if len(c) == 0 { - continue - } - buf := new(bytes.Buffer) - binary.Write(buf, binary.LittleEndian, uint64(0xC0BFFFFFFFFFFF64)) - for _, pc := range c { - binary.Write(buf, binary.LittleEndian, cover.RestorePC(pc)) - } - err := ioutil.WriteFile(fmt.Sprintf("%v.%v", *flagCoverFile, i), buf.Bytes(), 0660) - if err != nil { - log.Fatalf("failed to write coverage file: %v", err) - } - } - } - } - }() - } - wg.Wait() -} - -func parseFile(fn string) []*prog.Prog { - logf, err := os.Open(fn) - if err != nil { - log.Fatalf("failed to open log file: %v", err) - } - log.Printf("parsing log %v", fn) - s := bufio.NewScanner(logf) - var cur []byte - var last *prog.Prog - var progs []*prog.Prog - for s.Scan() { - ln := s.Text() - tmp := append(cur, ln...) - tmp = append(tmp, '\n') - p, err := prog.Deserialize(tmp) - if err == nil { - cur = tmp - last = p - continue - } - if last != nil { - progs = append(progs, last) - last = nil - cur = cur[:0] - } - } - if last != nil { - progs = append(progs, last) - } - return progs -} diff --git a/tools/mutate/mutate.go b/tools/mutate/mutate.go deleted file mode 100644 index 353d60390..000000000 --- a/tools/mutate/mutate.go +++ /dev/null @@ -1,49 +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. - -// mutates mutates a given program and prints result. -package main - -import ( - "flag" - "fmt" - "io/ioutil" - "math/rand" - "os" - "time" - - "github.com/google/syzkaller/prog" -) - -var ( - flagSeed = flag.Int("seed", -1, "prng seed") -) - -func main() { - flag.Parse() - if flag.NArg() != 1 { - fmt.Fprintf(os.Stderr, "usage: mutate program\n") - os.Exit(1) - } - data, err := ioutil.ReadFile(flag.Arg(0)) - if err != nil { - fmt.Fprintf(os.Stderr, "failed to read prog file: %v\n", err) - os.Exit(1) - } - p, err := prog.Deserialize(data) - if err != nil { - fmt.Fprintf(os.Stderr, "failed to deserialize the program: %v\n", err) - os.Exit(1) - } - - prios := prog.CalculatePriorities(nil) - ct := prog.BuildChoiceTable(prios, nil) - - seed := time.Now().UnixNano() - if *flagSeed != -1 { - seed = int64(*flagSeed) - } - rs := rand.NewSource(seed) - p.Mutate(rs, len(p.Calls)+10, ct) - fmt.Printf("%s\n", p.Serialize()) -} diff --git a/tools/prog2c/prog2c.go b/tools/prog2c/prog2c.go deleted file mode 100644 index 9b8e58efa..000000000 --- a/tools/prog2c/prog2c.go +++ /dev/null @@ -1,31 +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 ( - "fmt" - "io/ioutil" - "os" - - "github.com/google/syzkaller/prog" -) - -func main() { - if len(os.Args) != 2 { - fmt.Fprintf(os.Stderr, "usage: prog2c prog_file\n") - os.Exit(1) - } - data, err := ioutil.ReadFile(os.Args[1]) - if err != nil { - fmt.Fprintf(os.Stderr, "failed to read prog file: %v\n", err) - os.Exit(1) - } - p, err := prog.Deserialize(data) - if err != nil { - fmt.Fprintf(os.Stderr, "failed to deserialize the program: %v\n", err) - os.Exit(1) - } - src := p.WriteCSource() - os.Stdout.Write(src) -} diff --git a/tools/stress/stress.go b/tools/stress/stress.go deleted file mode 100644 index e4f1a506c..000000000 --- a/tools/stress/stress.go +++ /dev/null @@ -1,153 +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 ( - "archive/zip" - "flag" - "fmt" - "io/ioutil" - "log" - "math/rand" - "os" - "regexp" - "runtime" - "sync" - "sync/atomic" - "time" - - "github.com/google/syzkaller/ipc" - "github.com/google/syzkaller/prog" -) - -var ( - flagCorpus = flag.String("corpus", "", "corpus directory") - flagExecutor = flag.String("executor", "", "path to executor binary") - flagOutput = flag.Bool("output", false, "print executor output to console") - flagDebug = flag.Bool("debug", false, "executor debug output") - flagProcs = flag.Int("procs", 2*runtime.NumCPU(), "number of parallel processes") - flagThreaded = flag.Bool("threaded", true, "use threaded mode in executor") - flagCollide = flag.Bool("collide", true, "collide syscalls to provoke data races") - flagNobody = flag.Bool("nobody", true, "impersonate into nobody") - flagTimeout = flag.Duration("timeout", 10*time.Second, "executor timeout") - flagLogProg = flag.Bool("logprog", false, "print programs before execution") - - failedRe = regexp.MustCompile("runtime error: |panic: |Panic: ") - - statExec uint64 - gate *ipc.Gate -) - -const programLength = 30 - -func main() { - flag.Parse() - corpus := readCorpus() - log.Printf("parsed %v programs", len(corpus)) - var flags uint64 - if *flagThreaded { - flags |= ipc.FlagThreaded - } - if *flagCollide { - flags |= ipc.FlagCollide - } - if *flagNobody { - flags |= ipc.FlagDropPrivs - } - if *flagDebug { - flags |= ipc.FlagDebug - } - - gate = ipc.NewGate(2 * *flagProcs) - for pid := 0; pid < *flagProcs; pid++ { - pid := pid - go func() { - env, err := ipc.MakeEnv(*flagExecutor, *flagTimeout, flags) - if err != nil { - failf("failed to create execution environment: %v", err) - } - rs := rand.NewSource(time.Now().UnixNano() + int64(pid)*1e12) - rnd := rand.New(rs) - for i := 0; ; i++ { - var p *prog.Prog - if len(corpus) == 0 || i%2 != 0 { - p = prog.Generate(rs, programLength, nil) - execute(pid, env, p) - p.Mutate(rs, programLength, nil) - execute(pid, env, p) - } else { - p = corpus[rnd.Intn(len(corpus))].Clone() - p.Mutate(rs, programLength, nil) - execute(pid, env, p) - p.Mutate(rs, programLength, nil) - execute(pid, env, p) - } - } - }() - } - for range time.NewTicker(5 * time.Second).C { - log.Printf("executed %v programs", atomic.LoadUint64(&statExec)) - } -} - -var outMu sync.Mutex - -func execute(pid int, env *ipc.Env, p *prog.Prog) { - if *flagExecutor == "" { - return - } - atomic.AddUint64(&statExec, 1) - if *flagLogProg { - ticket := gate.Enter() - defer gate.Leave(ticket, nil) - outMu.Lock() - fmt.Printf("executing program %v\n%s\n", pid, p.Serialize()) - outMu.Unlock() - } - - output, _, _, _, _, err := env.Exec(p) - if err != nil { - fmt.Printf("failed to execute executor: %v\n", err) - } - failed := failedRe.Match(output) - if failed { - fmt.Printf("PROGRAM:\n%s\n", p.Serialize()) - } - if failed || *flagOutput { - os.Stdout.Write(output) - } -} - -func readCorpus() []*prog.Prog { - if *flagCorpus == "" { - return nil - } - zipr, err := zip.OpenReader(*flagCorpus) - if err != nil { - failf("failed to open bin file: %v", err) - } - var progs []*prog.Prog - for _, zipf := range zipr.File { - r, err := zipf.Open() - if err != nil { - failf("failed to uzip file from input archive: %v", err) - } - data, err := ioutil.ReadAll(r) - if err != nil { - failf("failed to read corpus file: %v", err) - } - p, err := prog.Deserialize(data) - if err != nil { - failf("failed to deserialize corpus program: %v", err) - } - progs = append(progs, p) - r.Close() - } - zipr.Close() - return progs -} - -func failf(msg string, args ...interface{}) { - log.Fatalf(msg, args...) -} diff --git a/tools/syz-execprog/execprog.go b/tools/syz-execprog/execprog.go new file mode 100644 index 000000000..dcb2a0b8b --- /dev/null +++ b/tools/syz-execprog/execprog.go @@ -0,0 +1,169 @@ +// 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. + +// execprog executes a single program or a set of programs +// and optinally prints information about execution. +package main + +import ( + "bufio" + "bytes" + "encoding/binary" + "flag" + "fmt" + "io/ioutil" + "log" + "os" + "sync" + "time" + + "github.com/google/syzkaller/cover" + "github.com/google/syzkaller/ipc" + "github.com/google/syzkaller/prog" +) + +var ( + flagExecutor = flag.String("executor", "", "path to executor binary") + flagThreaded = flag.Bool("threaded", true, "use threaded mode in executor") + flagCollide = flag.Bool("collide", true, "collide syscalls to provoke data races") + flagDebug = flag.Bool("debug", false, "debug output from executor") + flagStrace = flag.Bool("strace", false, "run executor under strace") + flagCover = flag.Bool("cover", true, "collect coverage") + flagCoverFile = flag.String("coverfile", "", "write coverage to the file") + flagNobody = flag.Bool("nobody", true, "impersonate into nobody") + flagDedup = flag.Bool("dedup", false, "deduplicate coverage in executor") + flagLoop = flag.Bool("loop", false, "execute programs in a loop") + flagProcs = flag.Int("procs", 1, "number of parallel processes to execute programs") + flagTimeout = flag.Duration("timeout", 10*time.Second, "execution timeout") +) + +func main() { + flag.Parse() + if len(flag.Args()) == 0 { + fmt.Fprintf(os.Stderr, "usage: execprog [flags] file-with-programs*\n") + flag.PrintDefaults() + os.Exit(1) + } + + var progs []*prog.Prog + for _, fn := range flag.Args() { + progs = append(progs, parseFile(fn)...) + } + log.Printf("parsed %v programs", len(progs)) + if len(progs) == 0 { + return + } + + var flags uint64 + if *flagThreaded { + flags |= ipc.FlagThreaded + } + if *flagCollide { + flags |= ipc.FlagCollide + } + if *flagDebug { + flags |= ipc.FlagDebug + } + if *flagStrace { + flags |= ipc.FlagStrace + } + if *flagCover || *flagCoverFile != "" { + flags |= ipc.FlagCover + } + if *flagDedup { + flags |= ipc.FlagDedupCover + } + if *flagNobody { + flags |= ipc.FlagDropPrivs + } + + var wg sync.WaitGroup + wg.Add(*flagProcs) + var posMu sync.Mutex + var pos int + var lastPrint time.Time + for p := 0; p < *flagProcs; p++ { + go func() { + env, err := ipc.MakeEnv(*flagExecutor, *flagTimeout, flags) + if err != nil { + log.Fatalf("failed to create ipc env: %v", err) + } + for { + posMu.Lock() + idx := pos + pos++ + if idx%len(progs) == 0 && time.Since(lastPrint) > 5*time.Second { + log.Printf("executed %v programs\n", idx) + lastPrint = time.Now() + } + posMu.Unlock() + if !*flagLoop && idx >= len(progs) { + env.Close() + wg.Done() + return + } + p := progs[idx%len(progs)] + output, strace, cov, failed, hanged, err := env.Exec(p) + if *flagDebug || err != nil { + fmt.Printf("result: failed=%v hanged=%v err=%v\n\n%s", failed, hanged, err, output) + } + if *flagStrace { + fmt.Printf("strace output:\n%s", strace) + } + if *flagCoverFile != "" { + // Coverage is dumped in sanitizer format. + // github.com/google/sanitizers/tools/sancov command can be used to dump PCs, + // then they can be piped via addr2line to symbolize. + for i, c := range cov { + fmt.Printf("call #%v: coverage %v\n", i, len(c)) + if len(c) == 0 { + continue + } + buf := new(bytes.Buffer) + binary.Write(buf, binary.LittleEndian, uint64(0xC0BFFFFFFFFFFF64)) + for _, pc := range c { + binary.Write(buf, binary.LittleEndian, cover.RestorePC(pc)) + } + err := ioutil.WriteFile(fmt.Sprintf("%v.%v", *flagCoverFile, i), buf.Bytes(), 0660) + if err != nil { + log.Fatalf("failed to write coverage file: %v", err) + } + } + } + } + }() + } + wg.Wait() +} + +func parseFile(fn string) []*prog.Prog { + logf, err := os.Open(fn) + if err != nil { + log.Fatalf("failed to open log file: %v", err) + } + log.Printf("parsing log %v", fn) + s := bufio.NewScanner(logf) + var cur []byte + var last *prog.Prog + var progs []*prog.Prog + for s.Scan() { + ln := s.Text() + tmp := append(cur, ln...) + tmp = append(tmp, '\n') + p, err := prog.Deserialize(tmp) + if err == nil { + cur = tmp + last = p + continue + } + if last != nil { + progs = append(progs, last) + last = nil + cur = cur[:0] + } + } + if last != nil { + progs = append(progs, last) + } + return progs +} diff --git a/tools/syz-mutate/mutate.go b/tools/syz-mutate/mutate.go new file mode 100644 index 000000000..353d60390 --- /dev/null +++ b/tools/syz-mutate/mutate.go @@ -0,0 +1,49 @@ +// 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. + +// mutates mutates a given program and prints result. +package main + +import ( + "flag" + "fmt" + "io/ioutil" + "math/rand" + "os" + "time" + + "github.com/google/syzkaller/prog" +) + +var ( + flagSeed = flag.Int("seed", -1, "prng seed") +) + +func main() { + flag.Parse() + if flag.NArg() != 1 { + fmt.Fprintf(os.Stderr, "usage: mutate program\n") + os.Exit(1) + } + data, err := ioutil.ReadFile(flag.Arg(0)) + if err != nil { + fmt.Fprintf(os.Stderr, "failed to read prog file: %v\n", err) + os.Exit(1) + } + p, err := prog.Deserialize(data) + if err != nil { + fmt.Fprintf(os.Stderr, "failed to deserialize the program: %v\n", err) + os.Exit(1) + } + + prios := prog.CalculatePriorities(nil) + ct := prog.BuildChoiceTable(prios, nil) + + seed := time.Now().UnixNano() + if *flagSeed != -1 { + seed = int64(*flagSeed) + } + rs := rand.NewSource(seed) + p.Mutate(rs, len(p.Calls)+10, ct) + fmt.Printf("%s\n", p.Serialize()) +} diff --git a/tools/syz-prog2c/prog2c.go b/tools/syz-prog2c/prog2c.go new file mode 100644 index 000000000..9b8e58efa --- /dev/null +++ b/tools/syz-prog2c/prog2c.go @@ -0,0 +1,31 @@ +// 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 ( + "fmt" + "io/ioutil" + "os" + + "github.com/google/syzkaller/prog" +) + +func main() { + if len(os.Args) != 2 { + fmt.Fprintf(os.Stderr, "usage: prog2c prog_file\n") + os.Exit(1) + } + data, err := ioutil.ReadFile(os.Args[1]) + if err != nil { + fmt.Fprintf(os.Stderr, "failed to read prog file: %v\n", err) + os.Exit(1) + } + p, err := prog.Deserialize(data) + if err != nil { + fmt.Fprintf(os.Stderr, "failed to deserialize the program: %v\n", err) + os.Exit(1) + } + src := p.WriteCSource() + os.Stdout.Write(src) +} diff --git a/tools/syz-stress/stress.go b/tools/syz-stress/stress.go new file mode 100644 index 000000000..e4f1a506c --- /dev/null +++ b/tools/syz-stress/stress.go @@ -0,0 +1,153 @@ +// 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 ( + "archive/zip" + "flag" + "fmt" + "io/ioutil" + "log" + "math/rand" + "os" + "regexp" + "runtime" + "sync" + "sync/atomic" + "time" + + "github.com/google/syzkaller/ipc" + "github.com/google/syzkaller/prog" +) + +var ( + flagCorpus = flag.String("corpus", "", "corpus directory") + flagExecutor = flag.String("executor", "", "path to executor binary") + flagOutput = flag.Bool("output", false, "print executor output to console") + flagDebug = flag.Bool("debug", false, "executor debug output") + flagProcs = flag.Int("procs", 2*runtime.NumCPU(), "number of parallel processes") + flagThreaded = flag.Bool("threaded", true, "use threaded mode in executor") + flagCollide = flag.Bool("collide", true, "collide syscalls to provoke data races") + flagNobody = flag.Bool("nobody", true, "impersonate into nobody") + flagTimeout = flag.Duration("timeout", 10*time.Second, "executor timeout") + flagLogProg = flag.Bool("logprog", false, "print programs before execution") + + failedRe = regexp.MustCompile("runtime error: |panic: |Panic: ") + + statExec uint64 + gate *ipc.Gate +) + +const programLength = 30 + +func main() { + flag.Parse() + corpus := readCorpus() + log.Printf("parsed %v programs", len(corpus)) + var flags uint64 + if *flagThreaded { + flags |= ipc.FlagThreaded + } + if *flagCollide { + flags |= ipc.FlagCollide + } + if *flagNobody { + flags |= ipc.FlagDropPrivs + } + if *flagDebug { + flags |= ipc.FlagDebug + } + + gate = ipc.NewGate(2 * *flagProcs) + for pid := 0; pid < *flagProcs; pid++ { + pid := pid + go func() { + env, err := ipc.MakeEnv(*flagExecutor, *flagTimeout, flags) + if err != nil { + failf("failed to create execution environment: %v", err) + } + rs := rand.NewSource(time.Now().UnixNano() + int64(pid)*1e12) + rnd := rand.New(rs) + for i := 0; ; i++ { + var p *prog.Prog + if len(corpus) == 0 || i%2 != 0 { + p = prog.Generate(rs, programLength, nil) + execute(pid, env, p) + p.Mutate(rs, programLength, nil) + execute(pid, env, p) + } else { + p = corpus[rnd.Intn(len(corpus))].Clone() + p.Mutate(rs, programLength, nil) + execute(pid, env, p) + p.Mutate(rs, programLength, nil) + execute(pid, env, p) + } + } + }() + } + for range time.NewTicker(5 * time.Second).C { + log.Printf("executed %v programs", atomic.LoadUint64(&statExec)) + } +} + +var outMu sync.Mutex + +func execute(pid int, env *ipc.Env, p *prog.Prog) { + if *flagExecutor == "" { + return + } + atomic.AddUint64(&statExec, 1) + if *flagLogProg { + ticket := gate.Enter() + defer gate.Leave(ticket, nil) + outMu.Lock() + fmt.Printf("executing program %v\n%s\n", pid, p.Serialize()) + outMu.Unlock() + } + + output, _, _, _, _, err := env.Exec(p) + if err != nil { + fmt.Printf("failed to execute executor: %v\n", err) + } + failed := failedRe.Match(output) + if failed { + fmt.Printf("PROGRAM:\n%s\n", p.Serialize()) + } + if failed || *flagOutput { + os.Stdout.Write(output) + } +} + +func readCorpus() []*prog.Prog { + if *flagCorpus == "" { + return nil + } + zipr, err := zip.OpenReader(*flagCorpus) + if err != nil { + failf("failed to open bin file: %v", err) + } + var progs []*prog.Prog + for _, zipf := range zipr.File { + r, err := zipf.Open() + if err != nil { + failf("failed to uzip file from input archive: %v", err) + } + data, err := ioutil.ReadAll(r) + if err != nil { + failf("failed to read corpus file: %v", err) + } + p, err := prog.Deserialize(data) + if err != nil { + failf("failed to deserialize corpus program: %v", err) + } + progs = append(progs, p) + r.Close() + } + zipr.Close() + return progs +} + +func failf(msg string, args ...interface{}) { + log.Fatalf(msg, args...) +} 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) -- cgit mrf-deployment