1// Copyright 2015 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 !ppc64le
5
6package adb
7
8import (
9	"bytes"
10	"fmt"
11	"io"
12	"io/ioutil"
13	"os"
14	"os/exec"
15	"path/filepath"
16	"regexp"
17	"sync"
18	"time"
19
20	"github.com/google/syzkaller/pkg/config"
21	"github.com/google/syzkaller/pkg/log"
22	"github.com/google/syzkaller/pkg/osutil"
23	"github.com/google/syzkaller/vm/vmimpl"
24)
25
26func init() {
27	vmimpl.Register("adb", ctor)
28}
29
30type Config struct {
31	Adb     string   `json:"adb"`     // adb binary name ("adb" by default)
32	Devices []string `json:"devices"` // list of adb device IDs to use
33
34	// Ensure that a device battery level is at 20+% before fuzzing.
35	// Sometimes we observe that a device can't charge during heavy fuzzing
36	// and eventually powers down (which then requires manual intervention).
37	// This option is enabled by default. Turn it off if your devices
38	// don't have battery service, or it causes problems otherwise.
39	BatteryCheck bool `json:"battery_check"`
40}
41
42type Pool struct {
43	env *vmimpl.Env
44	cfg *Config
45}
46
47type instance struct {
48	adbBin  string
49	device  string
50	console string
51	closed  chan bool
52	debug   bool
53}
54
55func ctor(env *vmimpl.Env) (vmimpl.Pool, error) {
56	cfg := &Config{
57		Adb:          "adb",
58		BatteryCheck: true,
59	}
60	if err := config.LoadData(env.Config, cfg); err != nil {
61		return nil, fmt.Errorf("failed to parse adb vm config: %v", err)
62	}
63	if _, err := exec.LookPath(cfg.Adb); err != nil {
64		return nil, err
65	}
66	if len(cfg.Devices) == 0 {
67		return nil, fmt.Errorf("no adb devices specified")
68	}
69	devRe := regexp.MustCompile("[0-9A-F]+")
70	for _, dev := range cfg.Devices {
71		if !devRe.MatchString(dev) {
72			return nil, fmt.Errorf("invalid adb device id '%v'", dev)
73		}
74	}
75	if env.Debug {
76		cfg.Devices = cfg.Devices[:1]
77	}
78	pool := &Pool{
79		cfg: cfg,
80		env: env,
81	}
82	return pool, nil
83}
84
85func (pool *Pool) Count() int {
86	return len(pool.cfg.Devices)
87}
88
89func (pool *Pool) Create(workdir string, index int) (vmimpl.Instance, error) {
90	inst := &instance{
91		adbBin: pool.cfg.Adb,
92		device: pool.cfg.Devices[index],
93		closed: make(chan bool),
94		debug:  pool.env.Debug,
95	}
96	closeInst := inst
97	defer func() {
98		if closeInst != nil {
99			closeInst.Close()
100		}
101	}()
102	if err := inst.repair(); err != nil {
103		return nil, err
104	}
105	inst.console = findConsole(inst.adbBin, inst.device)
106	if pool.cfg.BatteryCheck {
107		if err := inst.checkBatteryLevel(); err != nil {
108			return nil, err
109		}
110	}
111	// Remove temp files from previous runs.
112	if _, err := inst.adb("shell", "rm -Rf /data/syzkaller*"); err != nil {
113		return nil, err
114	}
115	inst.adb("shell", "echo 0 > /proc/sys/kernel/kptr_restrict")
116	closeInst = nil
117	return inst, nil
118}
119
120var (
121	consoleCacheMu sync.Mutex
122	consoleToDev   = make(map[string]string)
123	devToConsole   = make(map[string]string)
124)
125
126// findConsole returns console file associated with the dev device (e.g. /dev/ttyUSB0).
127// This code was tested with Suzy-Q and Android Serial Cable (ASC). For Suzy-Q see:
128// https://chromium.googlesource.com/chromiumos/platform/ec/+/master/docs/case_closed_debugging.md
129// The difference between Suzy-Q and ASC is that ASC is a separate cable,
130// so it is not possible to match USB bus/port used by adb with the serial console device;
131// while Suzy-Q console uses the same USB calbe as adb.
132// The overall idea is as follows. We use 'adb shell' to write a unique string onto console,
133// then we read from all console devices and see on what console the unique string appears.
134func findConsole(adb, dev string) string {
135	consoleCacheMu.Lock()
136	defer consoleCacheMu.Unlock()
137	if con := devToConsole[dev]; con != "" {
138		return con
139	}
140	con, err := findConsoleImpl(adb, dev)
141	if err != nil {
142		log.Logf(0, "failed to associate adb device %v with console: %v", dev, err)
143		log.Logf(0, "falling back to 'adb shell dmesg -w'")
144		log.Logf(0, "note: some bugs may be detected as 'lost connection to test machine' with no kernel output")
145		con = "adb"
146		devToConsole[dev] = con
147		return con
148	}
149	devToConsole[dev] = con
150	consoleToDev[con] = dev
151	log.Logf(0, "associating adb device %v with console %v", dev, con)
152	return con
153}
154
155func findConsoleImpl(adb, dev string) (string, error) {
156	// Attempt to find an exact match, at /dev/ttyUSB.{SERIAL}
157	// This is something that can be set up on Linux via 'udev' rules
158	exactCon := "/dev/ttyUSB." + dev
159	if osutil.IsExist(exactCon) {
160		return exactCon, nil
161	}
162
163	// Search all consoles, as described in 'findConsole'
164	consoles, err := filepath.Glob("/dev/ttyUSB*")
165	if err != nil {
166		return "", fmt.Errorf("failed to list /dev/ttyUSB devices: %v", err)
167	}
168	output := make(map[string]*[]byte)
169	errors := make(chan error, len(consoles))
170	done := make(chan bool)
171	for _, con := range consoles {
172		if consoleToDev[con] != "" {
173			continue
174		}
175		out := new([]byte)
176		output[con] = out
177		go func(con string) {
178			tty, err := vmimpl.OpenConsole(con)
179			if err != nil {
180				errors <- err
181				return
182			}
183			defer tty.Close()
184			go func() {
185				<-done
186				tty.Close()
187			}()
188			*out, _ = ioutil.ReadAll(tty)
189			errors <- nil
190		}(con)
191	}
192	if len(output) == 0 {
193		return "", fmt.Errorf("no unassociated console devices left")
194	}
195	time.Sleep(500 * time.Millisecond)
196	unique := fmt.Sprintf(">>>%v<<<", dev)
197	cmd := osutil.Command(adb, "-s", dev, "shell", "echo", "\"<1>", unique, "\"", ">", "/dev/kmsg")
198	if out, err := cmd.CombinedOutput(); err != nil {
199		return "", fmt.Errorf("failed to run adb shell: %v\n%s", err, out)
200	}
201	time.Sleep(500 * time.Millisecond)
202	close(done)
203
204	var anyErr error
205	for range output {
206		err := <-errors
207		if anyErr == nil && err != nil {
208			anyErr = err
209		}
210	}
211
212	con := ""
213	for con1, out := range output {
214		if bytes.Contains(*out, []byte(unique)) {
215			if con == "" {
216				con = con1
217			} else {
218				anyErr = fmt.Errorf("device is associated with several consoles: %v and %v", con, con1)
219			}
220		}
221	}
222
223	if con == "" {
224		if anyErr != nil {
225			return "", anyErr
226		}
227		return "", fmt.Errorf("no console is associated with this device")
228	}
229	return con, nil
230}
231
232func (inst *instance) Forward(port int) (string, error) {
233	var err error
234	for i := 0; i < 1000; i++ {
235		devicePort := vmimpl.RandomPort()
236		_, err = inst.adb("reverse", fmt.Sprintf("tcp:%v", devicePort), fmt.Sprintf("tcp:%v", port))
237		if err == nil {
238			return fmt.Sprintf("127.0.0.1:%v", devicePort), nil
239		}
240	}
241	return "", err
242}
243
244func (inst *instance) adb(args ...string) ([]byte, error) {
245	if inst.debug {
246		log.Logf(0, "executing adb %+v", args)
247	}
248	args = append([]string{"-s", inst.device}, args...)
249	out, err := osutil.RunCmd(time.Minute, "", inst.adbBin, args...)
250	if inst.debug {
251		log.Logf(0, "adb returned")
252	}
253	return out, err
254}
255
256func (inst *instance) repair() error {
257	// Assume that the device is in a bad state initially and reboot it.
258	// Ignore errors, maybe we will manage to reboot it anyway.
259	inst.waitForSSH()
260	// History: adb reboot episodically hangs, so we used a more reliable way:
261	// using syz-executor to issue reboot syscall. However, this has stopped
262	// working, probably due to the introduction of seccomp. Therefore,
263	// we revert this to `adb shell reboot` in the meantime, until a more
264	// reliable solution can be sought out.
265	if _, err := inst.adb("shell", "reboot"); err != nil {
266		return err
267	}
268	// Now give it another 5 minutes to boot.
269	if !vmimpl.SleepInterruptible(10 * time.Second) {
270		return fmt.Errorf("shutdown in progress")
271	}
272	if err := inst.waitForSSH(); err != nil {
273		return err
274	}
275	// Switch to root for userdebug builds.
276	inst.adb("root")
277	return inst.waitForSSH()
278}
279
280func (inst *instance) waitForSSH() error {
281	var err error
282	for i := 0; i < 300; i++ {
283		if !vmimpl.SleepInterruptible(time.Second) {
284			return fmt.Errorf("shutdown in progress")
285		}
286		if _, err = inst.adb("shell", "pwd"); err == nil {
287			return nil
288		}
289	}
290	return fmt.Errorf("instance is dead and unrepairable: %v", err)
291}
292
293func (inst *instance) checkBatteryLevel() error {
294	const (
295		minLevel      = 20
296		requiredLevel = 30
297	)
298	val, err := inst.getBatteryLevel(30)
299	if err != nil {
300		return err
301	}
302	if val >= minLevel {
303		log.Logf(0, "device %v: battery level %v%%, OK", inst.device, val)
304		return nil
305	}
306	for {
307		log.Logf(0, "device %v: battery level %v%%, waiting for %v%%", inst.device, val, requiredLevel)
308		if !vmimpl.SleepInterruptible(time.Minute) {
309			return nil
310		}
311		val, err = inst.getBatteryLevel(0)
312		if err != nil {
313			return err
314		}
315		if val >= requiredLevel {
316			break
317		}
318	}
319	return nil
320}
321
322func (inst *instance) getBatteryLevel(numRetry int) (int, error) {
323	out, err := inst.adb("shell", "dumpsys battery | grep level:")
324
325	// allow for retrying for devices that does not boot up so fast
326	for ; numRetry >= 0 && err != nil; numRetry-- {
327		if numRetry > 0 {
328			// sleep for 5 seconds before retrying
329			time.Sleep(5 * time.Second)
330			out, err = inst.adb("shell", "dumpsys battery | grep level:")
331		} else {
332			if err != nil {
333				return 0, err
334			}
335		}
336	}
337	val := 0
338	for _, c := range out {
339		if c >= '0' && c <= '9' {
340			val = val*10 + int(c) - '0'
341			continue
342		}
343		if val != 0 {
344			break
345		}
346	}
347	if val == 0 {
348		return 0, fmt.Errorf("failed to parse 'dumpsys battery' output: %s", out)
349	}
350	return val, nil
351}
352
353func (inst *instance) Close() {
354	close(inst.closed)
355}
356
357func (inst *instance) Copy(hostSrc string) (string, error) {
358	vmDst := filepath.Join("/data", filepath.Base(hostSrc))
359	if _, err := inst.adb("push", hostSrc, vmDst); err != nil {
360		return "", err
361	}
362	return vmDst, nil
363}
364
365func (inst *instance) Run(timeout time.Duration, stop <-chan bool, command string) (
366	<-chan []byte, <-chan error, error) {
367	var tty io.ReadCloser
368	var err error
369	if inst.console == "adb" {
370		tty, err = vmimpl.OpenAdbConsole(inst.adbBin, inst.device)
371	} else {
372		tty, err = vmimpl.OpenConsole(inst.console)
373	}
374	if err != nil {
375		return nil, nil, err
376	}
377
378	adbRpipe, adbWpipe, err := osutil.LongPipe()
379	if err != nil {
380		tty.Close()
381		return nil, nil, err
382	}
383	if inst.debug {
384		log.Logf(0, "starting: adb shell %v", command)
385	}
386	adb := osutil.Command(inst.adbBin, "-s", inst.device, "shell", "cd /data; "+command)
387	adb.Stdout = adbWpipe
388	adb.Stderr = adbWpipe
389	if err := adb.Start(); err != nil {
390		tty.Close()
391		adbRpipe.Close()
392		adbWpipe.Close()
393		return nil, nil, fmt.Errorf("failed to start adb: %v", err)
394	}
395	adbWpipe.Close()
396
397	var tee io.Writer
398	if inst.debug {
399		tee = os.Stdout
400	}
401	merger := vmimpl.NewOutputMerger(tee)
402	merger.Add("console", tty)
403	merger.Add("adb", adbRpipe)
404
405	return vmimpl.Multiplex(adb, merger, tty, timeout, stop, inst.closed, inst.debug)
406}
407
408func (inst *instance) Diagnose() bool {
409	return false
410}
411