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 }