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 }