github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/vm/adb/adb.go (about)

     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  //go:build !ppc64le
     5  
     6  package adb
     7  
     8  import (
     9  	"bytes"
    10  	"encoding/json"
    11  	"fmt"
    12  	"io"
    13  	"os"
    14  	"os/exec"
    15  	"path/filepath"
    16  	"regexp"
    17  	"strings"
    18  	"sync"
    19  	"time"
    20  
    21  	"github.com/google/syzkaller/pkg/config"
    22  	"github.com/google/syzkaller/pkg/log"
    23  	"github.com/google/syzkaller/pkg/osutil"
    24  	"github.com/google/syzkaller/pkg/report"
    25  	"github.com/google/syzkaller/vm/vmimpl"
    26  )
    27  
    28  func init() {
    29  	vmimpl.Register("adb", ctor, false)
    30  }
    31  
    32  type Device struct {
    33  	Serial  string `json:"serial"`  // device serial to connect
    34  	Console string `json:"console"` // console device name (e.g. "/dev/pts/0")
    35  }
    36  
    37  type Config struct {
    38  	Adb     string            `json:"adb"`     // adb binary name ("adb" by default)
    39  	Devices []json.RawMessage `json:"devices"` // list of adb devices to use
    40  
    41  	// Ensure that a device battery level is at 20+% before fuzzing.
    42  	// Sometimes we observe that a device can't charge during heavy fuzzing
    43  	// and eventually powers down (which then requires manual intervention).
    44  	// This option is enabled by default. Turn it off if your devices
    45  	// don't have battery service, or it causes problems otherwise.
    46  	BatteryCheck bool `json:"battery_check"`
    47  	// If this option is set (default), the device is rebooted after each crash.
    48  	// Set it to false to disable reboots.
    49  	TargetReboot  bool   `json:"target_reboot"`
    50  	RepairScript  string `json:"repair_script"`  // script to execute before each startup
    51  	StartupScript string `json:"startup_script"` // script to execute after each startup
    52  }
    53  
    54  type Pool struct {
    55  	env *vmimpl.Env
    56  	cfg *Config
    57  }
    58  
    59  type instance struct {
    60  	cfg     *Config
    61  	adbBin  string
    62  	device  string
    63  	console string
    64  	closed  chan bool
    65  	debug   bool
    66  }
    67  
    68  var (
    69  	androidSerial = "^[0-9A-Za-z]+$"
    70  	ipAddress     = `^(?:localhost|(?:[0-9]{1,3}\.){3}[0-9]{1,3})\:(?:[0-9]{1,5})$` // cuttlefish or remote_device_proxy
    71  	emulatorID    = `^emulator\-\d+$`
    72  )
    73  
    74  func loadDevice(data []byte) (*Device, error) {
    75  	devObj := &Device{}
    76  	var devStr string
    77  	err1 := config.LoadData(data, devObj)
    78  	err2 := config.LoadData(data, &devStr)
    79  	if err1 != nil && err2 != nil {
    80  		return nil, fmt.Errorf("failed to parse adb vm config: %w %w", err1, err2)
    81  	}
    82  	if err2 == nil {
    83  		devObj.Serial = devStr
    84  	}
    85  	return devObj, nil
    86  }
    87  
    88  func ctor(env *vmimpl.Env) (vmimpl.Pool, error) {
    89  	cfg := &Config{
    90  		Adb:          "adb",
    91  		BatteryCheck: true,
    92  		TargetReboot: true,
    93  	}
    94  	if err := config.LoadData(env.Config, cfg); err != nil {
    95  		return nil, fmt.Errorf("failed to parse adb vm config: %w", err)
    96  	}
    97  	if _, err := exec.LookPath(cfg.Adb); err != nil {
    98  		return nil, err
    99  	}
   100  	if len(cfg.Devices) == 0 {
   101  		return nil, fmt.Errorf("no adb devices specified")
   102  	}
   103  	// Device should be either regular serial number, a valid Cuttlefish ID, or an Android Emulator ID.
   104  	devRe := regexp.MustCompile(fmt.Sprintf("%s|%s|%s", androidSerial, ipAddress, emulatorID))
   105  	for _, dev := range cfg.Devices {
   106  		device, err := loadDevice(dev)
   107  		if err != nil {
   108  			return nil, err
   109  		}
   110  		if !devRe.MatchString(device.Serial) {
   111  			return nil, fmt.Errorf("invalid adb device id '%v'", device.Serial)
   112  		}
   113  	}
   114  	if env.Debug {
   115  		cfg.Devices = cfg.Devices[:1]
   116  	}
   117  	pool := &Pool{
   118  		cfg: cfg,
   119  		env: env,
   120  	}
   121  	return pool, nil
   122  }
   123  
   124  func (pool *Pool) Count() int {
   125  	return len(pool.cfg.Devices)
   126  }
   127  
   128  func (pool *Pool) Create(workdir string, index int) (vmimpl.Instance, error) {
   129  	device, err := loadDevice(pool.cfg.Devices[index])
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  	inst := &instance{
   134  		cfg:     pool.cfg,
   135  		adbBin:  pool.cfg.Adb,
   136  		device:  device.Serial,
   137  		console: device.Console,
   138  		closed:  make(chan bool),
   139  		debug:   pool.env.Debug,
   140  	}
   141  	closeInst := inst
   142  	defer func() {
   143  		if closeInst != nil {
   144  			closeInst.Close()
   145  		}
   146  	}()
   147  	if err := inst.repair(); err != nil {
   148  		return nil, err
   149  	}
   150  	if inst.console == "" {
   151  		inst.console = findConsole(inst.adbBin, inst.device)
   152  	}
   153  	log.Logf(0, "associating adb device %v with console %v", inst.device, inst.console)
   154  	if pool.cfg.BatteryCheck {
   155  		if err := inst.checkBatteryLevel(); err != nil {
   156  			return nil, err
   157  		}
   158  	}
   159  	// Remove temp files from previous runs.
   160  	// rm chokes on bad symlinks so we must remove them first
   161  	if _, err := inst.adb("shell", "ls /data/syzkaller*"); err == nil {
   162  		if _, err := inst.adb("shell", "find /data/syzkaller* -type l -exec unlink {} \\;"+
   163  			" && rm -Rf /data/syzkaller*"); err != nil {
   164  			return nil, err
   165  		}
   166  	}
   167  	inst.adb("shell", "echo 0 > /proc/sys/kernel/kptr_restrict")
   168  	closeInst = nil
   169  	return inst, nil
   170  }
   171  
   172  var (
   173  	consoleCacheMu sync.Mutex
   174  	consoleToDev   = make(map[string]string)
   175  	devToConsole   = make(map[string]string)
   176  )
   177  
   178  func parseAdbOutToInt(out []byte) int {
   179  	val := 0
   180  	for _, c := range out {
   181  		if c >= '0' && c <= '9' {
   182  			val = val*10 + int(c) - '0'
   183  			continue
   184  		}
   185  		if val != 0 {
   186  			break
   187  		}
   188  	}
   189  	return val
   190  }
   191  
   192  // findConsole returns console file associated with the dev device (e.g. /dev/ttyUSB0).
   193  // This code was tested with Suzy-Q and Android Serial Cable (ASC). For Suzy-Q see:
   194  // https://chromium.googlesource.com/chromiumos/platform/ec/+/master/docs/case_closed_debugging.md
   195  // The difference between Suzy-Q and ASC is that ASC is a separate cable,
   196  // so it is not possible to match USB bus/port used by adb with the serial console device;
   197  // while Suzy-Q console uses the same USB calbe as adb.
   198  // The overall idea is as follows. We use 'adb shell' to write a unique string onto console,
   199  // then we read from all console devices and see on what console the unique string appears.
   200  func findConsole(adb, dev string) string {
   201  	consoleCacheMu.Lock()
   202  	defer consoleCacheMu.Unlock()
   203  	if con := devToConsole[dev]; con != "" {
   204  		return con
   205  	}
   206  	con, err := findConsoleImpl(adb, dev)
   207  	if err != nil {
   208  		log.Logf(0, "failed to associate adb device %v with console: %v", dev, err)
   209  		log.Logf(0, "falling back to 'adb shell dmesg -w'")
   210  		log.Logf(0, "note: some bugs may be detected as 'lost connection to test machine' with no kernel output")
   211  		con = "adb"
   212  		devToConsole[dev] = con
   213  		return con
   214  	}
   215  	devToConsole[dev] = con
   216  	consoleToDev[con] = dev
   217  	return con
   218  }
   219  
   220  func findConsoleImpl(adb, dev string) (string, error) {
   221  	// Attempt to find an exact match, at /dev/ttyUSB.{SERIAL}
   222  	// This is something that can be set up on Linux via 'udev' rules
   223  	exactCon := "/dev/ttyUSB." + dev
   224  	if osutil.IsExist(exactCon) {
   225  		return exactCon, nil
   226  	}
   227  
   228  	// Search all consoles, as described in 'findConsole'
   229  	consoles, err := filepath.Glob("/dev/ttyUSB*")
   230  	if err != nil {
   231  		return "", fmt.Errorf("failed to list /dev/ttyUSB devices: %w", err)
   232  	}
   233  	output := make(map[string]*[]byte)
   234  	errors := make(chan error, len(consoles))
   235  	done := make(chan bool)
   236  	for _, con := range consoles {
   237  		if consoleToDev[con] != "" {
   238  			continue
   239  		}
   240  		out := new([]byte)
   241  		output[con] = out
   242  		go func(con string) {
   243  			tty, err := vmimpl.OpenConsole(con)
   244  			if err != nil {
   245  				errors <- err
   246  				return
   247  			}
   248  			defer tty.Close()
   249  			go func() {
   250  				<-done
   251  				tty.Close()
   252  			}()
   253  			*out, _ = io.ReadAll(tty)
   254  			errors <- nil
   255  		}(con)
   256  	}
   257  	if len(output) == 0 {
   258  		return "", fmt.Errorf("no unassociated console devices left")
   259  	}
   260  	time.Sleep(500 * time.Millisecond)
   261  	unique := fmt.Sprintf(">>>%v<<<", dev)
   262  	cmd := osutil.Command(adb, "-s", dev, "shell", "echo", "\"<1>", unique, "\"", ">", "/dev/kmsg")
   263  	if out, err := cmd.CombinedOutput(); err != nil {
   264  		return "", fmt.Errorf("failed to run adb shell: %w\n%s", err, out)
   265  	}
   266  	time.Sleep(500 * time.Millisecond)
   267  	close(done)
   268  
   269  	var anyErr error
   270  	for range output {
   271  		err := <-errors
   272  		if anyErr == nil && err != nil {
   273  			anyErr = err
   274  		}
   275  	}
   276  
   277  	con := ""
   278  	for con1, out := range output {
   279  		if bytes.Contains(*out, []byte(unique)) {
   280  			if con == "" {
   281  				con = con1
   282  			} else {
   283  				anyErr = fmt.Errorf("device is associated with several consoles: %v and %v", con, con1)
   284  			}
   285  		}
   286  	}
   287  
   288  	if con == "" {
   289  		if anyErr != nil {
   290  			return "", anyErr
   291  		}
   292  		return "", fmt.Errorf("no console is associated with this device")
   293  	}
   294  	return con, nil
   295  }
   296  
   297  func (inst *instance) Forward(port int) (string, error) {
   298  	var err error
   299  	for i := 0; i < 1000; i++ {
   300  		devicePort := vmimpl.RandomPort()
   301  		_, err = inst.adb("reverse", fmt.Sprintf("tcp:%v", devicePort), fmt.Sprintf("tcp:%v", port))
   302  		if err == nil {
   303  			return fmt.Sprintf("127.0.0.1:%v", devicePort), nil
   304  		}
   305  	}
   306  	return "", err
   307  }
   308  
   309  func (inst *instance) adb(args ...string) ([]byte, error) {
   310  	return inst.adbWithTimeout(time.Minute, args...)
   311  }
   312  
   313  func (inst *instance) adbWithTimeout(timeout time.Duration, args ...string) ([]byte, error) {
   314  	if inst.debug {
   315  		log.Logf(0, "executing adb %+v", args)
   316  	}
   317  	args = append([]string{"-s", inst.device}, args...)
   318  	out, err := osutil.RunCmd(timeout, "", inst.adbBin, args...)
   319  	if inst.debug {
   320  		log.Logf(0, "adb returned")
   321  	}
   322  	return out, err
   323  }
   324  
   325  func (inst *instance) waitForBootCompletion() {
   326  	// ADB connects to a phone and starts syz-fuzzer while the phone is still booting.
   327  	// This enables syzkaller to create a race condition which in certain cases doesn't
   328  	// allow the phone to finalize initialization.
   329  	// To determine whether a system has booted and started all system processes and
   330  	// services we wait for a process named 'com.android.systemui' to start. It's possible
   331  	// that in the future a new devices which doesn't have 'systemui' process will be fuzzed
   332  	// with adb, in this case this code should be modified with a new process name to search for.
   333  	log.Logf(2, "waiting for boot completion")
   334  
   335  	sleepTime := 5
   336  	sleepDuration := time.Duration(sleepTime) * time.Second
   337  	maxWaitTime := 60 * 3 // 3 minutes to wait until boot completion
   338  	maxRetries := maxWaitTime / sleepTime
   339  	i := 0
   340  	for ; i < maxRetries; i++ {
   341  		time.Sleep(sleepDuration)
   342  
   343  		if out, err := inst.adb("shell", "pgrep systemui | wc -l"); err == nil {
   344  			count := parseAdbOutToInt(out)
   345  			if count != 0 {
   346  				log.Logf(0, "boot completed")
   347  				break
   348  			}
   349  		} else {
   350  			log.Logf(0, "failed to execute command 'pgrep systemui | wc -l', %v", err)
   351  			break
   352  		}
   353  	}
   354  	if i == maxRetries {
   355  		log.Logf(0, "failed to determine boot completion, can't find 'com.android.systemui' process")
   356  	}
   357  }
   358  
   359  func (inst *instance) repair() error {
   360  	// Assume that the device is in a bad state initially and reboot it.
   361  	// Ignore errors, maybe we will manage to reboot it anyway.
   362  	if inst.cfg.RepairScript != "" {
   363  		if err := inst.runScript(inst.cfg.RepairScript); err != nil {
   364  			return err
   365  		}
   366  	}
   367  	inst.waitForSSH()
   368  	// History: adb reboot episodically hangs, so we used a more reliable way:
   369  	// using syz-executor to issue reboot syscall. However, this has stopped
   370  	// working, probably due to the introduction of seccomp. Therefore,
   371  	// we revert this to `adb shell reboot` in the meantime, until a more
   372  	// reliable solution can be sought out.
   373  	if inst.cfg.TargetReboot {
   374  		if _, err := inst.adb("shell", "reboot"); err != nil {
   375  			return err
   376  		}
   377  		// Now give it another 5 minutes to boot.
   378  		if !vmimpl.SleepInterruptible(10 * time.Second) {
   379  			return fmt.Errorf("shutdown in progress")
   380  		}
   381  		if err := inst.waitForSSH(); err != nil {
   382  			return err
   383  		}
   384  	}
   385  	// Switch to root for userdebug builds.
   386  	inst.adb("root")
   387  	inst.waitForSSH()
   388  	inst.waitForBootCompletion()
   389  
   390  	// Mount debugfs.
   391  	if _, err := inst.adb("shell", "ls /sys/kernel/debug/kcov"); err != nil {
   392  		log.Logf(2, "debugfs was unmounted mounting")
   393  		// This prop only exist on Android 12+
   394  		inst.adb("shell", "setprop persist.dbg.keep_debugfs_mounted 1")
   395  		if _, err := inst.adb("shell", "mount -t debugfs debugfs /sys/kernel/debug "+
   396  			"&& chmod 0755 /sys/kernel/debug"); err != nil {
   397  			return err
   398  		}
   399  	}
   400  	if inst.cfg.StartupScript != "" {
   401  		if err := inst.runScript(inst.cfg.StartupScript); err != nil {
   402  			return err
   403  		}
   404  	}
   405  	return nil
   406  }
   407  
   408  func (inst *instance) runScript(script string) error {
   409  	log.Logf(2, "adb: executing %s", script)
   410  	// Execute the contents of the script.
   411  	contents, err := os.ReadFile(script)
   412  	if err != nil {
   413  		return fmt.Errorf("unable to read %s: %w", script, err)
   414  	}
   415  	c := string(contents)
   416  	output, err := osutil.RunCmd(5*time.Minute, "", "sh", "-c", c)
   417  	if err != nil {
   418  		return fmt.Errorf("failed to execute %s: %w", script, err)
   419  	}
   420  	log.Logf(2, "adb: execute %s output\n%s", script, output)
   421  	log.Logf(2, "adb: done executing %s", script)
   422  	return nil
   423  }
   424  
   425  func (inst *instance) waitForSSH() error {
   426  	if !vmimpl.SleepInterruptible(time.Second) {
   427  		return fmt.Errorf("shutdown in progress")
   428  	}
   429  
   430  	if _, err := inst.adbWithTimeout(10*time.Minute, "wait-for-device"); err != nil {
   431  		return fmt.Errorf("instance is dead and unrepairable: %w", err)
   432  	}
   433  
   434  	return nil
   435  }
   436  
   437  func (inst *instance) checkBatteryLevel() error {
   438  	const (
   439  		minLevel      = 20
   440  		requiredLevel = 30
   441  	)
   442  	val, err := inst.getBatteryLevel(30)
   443  	if err != nil {
   444  		return err
   445  	}
   446  	if val >= minLevel {
   447  		log.Logf(0, "device %v: battery level %v%%, OK", inst.device, val)
   448  		return nil
   449  	}
   450  	for {
   451  		log.Logf(0, "device %v: battery level %v%%, waiting for %v%%", inst.device, val, requiredLevel)
   452  		if !vmimpl.SleepInterruptible(time.Minute) {
   453  			return nil
   454  		}
   455  		val, err = inst.getBatteryLevel(0)
   456  		if err != nil {
   457  			return err
   458  		}
   459  		if val >= requiredLevel {
   460  			break
   461  		}
   462  	}
   463  	return nil
   464  }
   465  
   466  func (inst *instance) getBatteryLevel(numRetry int) (int, error) {
   467  	out, err := inst.adb("shell", "dumpsys battery | grep level:")
   468  
   469  	// Allow for retrying for devices that does not boot up so fast.
   470  	for ; numRetry >= 0 && err != nil; numRetry-- {
   471  		if numRetry > 0 {
   472  			// Sleep for 5 seconds before retrying.
   473  			time.Sleep(5 * time.Second)
   474  			out, err = inst.adb("shell", "dumpsys battery | grep level:")
   475  		}
   476  	}
   477  	if err != nil {
   478  		return 0, err
   479  	}
   480  	val := parseAdbOutToInt(out)
   481  	if val == 0 {
   482  		return 0, fmt.Errorf("failed to parse 'dumpsys battery' output: %s", out)
   483  	}
   484  	return val, nil
   485  }
   486  
   487  func (inst *instance) Close() {
   488  	close(inst.closed)
   489  }
   490  
   491  func (inst *instance) Copy(hostSrc string) (string, error) {
   492  	vmDst := filepath.Join("/data", filepath.Base(hostSrc))
   493  	if _, err := inst.adb("push", hostSrc, vmDst); err != nil {
   494  		return "", err
   495  	}
   496  	return vmDst, nil
   497  }
   498  
   499  // Check if the device is cuttlefish on remote vm.
   500  func isRemoteCuttlefish(dev string) (bool, string) {
   501  	if !strings.Contains(dev, ":") {
   502  		return false, ""
   503  	}
   504  	ip := strings.Split(dev, ":")[0]
   505  	if ip == "localhost" || ip == "0.0.0.0" || ip == "127.0.0.1" {
   506  		return false, ip
   507  	}
   508  	return true, ip
   509  }
   510  
   511  func (inst *instance) Run(timeout time.Duration, stop <-chan bool, command string) (
   512  	<-chan []byte, <-chan error, error) {
   513  	var tty io.ReadCloser
   514  	var err error
   515  
   516  	if ok, ip := isRemoteCuttlefish(inst.device); ok {
   517  		tty, err = vmimpl.OpenRemoteKernelLog(ip, inst.console)
   518  	} else if inst.console == "adb" {
   519  		tty, err = vmimpl.OpenAdbConsole(inst.adbBin, inst.device)
   520  	} else {
   521  		tty, err = vmimpl.OpenConsole(inst.console)
   522  	}
   523  	if err != nil {
   524  		return nil, nil, err
   525  	}
   526  
   527  	adbRpipe, adbWpipe, err := osutil.LongPipe()
   528  	if err != nil {
   529  		tty.Close()
   530  		return nil, nil, err
   531  	}
   532  	if inst.debug {
   533  		log.Logf(0, "starting: adb shell %v", command)
   534  	}
   535  	adb := osutil.Command(inst.adbBin, "-s", inst.device, "shell", "cd /data; "+command)
   536  	adb.Stdout = adbWpipe
   537  	adb.Stderr = adbWpipe
   538  	if err := adb.Start(); err != nil {
   539  		tty.Close()
   540  		adbRpipe.Close()
   541  		adbWpipe.Close()
   542  		return nil, nil, fmt.Errorf("failed to start adb: %w", err)
   543  	}
   544  	adbWpipe.Close()
   545  
   546  	var tee io.Writer
   547  	if inst.debug {
   548  		tee = os.Stdout
   549  	}
   550  	merger := vmimpl.NewOutputMerger(tee)
   551  	merger.Add("console", tty)
   552  	merger.Add("adb", adbRpipe)
   553  
   554  	return vmimpl.Multiplex(adb, merger, tty, timeout, stop, inst.closed, inst.debug)
   555  }
   556  
   557  func (inst *instance) Diagnose(rep *report.Report) ([]byte, bool) {
   558  	return nil, false
   559  }