github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/vm/vmware/vmware.go (about) 1 // Copyright 2020 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 vmware 5 6 import ( 7 "context" 8 "fmt" 9 "io" 10 "net" 11 "os" 12 "os/exec" 13 "path/filepath" 14 "strconv" 15 "strings" 16 "time" 17 18 "github.com/google/syzkaller/pkg/config" 19 "github.com/google/syzkaller/pkg/log" 20 "github.com/google/syzkaller/pkg/osutil" 21 "github.com/google/syzkaller/pkg/report" 22 "github.com/google/syzkaller/sys/targets" 23 "github.com/google/syzkaller/vm/vmimpl" 24 ) 25 26 func init() { 27 vmimpl.Register("vmware", vmimpl.Type{ 28 Ctor: ctor, 29 }) 30 } 31 32 type Config struct { 33 BaseVMX string `json:"base_vmx"` // location of the base vmx 34 Count int `json:"count"` // number of VMs to run in parallel 35 } 36 37 type Pool struct { 38 env *vmimpl.Env 39 cfg *Config 40 } 41 42 type instance struct { 43 cfg *Config 44 baseVMX string 45 vmx string 46 ipAddr string 47 closed chan bool 48 debug bool 49 sshuser string 50 sshkey string 51 forwardPort int 52 timeouts targets.Timeouts 53 } 54 55 func ctor(env *vmimpl.Env) (vmimpl.Pool, error) { 56 cfg := &Config{} 57 if err := config.LoadData(env.Config, cfg); err != nil { 58 return nil, err 59 } 60 if cfg.BaseVMX == "" { 61 return nil, fmt.Errorf("config param base_vmx is empty") 62 } 63 if cfg.Count < 1 || cfg.Count > 128 { 64 return nil, fmt.Errorf("invalid config param count: %v, want [1, 128]", cfg.Count) 65 } 66 if _, err := exec.LookPath("vmrun"); err != nil { 67 return nil, fmt.Errorf("cannot find vmrun") 68 } 69 pool := &Pool{ 70 cfg: cfg, 71 env: env, 72 } 73 return pool, nil 74 } 75 76 func (pool *Pool) Count() int { 77 return pool.cfg.Count 78 } 79 80 func (pool *Pool) Create(_ context.Context, workdir string, index int) (vmimpl.Instance, error) { 81 createTime := strconv.FormatInt(time.Now().UnixNano(), 10) 82 vmx := filepath.Join(workdir, createTime, "syzkaller.vmx") 83 sshkey := pool.env.SSHKey 84 sshuser := pool.env.SSHUser 85 inst := &instance{ 86 cfg: pool.cfg, 87 debug: pool.env.Debug, 88 baseVMX: pool.cfg.BaseVMX, 89 vmx: vmx, 90 sshkey: sshkey, 91 sshuser: sshuser, 92 closed: make(chan bool), 93 timeouts: pool.env.Timeouts, 94 } 95 if err := inst.clone(); err != nil { 96 return nil, err 97 } 98 if err := inst.boot(); err != nil { 99 return nil, err 100 } 101 return inst, nil 102 } 103 104 func (inst *instance) clone() error { 105 if inst.debug { 106 log.Logf(0, "cloning %v to %v", inst.baseVMX, inst.vmx) 107 } 108 if _, err := osutil.RunCmd(2*time.Minute, "", "vmrun", "clone", inst.baseVMX, inst.vmx, "full"); err != nil { 109 return err 110 } 111 return nil 112 } 113 114 func (inst *instance) boot() error { 115 if inst.debug { 116 log.Logf(0, "starting %v", inst.vmx) 117 } 118 if _, err := osutil.RunCmd(5*time.Minute, "", "vmrun", "start", inst.vmx, "nogui"); err != nil { 119 return err 120 } 121 if inst.debug { 122 log.Logf(0, "getting IP of %v", inst.vmx) 123 } 124 ip, err := osutil.RunCmd(5*time.Minute, "", "vmrun", "getGuestIPAddress", inst.vmx, "-wait") 125 if err != nil { 126 return err 127 } 128 inst.ipAddr = strings.TrimSuffix(string(ip), "\n") 129 if inst.debug { 130 log.Logf(0, "VM %v has IP: %v", inst.vmx, inst.ipAddr) 131 } 132 return nil 133 } 134 135 func (inst *instance) Forward(port int) (string, error) { 136 if inst.forwardPort != 0 { 137 return "", fmt.Errorf("isolated: Forward port already set") 138 } 139 if port == 0 { 140 return "", fmt.Errorf("isolated: Forward port is zero") 141 } 142 inst.forwardPort = port 143 return fmt.Sprintf("127.0.0.1:%v", port), nil 144 } 145 146 func (inst *instance) Close() error { 147 if inst.debug { 148 log.Logf(0, "stopping %v", inst.vmx) 149 } 150 osutil.RunCmd(2*time.Minute, "", "vmrun", "stop", inst.vmx, "hard") 151 if inst.debug { 152 log.Logf(0, "deleting %v", inst.vmx) 153 } 154 osutil.RunCmd(2*time.Minute, "", "vmrun", "deleteVM", inst.vmx) 155 close(inst.closed) 156 return nil 157 } 158 159 func (inst *instance) Copy(hostSrc string) (string, error) { 160 base := filepath.Base(hostSrc) 161 vmDst := filepath.Join("/", base) 162 163 args := append(vmimpl.SCPArgs(inst.debug, inst.sshkey, 22, false), 164 hostSrc, fmt.Sprintf("%v@%v:%v", inst.sshuser, inst.ipAddr, vmDst)) 165 166 if inst.debug { 167 log.Logf(0, "running command: scp %#v", args) 168 } 169 170 _, err := osutil.RunCmd(3*time.Minute, "", "scp", args...) 171 if err != nil { 172 return "", err 173 } 174 return vmDst, nil 175 } 176 177 func (inst *instance) Run(ctx context.Context, command string) ( 178 <-chan []byte, <-chan error, error) { 179 vmxDir := filepath.Dir(inst.vmx) 180 serial := filepath.Join(vmxDir, "serial") 181 dmesg, err := net.Dial("unix", serial) 182 if err != nil { 183 return nil, nil, err 184 } 185 186 rpipe, wpipe, err := osutil.LongPipe() 187 if err != nil { 188 dmesg.Close() 189 return nil, nil, err 190 } 191 192 args := vmimpl.SSHArgs(inst.debug, inst.sshkey, 22, false) 193 // Forward target port as part of the ssh connection (reverse proxy) 194 if inst.forwardPort != 0 { 195 proxy := fmt.Sprintf("%v:127.0.0.1:%v", inst.forwardPort, inst.forwardPort) 196 args = append(args, "-R", proxy) 197 } 198 args = append(args, inst.sshuser+"@"+inst.ipAddr, "cd / && exec "+command) 199 if inst.debug { 200 log.Logf(0, "running command: ssh %#v", args) 201 } 202 cmd := osutil.Command("ssh", args...) 203 cmd.Stdout = wpipe 204 cmd.Stderr = wpipe 205 if err := cmd.Start(); err != nil { 206 dmesg.Close() 207 rpipe.Close() 208 wpipe.Close() 209 return nil, nil, err 210 } 211 wpipe.Close() 212 213 var tee io.Writer 214 if inst.debug { 215 tee = os.Stdout 216 } 217 merger := vmimpl.NewOutputMerger(tee) 218 merger.Add("dmesg", dmesg) 219 merger.Add("ssh", rpipe) 220 221 return vmimpl.Multiplex(ctx, cmd, merger, vmimpl.MultiplexConfig{ 222 Console: dmesg, 223 Close: inst.closed, 224 Debug: inst.debug, 225 Scale: inst.timeouts.Scale, 226 }) 227 } 228 229 func (inst *instance) Diagnose(rep *report.Report) ([]byte, bool) { 230 return nil, false 231 }