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