aboutsummaryrefslogtreecommitdiffstats
path: root/vm
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2015-10-12 10:16:57 +0200
committerDmitry Vyukov <dvyukov@google.com>2015-10-12 10:16:57 +0200
commit874c5754bb22dbf77d6b600ff91f0f4f1fc5073a (patch)
tree0075fbd088046ad5c86e6e972235701d68b3ce7c /vm
initial commit
Diffstat (limited to 'vm')
-rw-r--r--vm/local/local.go117
-rw-r--r--vm/qemu/qemu.go478
-rw-r--r--vm/vm.go28
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)
+}