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

     1  // Copyright 2022 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  // Package cuttlefish allows to use Cuttlefish Android emulators hosted on Google Compute Engine
     5  // (GCE) virtual machines as VMs. It is assumed that syz-manager also runs on GCE as VMs are
     6  // created in the current project/zone.
     7  //
     8  // See https://cloud.google.com/compute/docs for details.
     9  // In particular, how to build GCE-compatible images:
    10  // https://cloud.google.com/compute/docs/tutorials/building-images
    11  package cuttlefish
    12  
    13  import (
    14  	"fmt"
    15  	"os/exec"
    16  	"path/filepath"
    17  	"time"
    18  
    19  	"github.com/google/syzkaller/pkg/osutil"
    20  	"github.com/google/syzkaller/pkg/report"
    21  	"github.com/google/syzkaller/vm/gce"
    22  	"github.com/google/syzkaller/vm/vmimpl"
    23  )
    24  
    25  const (
    26  	deviceRoot     = "/data/fuzz"
    27  	consoleReadCmd = "tail -f cuttlefish/instances/cvd-1/kernel.log"
    28  )
    29  
    30  func init() {
    31  	vmimpl.Register("cuttlefish", ctor, true)
    32  }
    33  
    34  type Pool struct {
    35  	env     *vmimpl.Env
    36  	gcePool *gce.Pool
    37  }
    38  
    39  type instance struct {
    40  	name    string
    41  	sshKey  string
    42  	sshUser string
    43  	debug   bool
    44  	gceInst vmimpl.Instance
    45  }
    46  
    47  func ctor(env *vmimpl.Env) (vmimpl.Pool, error) {
    48  	gcePool, err := gce.Ctor(env, consoleReadCmd)
    49  	if err != nil {
    50  		return nil, fmt.Errorf("failed to create underlying GCE pool: %w", err)
    51  	}
    52  
    53  	pool := &Pool{
    54  		env:     env,
    55  		gcePool: gcePool,
    56  	}
    57  
    58  	return pool, nil
    59  }
    60  
    61  func (pool *Pool) Count() int {
    62  	return pool.gcePool.Count()
    63  }
    64  
    65  func (pool *Pool) Create(workdir string, index int) (vmimpl.Instance, error) {
    66  	gceInst, err := pool.gcePool.Create(workdir, index)
    67  	if err != nil {
    68  		return nil, fmt.Errorf("failed to create underlying gce instance: %w", err)
    69  	}
    70  
    71  	inst := &instance{
    72  		name:    fmt.Sprintf("%v-%v", pool.env.Name, index),
    73  		sshKey:  pool.env.SSHKey,
    74  		sshUser: pool.env.SSHUser,
    75  		debug:   pool.env.Debug,
    76  		gceInst: gceInst,
    77  	}
    78  
    79  	// Start a Cuttlefish device on the GCE instance.
    80  	if err := inst.runOnHost(10*time.Minute,
    81  		fmt.Sprintf("./bin/launch_cvd -daemon -kernel_path=./bzImage -initramfs_path=./initramfs.img"+
    82  			" --noenable_sandbox -report_anonymous_usage_stats=n --memory_mb=8192")); err != nil {
    83  		return nil, fmt.Errorf("failed to start cuttlefish: %w", err)
    84  	}
    85  
    86  	if err := inst.runOnHost(10*time.Minute, "adb wait-for-device"); err != nil {
    87  		return nil, fmt.Errorf("failed while waiting for device: %w", err)
    88  	}
    89  
    90  	if err := inst.runOnHost(5*time.Minute, "adb root"); err != nil {
    91  		return nil, fmt.Errorf("failed to get root access to device: %w", err)
    92  	}
    93  
    94  	if err := inst.runOnHost(5*time.Minute, fmt.Sprintf("adb shell '"+
    95  		"setprop persist.dbg.keep_debugfs_mounted 1;"+
    96  		"mount -t debugfs debugfs /sys/kernel/debug;"+
    97  		"chmod 0755 /sys/kernel/debug;"+
    98  		"mkdir %s;"+
    99  		"'", deviceRoot)); err != nil {
   100  		return nil, fmt.Errorf("failed to mount debugfs to /sys/kernel/debug: %w", err)
   101  	}
   102  
   103  	return inst, nil
   104  }
   105  
   106  func (inst *instance) sshArgs(command string) []string {
   107  	sshArgs := append(vmimpl.SSHArgs(inst.debug, inst.sshKey, 22, false), inst.sshUser+"@"+inst.name)
   108  	if inst.sshUser != "root" {
   109  		return append(sshArgs, "sudo", "bash", "-c", "'"+command+"'")
   110  	}
   111  	return append(sshArgs, command)
   112  }
   113  
   114  func (inst *instance) runOnHost(timeout time.Duration, command string) error {
   115  	_, err := osutil.RunCmd(timeout, "/root", "ssh", inst.sshArgs(command)...)
   116  
   117  	return err
   118  }
   119  
   120  func (inst *instance) Copy(hostSrc string) (string, error) {
   121  	gceDst, err := inst.gceInst.Copy(hostSrc)
   122  	if err != nil {
   123  		return "", fmt.Errorf("error copying to worker instance: %w", err)
   124  	}
   125  
   126  	deviceDst := filepath.Join(deviceRoot, filepath.Base(hostSrc))
   127  	pushCmd := fmt.Sprintf("adb push %s %s", gceDst, deviceDst)
   128  
   129  	if err := inst.runOnHost(5*time.Minute, pushCmd); err != nil {
   130  		return "", fmt.Errorf("error pushing to device: %w", err)
   131  	}
   132  
   133  	return deviceDst, nil
   134  }
   135  
   136  func (inst *instance) Forward(port int) (string, error) {
   137  	hostForward, err := inst.gceInst.Forward(port)
   138  	if err != nil {
   139  		return "", fmt.Errorf("failed to get IP/port from GCE instance: %w", err)
   140  	}
   141  
   142  	// Run socat in the background. This hangs when run from runOnHost().
   143  	cmdStr := fmt.Sprintf("nohup socat TCP-LISTEN:%d,fork TCP:%s", port, hostForward)
   144  	cmdArgs := append([]string{"-f"}, inst.sshArgs(cmdStr)...)
   145  	cmd := exec.Command("ssh", cmdArgs...)
   146  	cmd.Dir = "/root"
   147  	if err := cmd.Run(); err != nil {
   148  		return "", fmt.Errorf("unable to forward port on host: %w", err)
   149  	}
   150  
   151  	for i := 0; i < 100; i++ {
   152  		devicePort := vmimpl.RandomPort()
   153  		cmd := fmt.Sprintf("adb reverse tcp:%d tcp:%d", devicePort, port)
   154  		err = inst.runOnHost(10*time.Second, cmd)
   155  		if err == nil {
   156  			return fmt.Sprintf("127.0.0.1:%d", devicePort), nil
   157  		}
   158  	}
   159  
   160  	return "", fmt.Errorf("unable to forward port on device: %w", err)
   161  }
   162  
   163  func (inst *instance) Close() {
   164  	inst.gceInst.Close()
   165  }
   166  
   167  func (inst *instance) Run(timeout time.Duration, stop <-chan bool, command string) (
   168  	<-chan []byte, <-chan error, error) {
   169  	return inst.gceInst.Run(timeout, stop, fmt.Sprintf("adb shell 'cd %s; %s'", deviceRoot, command))
   170  }
   171  
   172  func (inst *instance) Diagnose(rep *report.Report) ([]byte, bool) {
   173  	return nil, false
   174  }