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