// 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 odroid 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/pkg/config" . "github.com/google/syzkaller/pkg/log" "github.com/google/syzkaller/pkg/osutil" "github.com/google/syzkaller/vm/vmimpl" ) func init() { vmimpl.Register("odroid", ctor) } type Config struct { Host_Addr string // ip address of the host machine Slave_Addr string // ip address of the Odroid board Console string // console device name (e.g. "/dev/ttyUSB0") Hub_Bus int // host USB bus number for the USB hub Hub_Device int // host USB device number for the USB hub Hub_Port int // port on the USB hub to which Odroid is connected } type Pool struct { env *vmimpl.Env cfg *Config } type instance struct { cfg *Config os string sshkey string closed chan bool debug bool } func ctor(env *vmimpl.Env) (vmimpl.Pool, error) { cfg := &Config{} if err := config.LoadData(env.Config, cfg); err != nil { return nil, fmt.Errorf("failed to parse odroid vm config: %v", err) } if cfg.Host_Addr == "" { return nil, fmt.Errorf("config param host_addr is empty") } if cfg.Slave_Addr == "" { return nil, fmt.Errorf("config param slave_addr is empty") } if cfg.Console == "" { return nil, fmt.Errorf("config param console is empty") } if cfg.Hub_Bus == 0 { return nil, fmt.Errorf("config param hub_bus is empty") } if cfg.Hub_Device == 0 { return nil, fmt.Errorf("config param hub_device is empty") } if cfg.Hub_Port == 0 { return nil, fmt.Errorf("config param hub_port is empty") } if !osutil.IxExist(cfg.Console) { return nil, fmt.Errorf("console file '%v' does not exist", cfg.Console) } pool := &Pool{ cfg: cfg, env: env, } return pool, nil } func (pool *Pool) Count() int { return 1 // no support for multiple Odroid devices yet } func (pool *Pool) Create(workdir string, index int) (vmimpl.Instance, error) { inst := &instance{ cfg: pool.cfg, os: pool.env.OS, sshkey: pool.env.Sshkey, closed: make(chan bool), debug: pool.env.Debug, } closeInst := inst defer func() { if closeInst != nil { closeInst.Close() } }() 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 (inst *instance) Forward(port int) (string, error) { return fmt.Sprintf(inst.cfg.Host_Addr+":%v", port), nil } func (inst *instance) ssh(command string) ([]byte, error) { if inst.debug { Logf(0, "executing ssh %+v", command) } rpipe, wpipe, err := osutil.LongPipe() if err != nil { return nil, err } args := append(vmimpl.SSHArgs(inst.debug, inst.sshkey, 22), "root@"+inst.cfg.Slave_Addr, command) if inst.debug { Logf(0, "running command: ssh %#v", args) } cmd := osutil.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.debug { Logf(0, "ssh hanged") } cmd.Process.Kill() case <-done: } }() if err := cmd.Wait(); err != nil { close(done) out, _ := ioutil.ReadAll(rpipe) if inst.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.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 * time.Second); err == nil { Logf(1, "odroid: ssh succeeded, shutting down now") inst.ssh("shutdown now") if !vmimpl.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.Hub_Bus, inst.cfg.Hub_Device, inst.cfg.Hub_Port, false); err != nil { return err } if !vmimpl.SleepInterruptible(5 * time.Second) { return fmt.Errorf("shutdown in progress") } if err := switchPortPower(inst.cfg.Hub_Bus, inst.cfg.Hub_Device, inst.cfg.Hub_Port, true); err != nil { return err } // Now wait for boot. Logf(1, "odroid: power back on, waiting for boot") if err := inst.waitForSSH(150 * time.Second); err != nil { return err } Logf(1, "odroid: boot succeeded") return nil } func (inst *instance) waitForSSH(timeout time.Duration) error { return vmimpl.WaitForSSH(inst.debug, timeout, inst.cfg.Slave_Addr, inst.sshkey, "root", inst.os, 22) } func (inst *instance) Close() { close(inst.closed) } func (inst *instance) Copy(hostSrc string) (string, error) { basePath := "/data/" vmDst := filepath.Join(basePath, filepath.Base(hostSrc)) args := append(vmimpl.SCPArgs(inst.debug, inst.sshkey, 22), hostSrc, "root@"+inst.cfg.Slave_Addr+":"+vmDst) cmd := osutil.Command("scp", args...) if inst.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 := vmimpl.OpenConsole(inst.cfg.Console) if err != nil { return nil, nil, err } rpipe, wpipe, err := osutil.LongPipe() if err != nil { tty.Close() return nil, nil, err } args := append(vmimpl.SSHArgs(inst.debug, inst.sshkey, 22), "root@"+inst.cfg.Slave_Addr, "cd /data; "+command) if inst.debug { Logf(0, "running command: ssh %#v", args) } cmd := osutil.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.debug { tee = os.Stdout } merger := vmimpl.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(vmimpl.TimeoutErr) case <-stop: signal(vmimpl.TimeoutErr) case <-inst.closed: if inst.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 }