diff options
| author | Dmitry Vyukov <dvyukov@google.com> | 2015-10-12 10:16:57 +0200 |
|---|---|---|
| committer | Dmitry Vyukov <dvyukov@google.com> | 2015-10-12 10:16:57 +0200 |
| commit | 874c5754bb22dbf77d6b600ff91f0f4f1fc5073a (patch) | |
| tree | 0075fbd088046ad5c86e6e972235701d68b3ce7c /vm | |
initial commit
Diffstat (limited to 'vm')
| -rw-r--r-- | vm/local/local.go | 117 | ||||
| -rw-r--r-- | vm/qemu/qemu.go | 478 | ||||
| -rw-r--r-- | vm/vm.go | 28 |
3 files changed, 623 insertions, 0 deletions
diff --git a/vm/local/local.go b/vm/local/local.go new file mode 100644 index 000000000..3966771d0 --- /dev/null +++ b/vm/local/local.go @@ -0,0 +1,117 @@ +// 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 qemu + +import ( + "bytes" + "encoding/json" + "fmt" + "log" + "os" + "os/exec" + "syscall" + "time" + + "github.com/google/syzkaller/vm" +) + +func init() { + vm.Register("local", ctor) +} + +type local struct { + params + workdir string + syscalls map[int]bool + id int + mgrPort int +} + +type params struct { + Fuzzer string + Executor string + Parallel int +} + +func ctor(workdir string, syscalls map[int]bool, port, index int, paramsData []byte) (vm.Instance, error) { + p := new(params) + if err := json.Unmarshal(paramsData, p); err != nil { + return nil, fmt.Errorf("failed to unmarshal local params: %v", err) + } + if _, err := os.Stat(p.Fuzzer); err != nil { + return nil, fmt.Errorf("fuzzer binary '%v' does not exist: %v", p.Fuzzer, err) + } + if _, err := os.Stat(p.Executor); err != nil { + return nil, fmt.Errorf("executor binary '%v' does not exist: %v", p.Executor, err) + } + if p.Parallel == 0 { + p.Parallel = 1 + } + if p.Parallel <= 0 || p.Parallel > 100 { + return nil, fmt.Errorf("bad parallel param: %v, want [1-100]", p.Parallel) + } + + os.MkdirAll(workdir, 0770) + + // Disable annoying segfault dmesg messages, fuzzer is going to crash a lot. + etrace, err := os.Open("/proc/sys/debug/exception-trace") + if err == nil { + etrace.Write([]byte{'0'}) + etrace.Close() + } + + // Don't write executor core files. + syscall.Setrlimit(syscall.RLIMIT_CORE, &syscall.Rlimit{0, 0}) + + loc := &local{ + params: *p, + workdir: workdir, + syscalls: syscalls, + id: index, + mgrPort: port, + } + return loc, nil +} + +func (loc *local) Run() { + name := fmt.Sprintf("local-%v", loc.id) + log.Printf("%v: started\n", name) + for run := 0; ; run++ { + cmd := exec.Command(loc.Fuzzer, "-name", name, "-saveprog", "-executor", loc.Executor, + "-manager", fmt.Sprintf("localhost:%v", loc.mgrPort), "-parallel", fmt.Sprintf("%v", loc.Parallel)) + if len(loc.syscalls) != 0 { + buf := new(bytes.Buffer) + for c := range loc.syscalls { + fmt.Fprintf(buf, ",%v", c) + } + cmd.Args = append(cmd.Args, "-calls="+buf.String()[1:]) + } + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Dir = loc.workdir + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} + if err := cmd.Start(); err != nil { + log.Printf("failed to start fuzzer binary: %v", err) + time.Sleep(10 * time.Second) + continue + } + pid := cmd.Process.Pid + done := make(chan bool) + go func() { + select { + case <-done: + case <-time.After(time.Hour): + log.Printf("%v: running for long enough, restarting", name) + syscall.Kill(-pid, syscall.SIGKILL) + syscall.Kill(-pid, syscall.SIGKILL) + syscall.Kill(pid, syscall.SIGKILL) + syscall.Kill(pid, syscall.SIGKILL) + } + }() + err := cmd.Wait() + close(done) + log.Printf("fuzzer binary exited: %v", err) + time.Sleep(10 * time.Second) + } +} diff --git a/vm/qemu/qemu.go b/vm/qemu/qemu.go new file mode 100644 index 000000000..08719b0e9 --- /dev/null +++ b/vm/qemu/qemu.go @@ -0,0 +1,478 @@ +// 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 qemu + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "log" + "net" + "os" + "os/exec" + "path/filepath" + "regexp" + "strconv" + "sync" + "time" + + "github.com/google/syzkaller/vm" +) + +const hostAddr = "10.0.2.10" +const logOutput = false + +func init() { + vm.Register("qemu", ctor) +} + +type qemu struct { + params + workdir string + crashdir string + callsFlag string + id int + mgrPort int +} + +type params struct { + Qemu string + Kernel string + Image string + Sshkey string + Fuzzer string + Executor string + Port int + Cpu int + Mem int + Parallel int +} + +func ctor(workdir string, syscalls map[int]bool, port, index int, paramsData []byte) (vm.Instance, error) { + p := new(params) + if err := json.Unmarshal(paramsData, p); err != nil { + return nil, fmt.Errorf("failed to unmarshal qemu params: %v", err) + } + if _, err := os.Stat(p.Image); err != nil { + return nil, fmt.Errorf("image file '%v' does not exist: %v", p.Image, err) + } + if _, err := os.Stat(p.Kernel); err != nil { + return nil, fmt.Errorf("kernel file '%v' does not exist: %v", p.Kernel, err) + } + if _, err := os.Stat(p.Sshkey); err != nil { + return nil, fmt.Errorf("ssh key '%v' does not exist: %v", p.Sshkey, err) + } + if _, err := os.Stat(p.Fuzzer); err != nil { + return nil, fmt.Errorf("fuzzer binary '%v' does not exist: %v", p.Fuzzer, err) + } + if _, err := os.Stat(p.Executor); err != nil { + return nil, fmt.Errorf("executor binary '%v' does not exist: %v", p.Executor, err) + } + if p.Qemu == "" { + p.Qemu = "qemu-system-x86_64" + } + if p.Port <= 1024 || p.Port >= 64<<10 { + return nil, fmt.Errorf("bad qemu port: %v, want (1024-65536)", p.Port) + } + p.Port += index + if p.Cpu <= 0 || p.Cpu > 1024 { + return nil, fmt.Errorf("bad qemu cpu: %v, want [1-1024]", p.Cpu) + } + if p.Mem < 128 || p.Mem > 1048576 { + return nil, fmt.Errorf("bad qemu mem: %v, want [128-1048576]", p.Mem) + } + if p.Parallel == 0 { + p.Parallel = 1 + } + if p.Parallel <= 0 || p.Parallel > 100 { + return nil, fmt.Errorf("bad parallel param: %v, want [1-100]", p.Parallel) + } + + crashdir := filepath.Join(workdir, "crashes") + os.MkdirAll(crashdir, 0770) + + workdir = filepath.Join(workdir, "qemu") + os.MkdirAll(workdir, 0770) + + q := &qemu{ + params: *p, + workdir: workdir, + crashdir: crashdir, + id: index, + mgrPort: port, + } + + if len(syscalls) != 0 { + buf := new(bytes.Buffer) + for c := range syscalls { + fmt.Fprintf(buf, ",%v", c) + } + q.callsFlag = "-calls=" + buf.String()[1:] + } + + return q, nil +} + +func (q *qemu) Run() { + log.Printf("qemu/%v: started\n", q.id) + imagename := filepath.Join(q.workdir, fmt.Sprintf("image%v", q.id)) + for run := 0; ; run++ { + logname := filepath.Join(q.workdir, fmt.Sprintf("log%v-%v-%v", q.id, run, time.Now().Unix())) + var logf *os.File + if logOutput { + var err error + logf, err = os.Create(logname) + if err != nil { + log.Printf("failed to create log file: %v", err) + time.Sleep(10 * time.Second) + continue + } + } + rpipe, wpipe, err := os.Pipe() + if err != nil { + log.Printf("failed to create pipe: %v", err) + if logf != nil { + logf.Close() + } + time.Sleep(10 * time.Second) + continue + } + os.Remove(imagename) + if err := copyFile(q.Image, imagename); err != nil { + log.Printf("failed to copy image file: %v", err) + if logf != nil { + logf.Close() + } + rpipe.Close() + wpipe.Close() + time.Sleep(10 * time.Second) + continue + } + inst := &Instance{ + id: q.id, + crashdir: q.crashdir, + params: q.params, + name: fmt.Sprintf("qemu/%v-%v", q.id, run), + image: imagename, + callsFlag: q.callsFlag, + log: logf, + rpipe: rpipe, + wpipe: wpipe, + mgrPort: q.mgrPort, + cmds: make(map[*Command]bool), + } + inst.Run() + inst.Shutdown() + time.Sleep(10 * time.Second) + } +} + +type Instance struct { + params + sync.Mutex + id int + crashdir string + name string + image string + callsFlag string + log *os.File + rpipe *os.File + wpipe *os.File + mgrPort int + cmds map[*Command]bool + qemu *Command +} + +type Command struct { + sync.Mutex + cmd *exec.Cmd + done chan struct{} + failed bool + out []byte + outpos int +} + +func (inst *Instance) Run() { + var outputMu sync.Mutex + var output []byte + go func() { + var buf [64 << 10]byte + for { + n, err := inst.rpipe.Read(buf[:]) + if n != 0 { + outputMu.Lock() + output = append(output, buf[:n]...) + outputMu.Unlock() + if inst.log != nil { + inst.log.Write(buf[:n]) + } + } + if err != nil { + break + } + } + }() + + // Start the instance. + // TODO: ignores inst.Cpu + inst.qemu = inst.CreateCommand(inst.Qemu, + "-hda", inst.image, + "-m", strconv.Itoa(inst.Mem), + "-net", "nic", + "-net", fmt.Sprintf("user,host=%v,hostfwd=tcp::%v-:22", hostAddr, inst.Port), + "-nographic", + "-kernel", inst.Kernel, + "-append", "console=ttyS0 root=/dev/sda debug earlyprintk=serial slub_debug=UZ", + "-enable-kvm", + "-numa", "node,nodeid=0,cpus=0-1", "-numa", "node,nodeid=1,cpus=2-3", + "-smp", "sockets=2,cores=2,threads=1", + "-usb", "-usbdevice", "mouse", "-usbdevice", "tablet", + ) + // Wait for ssh server. + time.Sleep(10 * time.Second) + start := time.Now() + for { + c, err := net.DialTimeout("tcp", fmt.Sprintf("localhost:%v", inst.Port), 3*time.Second) + if err == nil { + c.SetDeadline(time.Now().Add(3 * time.Second)) + var tmp [1]byte + n, err := c.Read(tmp[:]) + c.Close() + if err == nil && n > 0 { + // ssh is up and responding. + break + } + c.Close() + time.Sleep(3 * time.Second) + } + if inst.qemu.Exited() { + output = append(output, "qemu stopped\n"...) + inst.SaveCrasher(output) + inst.Logf("qemu stopped") + return + } + if time.Since(start) > 10*time.Minute { + outputMu.Lock() + output = append(output, "ssh server did not start\n"...) + inst.SaveCrasher(output) + outputMu.Unlock() + inst.Logf("ssh server did not start") + return + } + } + 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) { + inst.Logf("failed to scp binaries into the instance") + return + } + + // Disable annoying segfault dmesg messages, fuzzer is going to crash a lot. + inst.CreateSSHCommand("echo -n 0 > /proc/sys/debug/exception-trace").Wait(10 * time.Second) + + // Run the binary. + cmd := inst.CreateSSHCommand(fmt.Sprintf("/syzkaller_fuzzer -name %v -executor /syzkaller_executor -manager %v:%v -parallel %v %v", + inst.name, hostAddr, inst.mgrPort, inst.Parallel, inst.callsFlag)) + + deadline := start.Add(time.Hour) + lastOutput := time.Now() + lastOutputLen := 0 + matchPos := 0 + crashRe := regexp.MustCompile("\\[ cut here \\]|Kernel panic| BUG: | WARNING: | INFO: |unable to handle kernel NULL pointer dereference|general protection fault") + const contextSize = 64 << 10 + for range time.NewTicker(5 * time.Second).C { + outputMu.Lock() + if lastOutputLen != len(output) { + lastOutput = time.Now() + } + if loc := crashRe.FindAllIndex(output[matchPos:], -1); len(loc) != 0 { + // Give it some time to finish writing the error message. + outputMu.Unlock() + time.Sleep(5 * time.Second) + outputMu.Lock() + loc = crashRe.FindAllIndex(output[matchPos:], -1) + start := loc[0][0] - contextSize + if start < 0 { + start = 0 + } + end := loc[len(loc)-1][1] + contextSize + if end > len(output) { + end = len(output) + } + inst.SaveCrasher(output[start:end]) + } + if len(output) > 2*contextSize { + copy(output, output[len(output)-contextSize:]) + output = output[:contextSize] + } + matchPos = len(output) - 128 + if matchPos < 0 { + matchPos = 0 + } + lastOutputLen = len(output) + outputMu.Unlock() + + if time.Since(lastOutput) > 3*time.Minute { + time.Sleep(time.Second) + outputMu.Lock() + output = append(output, "no output from fuzzer, restarting\n"...) + inst.SaveCrasher(output) + outputMu.Unlock() + inst.Logf("no output from fuzzer, restarting") + cmd.cmd.Process.Kill() + cmd.cmd.Process.Kill() + return + } + if cmd.Exited() { + time.Sleep(time.Second) + outputMu.Lock() + output = append(output, "fuzzer binary stopped or lost connection\n"...) + inst.SaveCrasher(output) + inst.Logf("fuzzer binary stopped or lost connection") + outputMu.Unlock() + return + } + if time.Now().After(deadline) { + inst.Logf("running for long enough, restarting") + cmd.cmd.Process.Kill() + cmd.cmd.Process.Kill() + return + } + } +} + +func (inst *Instance) SaveCrasher(output []byte) { + ioutil.WriteFile(filepath.Join(inst.crashdir, fmt.Sprintf("crash%v-%v", inst.id, time.Now().UnixNano())), output, 0660) +} + +func (inst *Instance) Shutdown() { + defer func() { + os.Remove(inst.image) + inst.rpipe.Close() + inst.wpipe.Close() + if inst.log != nil { + inst.log.Close() + } + }() + if inst.qemu.cmd == nil { + // CreateCommand should have been failed very early. + return + } + for try := 0; try < 10; try++ { + inst.qemu.cmd.Process.Kill() + time.Sleep(time.Second) + inst.Lock() + n := len(inst.cmds) + inst.Unlock() + if n == 0 { + return + } + } + inst.Logf("hanged processes after kill") + inst.Lock() + for cmd := range inst.cmds { + cmd.cmd.Process.Kill() + cmd.cmd.Process.Kill() + } + inst.Unlock() + time.Sleep(3 * time.Second) +} + +func (inst *Instance) CreateCommand(args ...string) *Command { + if inst.log != nil { + fmt.Fprintf(inst.log, "executing command: %v\n", args) + } + cmd := &Command{} + cmd.done = make(chan struct{}) + cmd.cmd = exec.Command(args[0], args[1:]...) + cmd.cmd.Stdout = inst.wpipe + cmd.cmd.Stderr = inst.wpipe + if err := cmd.cmd.Start(); err != nil { + inst.Logf("failed to start command '%v': %v\n", args, err) + cmd.failed = true + close(cmd.done) + return cmd + } + inst.Lock() + inst.cmds[cmd] = true + inst.Unlock() + go func() { + err := cmd.cmd.Wait() + inst.Lock() + delete(inst.cmds, cmd) + inst.Unlock() + if inst.log != nil { + fmt.Fprintf(inst.log, "command '%v' exited: %v\n", args, err) + } + cmd.failed = err != nil + close(cmd.done) + }() + return cmd +} + +func (inst *Instance) CreateSSHCommand(args ...string) *Command { + args1 := []string{"ssh", "-i", inst.Sshkey, "-p", strconv.Itoa(inst.Port), + "-o", "ConnectionAttempts=10", "-o", "ConnectTimeout=10", + "-o", "BatchMode=yes", "-o", "UserKnownHostsFile=/dev/null", + "-o", "StrictHostKeyChecking=no", "root@localhost"} + return inst.CreateCommand(append(args1, args...)...) +} + +func (inst *Instance) CreateSCPCommand(from, to string) *Command { + return inst.CreateCommand("scp", "-i", inst.Sshkey, "-P", strconv.Itoa(inst.Port), + "-o", "ConnectionAttempts=10", "-o", "ConnectTimeout=10", + "-o", "BatchMode=yes", "-o", "UserKnownHostsFile=/dev/null", + "-o", "StrictHostKeyChecking=no", + from, "root@localhost:"+to) +} + +func (inst *Instance) Logf(str string, args ...interface{}) { + fmt.Fprintf(inst.wpipe, str+"\n", args...) + log.Printf("%v: "+str, append([]interface{}{inst.name}, args...)...) +} + +func (cmd *Command) Wait(max time.Duration) bool { + select { + case <-cmd.done: + return !cmd.failed + case <-time.After(max): + return false + } +} + +func (cmd *Command) Exited() bool { + select { + case <-cmd.done: + return true + default: + return false + } +} + +var copySem sync.Mutex + +func copyFile(oldfn, newfn string) error { + copySem.Lock() + defer copySem.Unlock() + + oldf, err := os.Open(oldfn) + if err != nil { + return err + } + defer oldf.Close() + newf, err := os.Create(newfn) + if err != nil { + return err + } + defer newf.Close() + _, err = io.Copy(newf, oldf) + if err != nil { + return err + } + return nil +} diff --git a/vm/vm.go b/vm/vm.go new file mode 100644 index 000000000..b9eded0c8 --- /dev/null +++ b/vm/vm.go @@ -0,0 +1,28 @@ +// 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 vm + +import ( + "fmt" +) + +type Instance interface { + Run() +} + +type ctorFunc func(workdir string, syscalls map[int]bool, port, index int, params []byte) (Instance, error) + +var ctors = make(map[string]ctorFunc) + +func Register(typ string, ctor ctorFunc) { + ctors[typ] = ctor +} + +func Create(typ string, workdir string, syscalls map[int]bool, port, index int, params []byte) (Instance, error) { + ctor := ctors[typ] + if ctor == nil { + return nil, fmt.Errorf("unknown instance type '%v'", typ) + } + return ctor(workdir, syscalls, port, index, params) +} |
