• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2017 syzkaller project authors. All rights reserved.
2// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3
4// +build odroid
5
6package odroid
7
8// #cgo pkg-config: libusb-1.0
9// #include <linux/usb/ch9.h>
10// #include <linux/usb/ch11.h>
11// #include <libusb.h>
12import "C"
13
14import (
15	"fmt"
16	"io"
17	"io/ioutil"
18	"os"
19	"os/exec"
20	"path/filepath"
21	"reflect"
22	"time"
23	"unsafe"
24
25	"github.com/google/syzkaller/pkg/config"
26	. "github.com/google/syzkaller/pkg/log"
27	"github.com/google/syzkaller/pkg/osutil"
28	"github.com/google/syzkaller/vm/vmimpl"
29)
30
31func init() {
32	vmimpl.Register("odroid", ctor)
33}
34
35type Config struct {
36	Host_Addr  string // ip address of the host machine
37	Slave_Addr string // ip address of the Odroid board
38	Console    string // console device name (e.g. "/dev/ttyUSB0")
39	Hub_Bus    int    // host USB bus number for the USB hub
40	Hub_Device int    // host USB device number for the USB hub
41	Hub_Port   int    // port on the USB hub to which Odroid is connected
42}
43
44type Pool struct {
45	env *vmimpl.Env
46	cfg *Config
47}
48
49type instance struct {
50	cfg    *Config
51	os     string
52	sshkey string
53	closed chan bool
54	debug  bool
55}
56
57func ctor(env *vmimpl.Env) (vmimpl.Pool, error) {
58	cfg := &Config{}
59	if err := config.LoadData(env.Config, cfg); err != nil {
60		return nil, fmt.Errorf("failed to parse odroid vm config: %v", err)
61	}
62	if cfg.Host_Addr == "" {
63		return nil, fmt.Errorf("config param host_addr is empty")
64	}
65	if cfg.Slave_Addr == "" {
66		return nil, fmt.Errorf("config param slave_addr is empty")
67	}
68	if cfg.Console == "" {
69		return nil, fmt.Errorf("config param console is empty")
70	}
71	if cfg.Hub_Bus == 0 {
72		return nil, fmt.Errorf("config param hub_bus is empty")
73	}
74	if cfg.Hub_Device == 0 {
75		return nil, fmt.Errorf("config param hub_device is empty")
76	}
77	if cfg.Hub_Port == 0 {
78		return nil, fmt.Errorf("config param hub_port is empty")
79	}
80	if !osutil.IxExist(cfg.Console) {
81		return nil, fmt.Errorf("console file '%v' does not exist", cfg.Console)
82	}
83	pool := &Pool{
84		cfg: cfg,
85		env: env,
86	}
87	return pool, nil
88}
89
90func (pool *Pool) Count() int {
91	return 1 // no support for multiple Odroid devices yet
92}
93
94func (pool *Pool) Create(workdir string, index int) (vmimpl.Instance, error) {
95	inst := &instance{
96		cfg:    pool.cfg,
97		os:     pool.env.OS,
98		sshkey: pool.env.Sshkey,
99		closed: make(chan bool),
100		debug:  pool.env.Debug,
101	}
102	closeInst := inst
103	defer func() {
104		if closeInst != nil {
105			closeInst.Close()
106		}
107	}()
108	if err := inst.repair(); err != nil {
109		return nil, err
110	}
111
112	// Create working dir if doesn't exist.
113	inst.ssh("mkdir -p /data/")
114
115	// Remove temp files from previous runs.
116	inst.ssh("rm -rf /data/syzkaller-*")
117
118	closeInst = nil
119	return inst, nil
120}
121
122func (inst *instance) Forward(port int) (string, error) {
123	return fmt.Sprintf(inst.cfg.Host_Addr+":%v", port), nil
124}
125
126func (inst *instance) ssh(command string) ([]byte, error) {
127	if inst.debug {
128		Logf(0, "executing ssh %+v", command)
129	}
130
131	rpipe, wpipe, err := osutil.LongPipe()
132	if err != nil {
133		return nil, err
134	}
135
136	args := append(vmimpl.SSHArgs(inst.debug, inst.sshkey, 22), "root@"+inst.cfg.Slave_Addr, command)
137	if inst.debug {
138		Logf(0, "running command: ssh %#v", args)
139	}
140	cmd := osutil.Command("ssh", args...)
141	cmd.Stdout = wpipe
142	cmd.Stderr = wpipe
143	if err := cmd.Start(); err != nil {
144		wpipe.Close()
145		return nil, err
146	}
147	wpipe.Close()
148
149	done := make(chan bool)
150	go func() {
151		select {
152		case <-time.After(time.Minute):
153			if inst.debug {
154				Logf(0, "ssh hanged")
155			}
156			cmd.Process.Kill()
157		case <-done:
158		}
159	}()
160	if err := cmd.Wait(); err != nil {
161		close(done)
162		out, _ := ioutil.ReadAll(rpipe)
163		if inst.debug {
164			Logf(0, "ssh failed: %v\n%s", err, out)
165		}
166		return nil, fmt.Errorf("ssh %+v failed: %v\n%s", args, err, out)
167	}
168	close(done)
169	if inst.debug {
170		Logf(0, "ssh returned")
171	}
172	out, _ := ioutil.ReadAll(rpipe)
173	return out, nil
174}
175
176func switchPortPower(busNum, deviceNum, portNum int, power bool) error {
177	var context *C.libusb_context
178	if err := C.libusb_init(&context); err != 0 {
179		return fmt.Errorf("failed to init libusb: %v\n", err)
180	}
181	defer C.libusb_exit(context)
182
183	var rawList **C.libusb_device
184	numDevices := int(C.libusb_get_device_list(context, &rawList))
185	if numDevices < 0 {
186		return fmt.Errorf("failed to init libusb: %v", numDevices)
187	}
188	defer C.libusb_free_device_list(rawList, 1)
189
190	var deviceList []*C.libusb_device
191	*(*reflect.SliceHeader)(unsafe.Pointer(&deviceList)) = reflect.SliceHeader{
192		Data: uintptr(unsafe.Pointer(rawList)),
193		Len:  numDevices,
194		Cap:  numDevices,
195	}
196
197	var hub *C.libusb_device
198	for i := 0; i < numDevices; i++ {
199		var desc C.struct_libusb_device_descriptor
200		if err := C.libusb_get_device_descriptor(deviceList[i], &desc); err != 0 {
201			return fmt.Errorf("failed to get device descriptor: %v", err)
202		}
203		if desc.bDeviceClass != C.USB_CLASS_HUB {
204			continue
205		}
206		if C.libusb_get_bus_number(deviceList[i]) != C.uint8_t(busNum) {
207			continue
208		}
209		if C.libusb_get_device_address(deviceList[i]) != C.uint8_t(deviceNum) {
210			continue
211		}
212		hub = deviceList[i]
213		break
214	}
215
216	if hub == nil {
217		return fmt.Errorf("hub not found: bus: %v, device: %v", busNum, deviceNum)
218	}
219
220	var handle *C.libusb_device_handle
221	if err := C.libusb_open(hub, &handle); err != 0 {
222		return fmt.Errorf("failed to open usb device: %v", err)
223	}
224
225	request := C.uint8_t(C.USB_REQ_CLEAR_FEATURE)
226	if power {
227		request = C.uint8_t(C.USB_REQ_SET_FEATURE)
228	}
229	port := C.uint16_t(portNum)
230	timeout := C.uint(1000)
231	if err := C.libusb_control_transfer(handle, C.USB_RT_PORT, request,
232		C.USB_PORT_FEAT_POWER, port, nil, 0, timeout); err < 0 {
233		return fmt.Errorf("failed to send control message: %v\n", err)
234	}
235
236	return nil
237}
238
239func (inst *instance) repair() error {
240	// Try to shutdown gracefully.
241	Logf(1, "odroid: trying to ssh")
242	if err := inst.waitForSSH(10 * time.Second); err == nil {
243		Logf(1, "odroid: ssh succeeded, shutting down now")
244		inst.ssh("shutdown now")
245		if !vmimpl.SleepInterruptible(20 * time.Second) {
246			return fmt.Errorf("shutdown in progress")
247		}
248	} else {
249		Logf(1, "odroid: ssh failed")
250	}
251
252	// Hard reset by turning off and back on power on a hub port.
253	Logf(1, "odroid: hard reset, turning off power")
254	if err := switchPortPower(inst.cfg.Hub_Bus, inst.cfg.Hub_Device, inst.cfg.Hub_Port, false); err != nil {
255		return err
256	}
257	if !vmimpl.SleepInterruptible(5 * time.Second) {
258		return fmt.Errorf("shutdown in progress")
259	}
260	if err := switchPortPower(inst.cfg.Hub_Bus, inst.cfg.Hub_Device, inst.cfg.Hub_Port, true); err != nil {
261		return err
262	}
263
264	// Now wait for boot.
265	Logf(1, "odroid: power back on, waiting for boot")
266	if err := inst.waitForSSH(150 * time.Second); err != nil {
267		return err
268	}
269
270	Logf(1, "odroid: boot succeeded")
271	return nil
272}
273
274func (inst *instance) waitForSSH(timeout time.Duration) error {
275	return vmimpl.WaitForSSH(inst.debug, timeout, inst.cfg.Slave_Addr, inst.sshkey, "root", inst.os, 22)
276}
277
278func (inst *instance) Close() {
279	close(inst.closed)
280}
281
282func (inst *instance) Copy(hostSrc string) (string, error) {
283	basePath := "/data/"
284	vmDst := filepath.Join(basePath, filepath.Base(hostSrc))
285	args := append(vmimpl.SCPArgs(inst.debug, inst.sshkey, 22), hostSrc, "root@"+inst.cfg.Slave_Addr+":"+vmDst)
286	cmd := osutil.Command("scp", args...)
287	if inst.debug {
288		Logf(0, "running command: scp %#v", args)
289		cmd.Stdout = os.Stdout
290		cmd.Stderr = os.Stdout
291	}
292	if err := cmd.Start(); err != nil {
293		return "", err
294	}
295	done := make(chan bool)
296	go func() {
297		select {
298		case <-time.After(3 * time.Minute):
299			cmd.Process.Kill()
300		case <-done:
301		}
302	}()
303	err := cmd.Wait()
304	close(done)
305	if err != nil {
306		return "", err
307	}
308	return vmDst, nil
309}
310
311func (inst *instance) Run(timeout time.Duration, stop <-chan bool, command string) (
312	<-chan []byte, <-chan error, error) {
313	tty, err := vmimpl.OpenConsole(inst.cfg.Console)
314	if err != nil {
315		return nil, nil, err
316	}
317
318	rpipe, wpipe, err := osutil.LongPipe()
319	if err != nil {
320		tty.Close()
321		return nil, nil, err
322	}
323
324	args := append(vmimpl.SSHArgs(inst.debug, inst.sshkey, 22),
325		"root@"+inst.cfg.Slave_Addr, "cd /data; "+command)
326	if inst.debug {
327		Logf(0, "running command: ssh %#v", args)
328	}
329	cmd := osutil.Command("ssh", args...)
330	cmd.Stdout = wpipe
331	cmd.Stderr = wpipe
332	if err := cmd.Start(); err != nil {
333		tty.Close()
334		rpipe.Close()
335		wpipe.Close()
336		return nil, nil, err
337	}
338	wpipe.Close()
339
340	var tee io.Writer
341	if inst.debug {
342		tee = os.Stdout
343	}
344	merger := vmimpl.NewOutputMerger(tee)
345	merger.Add("console", tty)
346	merger.Add("ssh", rpipe)
347
348	errc := make(chan error, 1)
349	signal := func(err error) {
350		select {
351		case errc <- err:
352		default:
353		}
354	}
355
356	go func() {
357		select {
358		case <-time.After(timeout):
359			signal(vmimpl.TimeoutErr)
360		case <-stop:
361			signal(vmimpl.TimeoutErr)
362		case <-inst.closed:
363			if inst.debug {
364				Logf(0, "instance closed")
365			}
366			signal(fmt.Errorf("instance closed"))
367		case err := <-merger.Err:
368			cmd.Process.Kill()
369			tty.Close()
370			merger.Wait()
371			if cmdErr := cmd.Wait(); cmdErr == nil {
372				// If the command exited successfully, we got EOF error from merger.
373				// But in this case no error has happened and the EOF is expected.
374				err = nil
375			}
376			signal(err)
377			return
378		}
379		cmd.Process.Kill()
380		tty.Close()
381		merger.Wait()
382		cmd.Wait()
383	}()
384	return merger.Output, errc, nil
385}
386