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  }