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