diff options
| -rw-r--r-- | config/config.go | 71 | ||||
| -rw-r--r-- | csource/common.go | 2 | ||||
| -rw-r--r-- | executor/common.h | 2 | ||||
| -rw-r--r-- | syz-manager/manager.go | 1 | ||||
| -rw-r--r-- | tools/syz-repro/repro.go | 1 | ||||
| -rw-r--r-- | vm/adb/adb.go | 6 | ||||
| -rw-r--r-- | vm/console.go (renamed from vm/adb/console.go) | 35 | ||||
| -rw-r--r-- | vm/odroid/odroid.go | 368 | ||||
| -rw-r--r-- | vm/vm.go | 38 |
9 files changed, 470 insertions, 54 deletions
diff --git a/config/config.go b/config/config.go index 410d9c663..aae8de35b 100644 --- a/config/config.go +++ b/config/config.go @@ -56,6 +56,13 @@ type Config struct { Machine_Type string // GCE machine type (e.g. "n1-highcpu-2") + Odroid_Host_Addr string // ip address of the host machine + Odroid_Slave_Addr string // ip address of the Odroid board + Odroid_Console string // console device name (e.g. "/dev/ttyUSB0") + Odroid_Hub_Bus int // host USB bus number for the USB hub + Odroid_Hub_Device int // host USB device number for the USB hub + Odroid_Hub_Port int // port on the USB hub to which Odroid is connected + Cover bool // use kcov coverage (default: true) Leak bool // do memory leak checking Reproduce bool // reproduce, localize and minimize crashers (on by default) @@ -138,6 +145,28 @@ func parse(data []byte) (*Config, map[int]bool, error) { return nil, nil, fmt.Errorf("machine_type parameter is empty (required for gce)") } fallthrough + case "odroid": + if cfg.Count != 1 { + return nil, nil, fmt.Errorf("no support for multiple Odroid devices yet, count should be 1") + } + if cfg.Odroid_Host_Addr == "" { + return nil, nil, fmt.Errorf("config param odroid_host_addr is empty") + } + if cfg.Odroid_Slave_Addr == "" { + return nil, nil, fmt.Errorf("config param odroid_slave_addr is empty") + } + if cfg.Odroid_Console == "" { + return nil, nil, fmt.Errorf("config param odroid_console is empty") + } + if cfg.Odroid_Hub_Bus == 0 { + return nil, nil, fmt.Errorf("config param odroid_hub_bus is empty") + } + if cfg.Odroid_Hub_Device == 0 { + return nil, nil, fmt.Errorf("config param odroid_hub_device is empty") + } + if cfg.Odroid_Hub_Port == 0 { + return nil, nil, fmt.Errorf("config param odroid_hub_port is empty") + } default: if cfg.Count <= 0 || cfg.Count > 1000 { return nil, nil, fmt.Errorf("invalid config param count: %v, want (1, 1000]", cfg.Count) @@ -308,21 +337,27 @@ func CreateVMConfig(cfg *Config, index int) (*vm.Config, error) { return nil, fmt.Errorf("failed to create instance temp dir: %v", err) } vmCfg := &vm.Config{ - Name: fmt.Sprintf("%v-%v-%v", cfg.Type, cfg.Name, index), - Index: index, - Workdir: workdir, - Bin: cfg.Bin, - BinArgs: cfg.Bin_Args, - Kernel: cfg.Kernel, - Cmdline: cfg.Cmdline, - Image: cfg.Image, - Initrd: cfg.Initrd, - Sshkey: cfg.Sshkey, - Executor: filepath.Join(cfg.Syzkaller, "bin", "syz-executor"), - Cpu: cfg.Cpu, - Mem: cfg.Mem, - Debug: cfg.Debug, - MachineType: cfg.Machine_Type, + Name: fmt.Sprintf("%v-%v-%v", cfg.Type, cfg.Name, index), + Index: index, + Workdir: workdir, + Bin: cfg.Bin, + BinArgs: cfg.Bin_Args, + Kernel: cfg.Kernel, + Cmdline: cfg.Cmdline, + Image: cfg.Image, + Initrd: cfg.Initrd, + Sshkey: cfg.Sshkey, + Executor: filepath.Join(cfg.Syzkaller, "bin", "syz-executor"), + Cpu: cfg.Cpu, + Mem: cfg.Mem, + Debug: cfg.Debug, + MachineType: cfg.Machine_Type, + OdroidHostAddr: cfg.Odroid_Host_Addr, + OdroidSlaveAddr: cfg.Odroid_Slave_Addr, + OdroidConsole: cfg.Odroid_Console, + OdroidHubBus: cfg.Odroid_Hub_Bus, + OdroidHubDevice: cfg.Odroid_Hub_Device, + OdroidHubPort: cfg.Odroid_Hub_Port, } if len(cfg.Devices) != 0 { vmCfg.Device = cfg.Devices[index] @@ -369,6 +404,12 @@ func checkUnknownFields(data []byte) (string, error) { "Ignores", "Initrd", "Machine_Type", + "Odroid_Host_Addr", + "Odroid_Slave_Addr", + "Odroid_Console", + "Odroid_Hub_Bus", + "Odroid_Hub_Device", + "Odroid_Hub_Port", } f := make(map[string]interface{}) if err := json.Unmarshal(data, &f); err != nil { diff --git a/csource/common.go b/csource/common.go index fb1f56517..f09cbf24d 100644 --- a/csource/common.go +++ b/csource/common.go @@ -1540,7 +1540,7 @@ static int do_sandbox_namespace(int executor_pid, bool enable_tun) epid = executor_pid; etun = enable_tun; mprotect(sandbox_stack, 4096, PROT_NONE); - return clone(namespace_sandbox_proc, &sandbox_stack[sizeof(sandbox_stack) - 8], + return clone(namespace_sandbox_proc, &sandbox_stack[sizeof(sandbox_stack) - 64], CLONE_NEWUSER | CLONE_NEWPID | CLONE_NEWUTS | CLONE_NEWNET, NULL); } #endif diff --git a/executor/common.h b/executor/common.h index b1084eb0c..4983802f2 100644 --- a/executor/common.h +++ b/executor/common.h @@ -633,7 +633,7 @@ static int do_sandbox_namespace(int executor_pid, bool enable_tun) epid = executor_pid; etun = enable_tun; mprotect(sandbox_stack, 4096, PROT_NONE); // to catch stack underflows - return clone(namespace_sandbox_proc, &sandbox_stack[sizeof(sandbox_stack) - 8], + return clone(namespace_sandbox_proc, &sandbox_stack[sizeof(sandbox_stack) - 64], CLONE_NEWUSER | CLONE_NEWPID | CLONE_NEWUTS | CLONE_NEWNET, NULL); } #endif diff --git a/syz-manager/manager.go b/syz-manager/manager.go index 6806061c3..6bf9d6e9a 100644 --- a/syz-manager/manager.go +++ b/syz-manager/manager.go @@ -35,6 +35,7 @@ import ( _ "github.com/google/syzkaller/vm/gce" _ "github.com/google/syzkaller/vm/kvm" _ "github.com/google/syzkaller/vm/local" + _ "github.com/google/syzkaller/vm/odroid" _ "github.com/google/syzkaller/vm/qemu" ) diff --git a/tools/syz-repro/repro.go b/tools/syz-repro/repro.go index e2e2f6985..841a121d1 100644 --- a/tools/syz-repro/repro.go +++ b/tools/syz-repro/repro.go @@ -19,6 +19,7 @@ import ( _ "github.com/google/syzkaller/vm/adb" _ "github.com/google/syzkaller/vm/gce" _ "github.com/google/syzkaller/vm/kvm" + _ "github.com/google/syzkaller/vm/odroid" _ "github.com/google/syzkaller/vm/qemu" ) diff --git a/vm/adb/adb.go b/vm/adb/adb.go index b8d00ea42..ae67ba182 100644 --- a/vm/adb/adb.go +++ b/vm/adb/adb.go @@ -118,7 +118,7 @@ func findConsoleImpl(adb, dev string) (string, error) { out := new([]byte) output[con] = out go func(con string) { - tty, err := openConsole(con) + tty, err := vm.OpenConsole(con) if err != nil { errors <- err return @@ -342,9 +342,9 @@ func (inst *instance) Run(timeout time.Duration, stop <-chan bool, command strin var tty io.ReadCloser var err error if inst.console == "adb" { - tty, err = openAdbConsole(inst.cfg.Bin, inst.cfg.Device) + tty, err = vm.OpenAdbConsole(inst.cfg.Bin, inst.cfg.Device) } else { - tty, err = openConsole(inst.console) + tty, err = vm.OpenConsole(inst.console) } if err != nil { return nil, nil, err diff --git a/vm/adb/console.go b/vm/console.go index 7fb32c4b9..6dd6a6edf 100644 --- a/vm/adb/console.go +++ b/vm/console.go @@ -1,9 +1,9 @@ -// Copyright 2016 syzkaller project authors. All rights reserved. +// Copyright 2017 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. // +build !ppc64le -package adb +package vm import ( "fmt" @@ -13,12 +13,11 @@ import ( "syscall" "unsafe" - "github.com/google/syzkaller/vm" - . "golang.org/x/sys/unix" + "golang.org/x/sys/unix" ) // Tested on Suzy-Q and BeagleBone. -func openConsole(con string) (rc io.ReadCloser, err error) { +func OpenConsole(con string) (rc io.ReadCloser, err error) { fd, err := syscall.Open(con, syscall.O_RDONLY|syscall.O_NOCTTY|syscall.O_SYNC, 0) if err != nil { return nil, fmt.Errorf("failed to open console file: %v", err) @@ -28,21 +27,21 @@ func openConsole(con string) (rc io.ReadCloser, err error) { syscall.Close(fd) } }() - var term Termios - if _, _, errno := syscall.Syscall(SYS_IOCTL, uintptr(fd), TCGETS2, uintptr(unsafe.Pointer(&term))); errno != 0 { + var term unix.Termios + if _, _, errno := syscall.Syscall(unix.SYS_IOCTL, uintptr(fd), unix.TCGETS2, uintptr(unsafe.Pointer(&term))); errno != 0 { return nil, fmt.Errorf("failed to get console termios: %v", errno) } // no parity bit, only need 1 stop bit, no hardware flowcontrol - term.Cflag &^= CBAUD | CSIZE | PARENB | CSTOPB | CRTSCTS + term.Cflag &^= unix.CBAUD | unix.CSIZE | unix.PARENB | unix.CSTOPB | unix.CRTSCTS // ignore modem controls - term.Cflag |= B115200 | CS8 | CLOCAL | CREAD + term.Cflag |= unix.B115200 | unix.CS8 | unix.CLOCAL | unix.CREAD // setup for non-canonical mode - term.Iflag &^= IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON - term.Lflag &^= ECHO | ECHONL | ICANON | ISIG | IEXTEN - term.Oflag &^= OPOST - term.Cc[VMIN] = 0 - term.Cc[VTIME] = 10 // 1 second timeout - if _, _, errno := syscall.Syscall(SYS_IOCTL, uintptr(fd), TCSETS2, uintptr(unsafe.Pointer(&term))); errno != 0 { + term.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON + term.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN + term.Oflag &^= unix.OPOST + term.Cc[unix.VMIN] = 0 + term.Cc[unix.VTIME] = 10 // 1 second timeout + if _, _, errno := syscall.Syscall(unix.SYS_IOCTL, uintptr(fd), unix.TCSETS2, uintptr(unsafe.Pointer(&term))); errno != 0 { return nil, fmt.Errorf("failed to get console termios: %v", errno) } tmp := fd @@ -78,9 +77,9 @@ func (t *tty) Close() error { return nil } -// openAdbConsole provides fallback console output using 'adb shell dmesg -w'. -func openAdbConsole(bin, dev string) (rc io.ReadCloser, err error) { - rpipe, wpipe, err := vm.LongPipe() +// OpenAdbConsole provides fallback console output using 'adb shell dmesg -w'. +func OpenAdbConsole(bin, dev string) (rc io.ReadCloser, err error) { + rpipe, wpipe, err := LongPipe() if err != nil { return nil, err } diff --git a/vm/odroid/odroid.go b/vm/odroid/odroid.go new file mode 100644 index 000000000..f99b2a645 --- /dev/null +++ b/vm/odroid/odroid.go @@ -0,0 +1,368 @@ +// Copyright 2017 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 odroid + +// #cgo pkg-config: libusb-1.0 +// #include <linux/usb/ch9.h> +// #include <linux/usb/ch11.h> +// #include <libusb.h> +import "C" + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "reflect" + "time" + "unsafe" + + . "github.com/google/syzkaller/log" + "github.com/google/syzkaller/vm" +) + +func init() { + vm.Register("odroid", ctor) +} + +type instance struct { + cfg *vm.Config + closed chan bool +} + +func ctor(cfg *vm.Config) (vm.Instance, error) { + inst := &instance{ + cfg: cfg, + closed: make(chan bool), + } + closeInst := inst + defer func() { + if closeInst != nil { + closeInst.Close() + } + }() + if err := validateConfig(cfg); err != nil { + return nil, err + } + if err := inst.repair(); err != nil { + return nil, err + } + + // Create working dir if doesn't exist. + inst.ssh("mkdir -p /data/") + + // Remove temp files from previous runs. + inst.ssh("rm -rf /data/syzkaller-*") + + closeInst = nil + return inst, nil +} + +func validateConfig(cfg *vm.Config) error { + if _, err := os.Stat(cfg.Sshkey); err != nil { + return fmt.Errorf("ssh key '%v' does not exist: %v", cfg.Sshkey, err) + } + if _, err := os.Stat(cfg.OdroidConsole); err != nil { + return fmt.Errorf("console file '%v' does not exist: %v", cfg.OdroidConsole, err) + } + return nil +} + +func (inst *instance) Forward(port int) (string, error) { + return fmt.Sprintf(inst.cfg.OdroidHostAddr+":%v", port), nil +} + +func (inst *instance) ssh(command string) ([]byte, error) { + if inst.cfg.Debug { + Logf(0, "executing ssh %+v", command) + } + + rpipe, wpipe, err := vm.LongPipe() + if err != nil { + return nil, err + } + + args := append(inst.sshArgs("-p"), "root@"+inst.cfg.OdroidSlaveAddr, command) + if inst.cfg.Debug { + Logf(0, "running command: ssh %#v", args) + } + cmd := exec.Command("ssh", args...) + cmd.Stdout = wpipe + cmd.Stderr = wpipe + if err := cmd.Start(); err != nil { + wpipe.Close() + return nil, err + } + wpipe.Close() + + done := make(chan bool) + go func() { + select { + case <-time.After(time.Minute): + if inst.cfg.Debug { + Logf(0, "ssh hanged") + } + cmd.Process.Kill() + case <-done: + } + }() + if err := cmd.Wait(); err != nil { + close(done) + out, _ := ioutil.ReadAll(rpipe) + if inst.cfg.Debug { + Logf(0, "ssh failed: %v\n%s", err, out) + } + return nil, fmt.Errorf("ssh %+v failed: %v\n%s", args, err, out) + } + close(done) + if inst.cfg.Debug { + Logf(0, "ssh returned") + } + out, _ := ioutil.ReadAll(rpipe) + return out, nil +} + +func switchPortPower(busNum, deviceNum, portNum int, power bool) error { + var context *C.libusb_context + if err := C.libusb_init(&context); err != 0 { + return fmt.Errorf("failed to init libusb: %v\n", err) + } + defer C.libusb_exit(context) + + var rawList **C.libusb_device + numDevices := int(C.libusb_get_device_list(context, &rawList)) + if numDevices < 0 { + return fmt.Errorf("failed to init libusb: %v", numDevices) + } + defer C.libusb_free_device_list(rawList, 1) + + var deviceList []*C.libusb_device + *(*reflect.SliceHeader)(unsafe.Pointer(&deviceList)) = reflect.SliceHeader{ + Data: uintptr(unsafe.Pointer(rawList)), + Len: numDevices, + Cap: numDevices, + } + + var hub *C.libusb_device + for i := 0; i < numDevices; i++ { + var desc C.struct_libusb_device_descriptor + if err := C.libusb_get_device_descriptor(deviceList[i], &desc); err != 0 { + return fmt.Errorf("failed to get device descriptor: %v", err) + } + if desc.bDeviceClass != C.USB_CLASS_HUB { + continue + } + if C.libusb_get_bus_number(deviceList[i]) != C.uint8_t(busNum) { + continue + } + if C.libusb_get_device_address(deviceList[i]) != C.uint8_t(deviceNum) { + continue + } + hub = deviceList[i] + break + } + + if hub == nil { + return fmt.Errorf("hub not found: bus: %v, device: %v", busNum, deviceNum) + } + + var handle *C.libusb_device_handle + if err := C.libusb_open(hub, &handle); err != 0 { + return fmt.Errorf("failed to open usb device: %v", err) + } + + request := C.uint8_t(C.USB_REQ_CLEAR_FEATURE) + if power { + request = C.uint8_t(C.USB_REQ_SET_FEATURE) + } + port := C.uint16_t(portNum) + timeout := C.uint(1000) + if err := C.libusb_control_transfer(handle, C.USB_RT_PORT, request, + C.USB_PORT_FEAT_POWER, port, nil, 0, timeout); err < 0 { + return fmt.Errorf("failed to send control message: %v\n", err) + } + + return nil +} + +func (inst *instance) repair() error { + // Try to shutdown gracefully. + Logf(1, "odroid: trying to ssh") + if err := inst.waitForSsh(10); err == nil { + Logf(1, "odroid: ssh succeeded, shutting down now") + inst.ssh("shutdown now") + if !vm.SleepInterruptible(20 * time.Second) { + return fmt.Errorf("shutdown in progress") + } + } else { + Logf(1, "odroid: ssh failed") + } + + // Hard reset by turning off and back on power on a hub port. + Logf(1, "odroid: hard reset, turning off power") + if err := switchPortPower(inst.cfg.OdroidHubBus, inst.cfg.OdroidHubDevice, inst.cfg.OdroidHubPort, false); err != nil { + return err + } + if !vm.SleepInterruptible(5 * time.Second) { + return fmt.Errorf("shutdown in progress") + } + if err := switchPortPower(inst.cfg.OdroidHubBus, inst.cfg.OdroidHubDevice, inst.cfg.OdroidHubPort, true); err != nil { + return err + } + + // Now wait for boot. + Logf(1, "odroid: power back on, waiting for boot") + if err := inst.waitForSsh(150); err != nil { + return err + } + + Logf(1, "odroid: boot succeeded") + return nil +} + +func (inst *instance) waitForSsh(timeout int) error { + var err error + start := time.Now() + for { + if !vm.SleepInterruptible(time.Second) { + return fmt.Errorf("shutdown in progress") + } + if _, err = inst.ssh("pwd"); err == nil { + return nil + } + if time.Since(start).Seconds() > float64(timeout) { + break + } + } + return fmt.Errorf("instance is dead and unrepairable: %v", err) +} + +func (inst *instance) Close() { + close(inst.closed) + os.RemoveAll(inst.cfg.Workdir) +} + +func (inst *instance) Copy(hostSrc string) (string, error) { + basePath := "/data/" + vmDst := filepath.Join(basePath, filepath.Base(hostSrc)) + args := append(inst.sshArgs("-P"), hostSrc, "root@"+inst.cfg.OdroidSlaveAddr+":"+vmDst) + cmd := exec.Command("scp", args...) + if inst.cfg.Debug { + Logf(0, "running command: scp %#v", args) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stdout + } + if err := cmd.Start(); err != nil { + return "", err + } + done := make(chan bool) + go func() { + select { + case <-time.After(3 * time.Minute): + cmd.Process.Kill() + case <-done: + } + }() + err := cmd.Wait() + close(done) + if err != nil { + return "", err + } + return vmDst, nil +} + +func (inst *instance) Run(timeout time.Duration, stop <-chan bool, command string) (<-chan []byte, <-chan error, error) { + tty, err := vm.OpenConsole(inst.cfg.OdroidConsole) + if err != nil { + return nil, nil, err + } + + rpipe, wpipe, err := vm.LongPipe() + if err != nil { + tty.Close() + return nil, nil, err + } + + args := append(inst.sshArgs("-p"), "root@"+inst.cfg.OdroidSlaveAddr, "cd /data; "+command) + if inst.cfg.Debug { + Logf(0, "running command: ssh %#v", args) + } + cmd := exec.Command("ssh", args...) + cmd.Stdout = wpipe + cmd.Stderr = wpipe + if err := cmd.Start(); err != nil { + tty.Close() + rpipe.Close() + wpipe.Close() + return nil, nil, err + } + wpipe.Close() + + var tee io.Writer + if inst.cfg.Debug { + tee = os.Stdout + } + merger := vm.NewOutputMerger(tee) + merger.Add("console", tty) + merger.Add("ssh", rpipe) + + errc := make(chan error, 1) + signal := func(err error) { + select { + case errc <- err: + default: + } + } + + go func() { + select { + case <-time.After(timeout): + signal(vm.TimeoutErr) + case <-stop: + signal(vm.TimeoutErr) + case <-inst.closed: + if inst.cfg.Debug { + Logf(0, "instance closed") + } + signal(fmt.Errorf("instance closed")) + case err := <-merger.Err: + cmd.Process.Kill() + tty.Close() + merger.Wait() + if cmdErr := cmd.Wait(); cmdErr == nil { + // If the command exited successfully, we got EOF error from merger. + // But in this case no error has happened and the EOF is expected. + err = nil + } + signal(err) + return + } + cmd.Process.Kill() + tty.Close() + merger.Wait() + cmd.Wait() + }() + return merger.Output, errc, nil +} + +func (inst *instance) sshArgs(portArg string) []string { + args := []string{ + "-i", inst.cfg.Sshkey, + portArg, "22", + "-F", "/dev/null", + "-o", "ConnectionAttempts=10", + "-o", "ConnectTimeout=10", + "-o", "BatchMode=yes", + "-o", "UserKnownHostsFile=/dev/null", + "-o", "IdentitiesOnly=yes", + "-o", "StrictHostKeyChecking=no", + "-o", "LogLevel=error", + } + if inst.cfg.Debug { + args = append(args, "-v") + } + return args +} @@ -36,22 +36,28 @@ type Instance interface { } type Config struct { - Name string - Index int - Workdir string - Bin string - BinArgs string - Initrd string - Kernel string - Cmdline string - Image string - Sshkey string - Executor string - Device string - MachineType string - Cpu int - Mem int - Debug bool + Name string + Index int + Workdir string + Bin string + BinArgs string + Initrd string + Kernel string + Cmdline string + Image string + Sshkey string + Executor string + Device string + MachineType string + OdroidHostAddr string + OdroidSlaveAddr string + OdroidConsole string + OdroidHubBus int + OdroidHubDevice int + OdroidHubPort int + Cpu int + Mem int + Debug bool } type ctorFunc func(cfg *Config) (Instance, error) |
