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  }