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 }