github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/vm/qemu/qemu.go (about)

     1  // Copyright 2015 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 qemu
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"encoding/json"
    10  	"errors"
    11  	"fmt"
    12  	"io"
    13  	"net"
    14  	"os"
    15  	"os/exec"
    16  	"path/filepath"
    17  	"regexp"
    18  	"strconv"
    19  	"strings"
    20  	"time"
    21  
    22  	"github.com/google/syzkaller/pkg/config"
    23  	"github.com/google/syzkaller/pkg/log"
    24  	"github.com/google/syzkaller/pkg/osutil"
    25  	"github.com/google/syzkaller/pkg/report"
    26  	"github.com/google/syzkaller/pkg/report/crash"
    27  	"github.com/google/syzkaller/sys/targets"
    28  	"github.com/google/syzkaller/vm/vmimpl"
    29  )
    30  
    31  func init() {
    32  	var _ vmimpl.Infoer = (*instance)(nil)
    33  	vmimpl.Register("qemu", vmimpl.Type{
    34  		Ctor:       ctor,
    35  		Overcommit: true,
    36  	})
    37  }
    38  
    39  type Config struct {
    40  	// Number of VMs to run in parallel (1 by default).
    41  	Count int `json:"count"`
    42  	// QEMU binary name (optional).
    43  	// If not specified, qemu-system-arch is used by default.
    44  	Qemu string `json:"qemu"`
    45  	// Additional command line arguments for the QEMU binary.
    46  	// If not specified, the default value specifies machine type, cpu and usually contains -enable-kvm.
    47  	// If you provide this parameter, it needs to contain the desired machine, cpu
    48  	// and include -enable-kvm if necessary.
    49  	// "{{INDEX}}" is replaced with 0-based index of the VM (from 0 to Count-1).
    50  	// "{{TEMPLATE}}" is replaced with the path to a copy of workdir/template dir.
    51  	// "{{TCP_PORT}}" is replaced with a random free TCP port
    52  	// "{{FN%8}}" is replaced with PCI BDF (Function#%8) and PCI BDF Dev# += index/8
    53  	QemuArgs string `json:"qemu_args"`
    54  	// Location of the kernel for injected boot (e.g. arch/x86/boot/bzImage, optional).
    55  	// This is passed to QEMU as the -kernel option.
    56  	Kernel string `json:"kernel"`
    57  	// Additional command line options for the booting kernel, for example `root=/dev/sda1`.
    58  	// Can only be specified with kernel.
    59  	Cmdline string `json:"cmdline"`
    60  	// Initial ramdisk, passed via -initrd QEMU flag (optional).
    61  	Initrd string `json:"initrd"`
    62  	// QEMU image device.
    63  	// The default value "hda" is transformed to "-hda image" for QEMU.
    64  	// The modern way of describing QEMU hard disks is supported, so the value
    65  	// "drive index=0,media=disk,file=" is transformed to "-drive index=0,media=disk,file=image" for QEMU.
    66  	ImageDevice string `json:"image_device"`
    67  	// EFI images containing the EFI itself, as well as this VMs EFI variables.
    68  	EfiCodeDevice string `json:"efi_code_device"`
    69  	EfiVarsDevice string `json:"efi_vars_device"`
    70  	// QEMU network device type to use.
    71  	// If not specified, some default per-arch value will be used.
    72  	// See the full list with qemu-system-x86_64 -device help.
    73  	NetDev string `json:"network_device"`
    74  	// Number of VM CPUs (1 by default).
    75  	CPU int `json:"cpu"`
    76  	// Amount of VM memory in MiB (1024 by default).
    77  	Mem int `json:"mem"`
    78  	// For building kernels without -snapshot for pkg/build (true by default).
    79  	Snapshot bool `json:"snapshot"`
    80  	// Magic key used to dongle macOS to the device.
    81  	AppleSmcOsk string `json:"apple_smc_osk"`
    82  }
    83  
    84  type Pool struct {
    85  	env        *vmimpl.Env
    86  	cfg        *Config
    87  	target     *targets.Target
    88  	archConfig *archConfig
    89  	version    string
    90  }
    91  
    92  type instance struct {
    93  	index      int
    94  	cfg        *Config
    95  	target     *targets.Target
    96  	archConfig *archConfig
    97  	version    string
    98  	args       []string
    99  	image      string
   100  	debug      bool
   101  	os         string
   102  	workdir    string
   103  	vmimpl.SSHOptions
   104  	timeouts    targets.Timeouts
   105  	monport     int
   106  	forwardPort int
   107  	mon         net.Conn
   108  	monEnc      *json.Encoder
   109  	monDec      *json.Decoder
   110  	rpipe       io.ReadCloser
   111  	wpipe       io.WriteCloser
   112  	qemu        *exec.Cmd
   113  	merger      *vmimpl.OutputMerger
   114  	files       map[string]string
   115  	*snapshot
   116  }
   117  
   118  type archConfig struct {
   119  	Qemu      string
   120  	QemuArgs  string
   121  	TargetDir string // "/" by default
   122  	NetDev    string // default network device type (see the full list with qemu-system-x86_64 -device help)
   123  	RngDev    string // default rng device (optional)
   124  	// UseNewQemuImageOptions specifies whether the arch uses "new" QEMU image device options.
   125  	UseNewQemuImageOptions bool
   126  	CmdLine                []string
   127  }
   128  
   129  var archConfigs = map[string]*archConfig{
   130  	"linux/amd64": {
   131  		Qemu:     "qemu-system-x86_64",
   132  		QemuArgs: "-enable-kvm -cpu host,migratable=off",
   133  		// e1000e fails on recent Debian distros with:
   134  		// Initialization of device e1000e failed: failed to find romfile "efi-e1000e.rom
   135  		// But other arches don't use e1000e, e.g. arm64 uses virtio by default.
   136  		NetDev: "e1000",
   137  		RngDev: "virtio-rng-pci",
   138  		CmdLine: []string{
   139  			"root=/dev/sda",
   140  			"console=ttyS0",
   141  		},
   142  	},
   143  	"linux/386": {
   144  		Qemu:   "qemu-system-i386",
   145  		NetDev: "e1000",
   146  		RngDev: "virtio-rng-pci",
   147  		CmdLine: []string{
   148  			"root=/dev/sda",
   149  			"console=ttyS0",
   150  		},
   151  	},
   152  	"linux/arm64": {
   153  		Qemu: "qemu-system-aarch64",
   154  		// Disable SVE and pointer authentication for now, they significantly slow down
   155  		// the emulation and are unlikely to bring a lot of new coverage.
   156  		QemuArgs: strings.Join([]string{"-machine virt,virtualization=on,gic-version=max ",
   157  			"-cpu max,sve128=on,pauth=off"}, ""),
   158  		NetDev: "virtio-net-pci",
   159  		RngDev: "virtio-rng-pci",
   160  		CmdLine: []string{
   161  			"root=/dev/vda",
   162  			"console=ttyAMA0",
   163  		},
   164  	},
   165  	"linux/arm": {
   166  		Qemu: "qemu-system-arm",
   167  		// For some reason, new qemu-system-arm versions complain that "The only valid type is: cortex-a15".
   168  		QemuArgs:               "-machine vexpress-a15 -cpu cortex-a15 -accel tcg,thread=multi",
   169  		NetDev:                 "virtio-net-device",
   170  		RngDev:                 "virtio-rng-device",
   171  		UseNewQemuImageOptions: true,
   172  		CmdLine: []string{
   173  			"root=/dev/vda",
   174  			"console=ttyAMA0",
   175  		},
   176  	},
   177  	"linux/mips64le": {
   178  		Qemu:     "qemu-system-mips64el",
   179  		QemuArgs: "-M malta -cpu MIPS64R2-generic -nodefaults",
   180  		NetDev:   "e1000",
   181  		RngDev:   "virtio-rng-pci",
   182  		CmdLine: []string{
   183  			"root=/dev/sda",
   184  			"console=ttyS0",
   185  		},
   186  	},
   187  	"linux/ppc64le": {
   188  		Qemu:     "qemu-system-ppc64",
   189  		QemuArgs: "-enable-kvm -vga none",
   190  		NetDev:   "virtio-net-pci",
   191  		RngDev:   "virtio-rng-pci",
   192  	},
   193  	"linux/riscv64": {
   194  		Qemu:                   "qemu-system-riscv64",
   195  		QemuArgs:               "-machine virt -cpu rv64,sv48=on",
   196  		NetDev:                 "virtio-net-pci",
   197  		RngDev:                 "virtio-rng-pci",
   198  		UseNewQemuImageOptions: true,
   199  		CmdLine: []string{
   200  			"root=/dev/vda",
   201  			"console=ttyS0",
   202  		},
   203  	},
   204  	"linux/s390x": {
   205  		Qemu:     "qemu-system-s390x",
   206  		QemuArgs: "-M s390-ccw-virtio -cpu max",
   207  		NetDev:   "virtio-net-ccw",
   208  		RngDev:   "virtio-rng-ccw",
   209  		CmdLine: []string{
   210  			"root=/dev/vda",
   211  			// The following kernel parameters is a temporary
   212  			// work-around for not having CONFIG_CMDLINE on s390x.
   213  			"net.ifnames=0",
   214  			"biosdevname=0",
   215  		},
   216  	},
   217  	"freebsd/amd64": {
   218  		Qemu:     "qemu-system-x86_64",
   219  		QemuArgs: "-enable-kvm",
   220  		NetDev:   "e1000",
   221  		RngDev:   "virtio-rng-pci",
   222  	},
   223  	"freebsd/riscv64": {
   224  		Qemu:                   "qemu-system-riscv64",
   225  		QemuArgs:               "-machine virt",
   226  		NetDev:                 "virtio-net-pci",
   227  		RngDev:                 "virtio-rng-pci",
   228  		UseNewQemuImageOptions: true,
   229  	},
   230  	"darwin/amd64": {
   231  		Qemu: "qemu-system-x86_64",
   232  		QemuArgs: strings.Join([]string{
   233  			"-accel hvf -machine q35 ",
   234  			"-cpu Penryn,vendor=GenuineIntel,+invtsc,vmware-cpuid-freq=on,",
   235  			"+pcid,+ssse3,+sse4.2,+popcnt,+avx,+aes,+xsave,+xsaveopt,check ",
   236  		}, ""),
   237  		TargetDir: "/tmp",
   238  		NetDev:    "e1000-82545em",
   239  		RngDev:    "virtio-rng-pci",
   240  	},
   241  	"netbsd/amd64": {
   242  		Qemu:     "qemu-system-x86_64",
   243  		QemuArgs: "-enable-kvm",
   244  		NetDev:   "e1000",
   245  		RngDev:   "virtio-rng-pci",
   246  	},
   247  	"fuchsia/amd64": {
   248  		Qemu:      "qemu-system-x86_64",
   249  		QemuArgs:  "-enable-kvm -machine q35 -cpu host,migratable=off",
   250  		TargetDir: "/tmp",
   251  		NetDev:    "e1000",
   252  		RngDev:    "virtio-rng-pci",
   253  		CmdLine: []string{
   254  			"kernel.serial=legacy",
   255  			"kernel.halt-on-panic=true",
   256  			// Set long (300sec) thresholds for kernel lockup detector to
   257  			// prevent false alarms from potentially oversubscribed hosts.
   258  			// (For more context, see fxbug.dev/109612.)
   259  			"kernel.lockup-detector.critical-section-threshold-ms=300000",
   260  			"kernel.lockup-detector.critical-section-fatal-threshold-ms=300000",
   261  			"kernel.lockup-detector.heartbeat-age-threshold-ms=300000",
   262  			"kernel.lockup-detector.heartbeat-age-fatal-threshold-ms=300000",
   263  		},
   264  	},
   265  }
   266  
   267  func ctor(env *vmimpl.Env) (vmimpl.Pool, error) {
   268  	archConfig := archConfigs[env.OS+"/"+env.Arch]
   269  	cfg := &Config{
   270  		Count:       1,
   271  		CPU:         1,
   272  		Mem:         1024,
   273  		ImageDevice: "hda",
   274  		Qemu:        archConfig.Qemu,
   275  		QemuArgs:    archConfig.QemuArgs,
   276  		NetDev:      archConfig.NetDev,
   277  		Snapshot:    true,
   278  	}
   279  	if err := config.LoadData(env.Config, cfg); err != nil {
   280  		return nil, fmt.Errorf("failed to parse qemu vm config: %w", err)
   281  	}
   282  	if cfg.Count < 1 || cfg.Count > 1024 {
   283  		return nil, fmt.Errorf("invalid config param count: %v, want [1, 1024]", cfg.Count)
   284  	}
   285  	if _, err := exec.LookPath(cfg.Qemu); err != nil {
   286  		return nil, err
   287  	}
   288  	if env.Image == "9p" {
   289  		if env.OS != targets.Linux {
   290  			return nil, fmt.Errorf("9p image is supported for linux only")
   291  		}
   292  		if cfg.Kernel == "" {
   293  			return nil, fmt.Errorf("9p image requires kernel")
   294  		}
   295  	} else {
   296  		if !osutil.IsExist(env.Image) {
   297  			return nil, fmt.Errorf("image file '%v' does not exist", env.Image)
   298  		}
   299  	}
   300  	if cfg.CPU <= 0 || cfg.CPU > 1024 {
   301  		return nil, fmt.Errorf("bad qemu cpu: %v, want [1-1024]", cfg.CPU)
   302  	}
   303  	if cfg.Mem < 128 || cfg.Mem > 1048576 {
   304  		return nil, fmt.Errorf("bad qemu mem: %v, want [128-1048576]", cfg.Mem)
   305  	}
   306  	cfg.Kernel = osutil.Abs(cfg.Kernel)
   307  	cfg.Initrd = osutil.Abs(cfg.Initrd)
   308  
   309  	output, err := osutil.RunCmd(time.Minute, "", cfg.Qemu, "--version")
   310  	if err != nil {
   311  		return nil, err
   312  	}
   313  	version := string(bytes.Split(output, []byte{'\n'})[0])
   314  
   315  	pool := &Pool{
   316  		env:        env,
   317  		cfg:        cfg,
   318  		version:    version,
   319  		target:     targets.Get(env.OS, env.Arch),
   320  		archConfig: archConfig,
   321  	}
   322  	return pool, nil
   323  }
   324  
   325  func (pool *Pool) Count() int {
   326  	return pool.cfg.Count
   327  }
   328  
   329  func (pool *Pool) Create(ctx context.Context, workdir string, index int) (vmimpl.Instance, error) {
   330  	sshkey := pool.env.SSHKey
   331  	sshuser := pool.env.SSHUser
   332  	if pool.env.Image == "9p" {
   333  		sshkey = filepath.Join(workdir, "key")
   334  		sshuser = "root"
   335  		if _, err := osutil.RunCmd(10*time.Minute, "", "ssh-keygen", "-t", "rsa", "-b", "2048",
   336  			"-N", "", "-C", "", "-f", sshkey); err != nil {
   337  			return nil, err
   338  		}
   339  		initFile := filepath.Join(workdir, "init.sh")
   340  		if err := osutil.WriteExecFile(initFile, []byte(strings.ReplaceAll(initScript, "{{KEY}}", sshkey))); err != nil {
   341  			return nil, fmt.Errorf("failed to create init file: %w", err)
   342  		}
   343  	}
   344  
   345  	for i := 0; ; i++ {
   346  		if err := ctx.Err(); err != nil {
   347  			return nil, err
   348  		}
   349  		inst, err := pool.ctor(workdir, sshkey, sshuser, index)
   350  		if err == nil {
   351  			return inst, nil
   352  		}
   353  		if errors.Is(err, vmimpl.ErrCantSSH) {
   354  			// It is most likely a boot crash, just return the error as is.
   355  			return nil, err
   356  		}
   357  		// Older qemu prints "could", newer -- "Could".
   358  		if i < 1000 && strings.Contains(err.Error(), "ould not set up host forwarding rule") {
   359  			continue
   360  		}
   361  		if i < 1000 && strings.Contains(err.Error(), "Device or resource busy") {
   362  			continue
   363  		}
   364  		if i < 1000 && strings.Contains(err.Error(), "Address already in use") {
   365  			continue
   366  		}
   367  
   368  		return nil, err
   369  	}
   370  }
   371  
   372  func (pool *Pool) ctor(workdir, sshkey, sshuser string, index int) (*instance, error) {
   373  	inst := &instance{
   374  		index:      index,
   375  		cfg:        pool.cfg,
   376  		target:     pool.target,
   377  		archConfig: pool.archConfig,
   378  		version:    pool.version,
   379  		image:      pool.env.Image,
   380  		debug:      pool.env.Debug,
   381  		os:         pool.env.OS,
   382  		timeouts:   pool.env.Timeouts,
   383  		workdir:    workdir,
   384  		SSHOptions: vmimpl.SSHOptions{
   385  			Addr: "localhost",
   386  			Port: vmimpl.UnusedTCPPort(),
   387  			Key:  sshkey,
   388  			User: sshuser,
   389  		},
   390  	}
   391  	if pool.env.Snapshot {
   392  		inst.snapshot = new(snapshot)
   393  	}
   394  	if st, err := os.Stat(inst.image); err == nil && st.Size() == 0 {
   395  		// Some kernels may not need an image, however caller may still
   396  		// want to pass us a fake empty image because the rest of syzkaller
   397  		// assumes that an image is mandatory. So if the image is empty, we ignore it.
   398  		inst.image = ""
   399  	}
   400  	closeInst := inst
   401  	defer func() {
   402  		if closeInst != nil {
   403  			closeInst.Close()
   404  		}
   405  	}()
   406  
   407  	var err error
   408  	inst.rpipe, inst.wpipe, err = osutil.LongPipe()
   409  	if err != nil {
   410  		return nil, err
   411  	}
   412  
   413  	if err := inst.boot(); err != nil {
   414  		return nil, err
   415  	}
   416  
   417  	closeInst = nil
   418  	return inst, nil
   419  }
   420  
   421  func (inst *instance) Close() error {
   422  	if inst.qemu != nil {
   423  		inst.qemu.Process.Kill()
   424  		inst.qemu.Wait()
   425  	}
   426  	if inst.merger != nil {
   427  		inst.merger.Wait()
   428  	}
   429  	if inst.rpipe != nil {
   430  		inst.rpipe.Close()
   431  	}
   432  	if inst.wpipe != nil {
   433  		inst.wpipe.Close()
   434  	}
   435  	if inst.mon != nil {
   436  		inst.mon.Close()
   437  	}
   438  	if inst.snapshot != nil {
   439  		inst.snapshotClose()
   440  	}
   441  	return nil
   442  }
   443  
   444  func (inst *instance) boot() error {
   445  	inst.monport = vmimpl.UnusedTCPPort()
   446  	args, err := inst.buildQemuArgs()
   447  	if err != nil {
   448  		return err
   449  	}
   450  	if inst.debug {
   451  		log.Logf(0, "running command: %v %#v", inst.cfg.Qemu, args)
   452  	}
   453  	inst.args = args
   454  	qemu := osutil.Command(inst.cfg.Qemu, args...)
   455  	qemu.Stdout = inst.wpipe
   456  	qemu.Stderr = inst.wpipe
   457  	if err := qemu.Start(); err != nil {
   458  		return fmt.Errorf("failed to start %v %+v: %w", inst.cfg.Qemu, args, err)
   459  	}
   460  	inst.wpipe.Close()
   461  	inst.wpipe = nil
   462  	inst.qemu = qemu
   463  	// Qemu has started.
   464  
   465  	// Start output merger.
   466  	var tee io.Writer
   467  	if inst.debug {
   468  		tee = os.Stdout
   469  	}
   470  	inst.merger = vmimpl.NewOutputMerger(tee)
   471  	inst.merger.Add("qemu", inst.rpipe)
   472  	inst.rpipe = nil
   473  
   474  	var bootOutput []byte
   475  	bootOutputStop := make(chan bool)
   476  	go func() {
   477  		for {
   478  			select {
   479  			case out := <-inst.merger.Output:
   480  				bootOutput = append(bootOutput, out...)
   481  			case <-bootOutputStop:
   482  				close(bootOutputStop)
   483  				return
   484  			}
   485  		}
   486  	}()
   487  
   488  	if inst.snapshot != nil {
   489  		if err := inst.snapshotHandshake(); err != nil {
   490  			return err
   491  		}
   492  	}
   493  
   494  	if err := vmimpl.WaitForSSH(10*time.Minute*inst.timeouts.Scale, inst.SSHOptions,
   495  		inst.os, inst.merger.Err, false, inst.debug); err != nil {
   496  		bootOutputStop <- true
   497  		<-bootOutputStop
   498  		return vmimpl.MakeBootError(err, bootOutput)
   499  	}
   500  	bootOutputStop <- true
   501  	return nil
   502  }
   503  
   504  func (inst *instance) buildQemuArgs() ([]string, error) {
   505  	args := []string{
   506  		"-m", strconv.Itoa(inst.cfg.Mem),
   507  		"-smp", strconv.Itoa(inst.cfg.CPU),
   508  		"-chardev", fmt.Sprintf("socket,id=SOCKSYZ,server=on,wait=off,host=localhost,port=%v", inst.monport),
   509  		"-mon", "chardev=SOCKSYZ,mode=control",
   510  		"-display", "none",
   511  		"-serial", "stdio",
   512  		"-no-reboot",
   513  		"-name", fmt.Sprintf("VM-%v", inst.index),
   514  	}
   515  	if inst.archConfig.RngDev != "" {
   516  		args = append(args, "-device", inst.archConfig.RngDev)
   517  	}
   518  	templateDir := filepath.Join(inst.workdir, "template")
   519  	args = append(args, splitArgs(inst.cfg.QemuArgs, templateDir, inst.index)...)
   520  	args = append(args,
   521  		"-device", inst.cfg.NetDev+",netdev=net0",
   522  		"-netdev", fmt.Sprintf("user,id=net0,restrict=on,hostfwd=tcp:127.0.0.1:%v-:22", inst.Port),
   523  	)
   524  	if inst.image == "9p" {
   525  		args = append(args,
   526  			"-fsdev", "local,id=fsdev0,path=/,security_model=none,readonly",
   527  			"-device", "virtio-9p-pci,fsdev=fsdev0,mount_tag=/dev/root",
   528  		)
   529  	} else if inst.image != "" {
   530  		if inst.archConfig.UseNewQemuImageOptions {
   531  			args = append(args,
   532  				"-device", "virtio-blk-device,drive=hd0",
   533  				"-drive", fmt.Sprintf("file=%v,if=none,format=raw,id=hd0", inst.image),
   534  			)
   535  		} else {
   536  			// inst.cfg.ImageDevice can contain spaces
   537  			imgline := strings.Split(inst.cfg.ImageDevice, " ")
   538  			imgline[0] = "-" + imgline[0]
   539  			if strings.HasSuffix(imgline[len(imgline)-1], "file=") {
   540  				imgline[len(imgline)-1] = imgline[len(imgline)-1] + inst.image
   541  			} else {
   542  				imgline = append(imgline, inst.image)
   543  			}
   544  			args = append(args, imgline...)
   545  		}
   546  		if inst.cfg.Snapshot {
   547  			args = append(args, "-snapshot")
   548  		}
   549  	}
   550  	if inst.cfg.Initrd != "" {
   551  		args = append(args,
   552  			"-initrd", inst.cfg.Initrd,
   553  		)
   554  	}
   555  	if inst.cfg.Kernel != "" {
   556  		cmdline := append([]string{}, inst.archConfig.CmdLine...)
   557  		if inst.image == "9p" {
   558  			cmdline = append(cmdline,
   559  				"root=/dev/root",
   560  				"rootfstype=9p",
   561  				"rootflags=trans=virtio,version=9p2000.L,cache=loose",
   562  				"init="+filepath.Join(inst.workdir, "init.sh"),
   563  			)
   564  		}
   565  		cmdline = append(cmdline, inst.cfg.Cmdline)
   566  		args = append(args,
   567  			"-kernel", inst.cfg.Kernel,
   568  			"-append", strings.Join(cmdline, " "),
   569  		)
   570  	}
   571  	if inst.cfg.EfiCodeDevice != "" {
   572  		args = append(args,
   573  			"-drive", "if=pflash,format=raw,readonly=on,file="+inst.cfg.EfiCodeDevice,
   574  		)
   575  	}
   576  	if inst.cfg.EfiVarsDevice != "" {
   577  		args = append(args,
   578  			"-drive", "if=pflash,format=raw,readonly=on,file="+inst.cfg.EfiVarsDevice,
   579  		)
   580  	}
   581  	if inst.cfg.AppleSmcOsk != "" {
   582  		args = append(args,
   583  			"-device", "isa-applesmc,osk="+inst.cfg.AppleSmcOsk,
   584  		)
   585  	}
   586  	if inst.snapshot != nil {
   587  		snapshotArgs, err := inst.snapshotEnable()
   588  		if err != nil {
   589  			return nil, err
   590  		}
   591  		args = append(args, snapshotArgs...)
   592  	}
   593  	return args, nil
   594  }
   595  
   596  // "vfio-pci,host=BN:DN.{{FN%8}},addr=0x11".
   597  func handleVfioPciArg(arg string, index int) string {
   598  	if !strings.Contains(arg, "{{FN%8}}") {
   599  		return arg
   600  	}
   601  	if index > 7 {
   602  		re := regexp.MustCompile(`vfio-pci,host=[a-bA-B0-9]+(:[a-bA-B0-9]{1,8}).{{FN%8}},[^:.,]+$`)
   603  		matches := re.FindAllStringSubmatch(arg, -1)
   604  		if len(matches[0]) != 2 {
   605  			return arg
   606  		}
   607  		submatch := matches[0][1]
   608  		dnSubmatch, _ := strconv.ParseInt(submatch[1:], 16, 64)
   609  		devno := dnSubmatch + int64(index/8)
   610  		arg = strings.ReplaceAll(arg, submatch, fmt.Sprintf(":%02x", devno))
   611  	}
   612  	arg = strings.ReplaceAll(arg, "{{FN%8}}", fmt.Sprint(index%8))
   613  	return arg
   614  }
   615  
   616  func splitArgs(str, templateDir string, index int) (args []string) {
   617  	for _, arg := range strings.Split(str, " ") {
   618  		if arg == "" {
   619  			continue
   620  		}
   621  		arg = strings.ReplaceAll(arg, "{{INDEX}}", fmt.Sprint(index))
   622  		arg = strings.ReplaceAll(arg, "{{TEMPLATE}}", templateDir)
   623  		arg = handleVfioPciArg(arg, index)
   624  		const tcpPort = "{{TCP_PORT}}"
   625  		if strings.Contains(arg, tcpPort) {
   626  			arg = strings.ReplaceAll(arg, tcpPort, fmt.Sprint(vmimpl.UnusedTCPPort()))
   627  		}
   628  		args = append(args, arg)
   629  	}
   630  	return
   631  }
   632  
   633  func (inst *instance) Forward(port int) (string, error) {
   634  	if port == 0 {
   635  		return "", fmt.Errorf("vm/qemu: forward port is zero")
   636  	}
   637  	if !inst.target.HostFuzzer {
   638  		if inst.forwardPort != 0 {
   639  			return "", fmt.Errorf("vm/qemu: forward port already set")
   640  		}
   641  		inst.forwardPort = port
   642  	}
   643  	return fmt.Sprintf("localhost:%v", port), nil
   644  }
   645  
   646  func (inst *instance) targetDir() string {
   647  	if inst.image == "9p" {
   648  		return "/tmp"
   649  	}
   650  	if inst.archConfig.TargetDir == "" {
   651  		return "/"
   652  	}
   653  	return inst.archConfig.TargetDir
   654  }
   655  
   656  func (inst *instance) Copy(hostSrc string) (string, error) {
   657  	base := filepath.Base(hostSrc)
   658  	vmDst := filepath.Join(inst.targetDir(), base)
   659  	if inst.target.HostFuzzer {
   660  		if base == "syz-execprog" {
   661  			return hostSrc, nil // we will run these on host
   662  		}
   663  		if inst.files == nil {
   664  			inst.files = make(map[string]string)
   665  		}
   666  		inst.files[vmDst] = hostSrc
   667  	}
   668  
   669  	args := append(vmimpl.SCPArgs(inst.debug, inst.Key, inst.Port, false),
   670  		hostSrc, inst.User+"@localhost:"+vmDst)
   671  	if inst.debug {
   672  		log.Logf(0, "running command: scp %#v", args)
   673  	}
   674  	_, err := osutil.RunCmd(10*time.Minute*inst.timeouts.Scale, "", "scp", args...)
   675  	if err != nil {
   676  		return "", err
   677  	}
   678  	return vmDst, nil
   679  }
   680  
   681  func (inst *instance) Run(ctx context.Context, command string) (
   682  	<-chan []byte, <-chan error, error) {
   683  	rpipe, wpipe, err := osutil.LongPipe()
   684  	if err != nil {
   685  		return nil, nil, err
   686  	}
   687  	inst.merger.Add("ssh", rpipe)
   688  
   689  	sshArgs := vmimpl.SSHArgsForward(inst.debug, inst.Key, inst.Port, inst.forwardPort, false)
   690  	args := strings.Split(command, " ")
   691  	if bin := filepath.Base(args[0]); inst.target.HostFuzzer && bin == "syz-execprog" {
   692  		// Weird mode for Fuchsia.
   693  		// Fuzzer and execprog are on host (we did not copy them), so we will run them as is,
   694  		// but we will also wrap executor with ssh invocation.
   695  		for i, arg := range args {
   696  			if strings.HasPrefix(arg, "-executor=") {
   697  				args[i] = "-executor=" + "/usr/bin/ssh " + strings.Join(sshArgs, " ") +
   698  					" " + inst.User + "@localhost " + arg[len("-executor="):]
   699  			}
   700  			if host := inst.files[arg]; host != "" {
   701  				args[i] = host
   702  			}
   703  		}
   704  	} else {
   705  		args = []string{"ssh"}
   706  		args = append(args, sshArgs...)
   707  		args = append(args, inst.User+"@localhost", "cd "+inst.targetDir()+" && "+command)
   708  	}
   709  	if inst.debug {
   710  		log.Logf(0, "running command: %#v", args)
   711  	}
   712  	cmd := osutil.Command(args[0], args[1:]...)
   713  	cmd.Dir = inst.workdir
   714  	cmd.Stdout = wpipe
   715  	cmd.Stderr = wpipe
   716  	if err := cmd.Start(); err != nil {
   717  		wpipe.Close()
   718  		return nil, nil, err
   719  	}
   720  	wpipe.Close()
   721  	return vmimpl.Multiplex(ctx, cmd, inst.merger, vmimpl.MultiplexConfig{
   722  		Debug: inst.debug,
   723  		Scale: inst.timeouts.Scale,
   724  	})
   725  }
   726  
   727  func (inst *instance) Info() ([]byte, error) {
   728  	info := fmt.Sprintf("%v\n%v %q\n", inst.version, inst.cfg.Qemu, inst.args)
   729  	return []byte(info), nil
   730  }
   731  
   732  func (inst *instance) Diagnose(rep *report.Report) ([]byte, bool) {
   733  	if inst.target.OS == targets.Linux {
   734  		if output, wait, handled := vmimpl.DiagnoseLinux(rep, inst.ssh); handled {
   735  			return output, wait
   736  		}
   737  	}
   738  
   739  	if !needsRegisterInfo(rep) {
   740  		return nil, false
   741  	}
   742  
   743  	ret := []byte(fmt.Sprintf("%s Registers:\n", time.Now().Format("15:04:05 ")))
   744  	for cpu := 0; cpu < inst.cfg.CPU; cpu++ {
   745  		regs, err := inst.hmp("info registers", cpu)
   746  		if err == nil {
   747  			ret = append(ret, []byte(fmt.Sprintf("info registers vcpu %v\n", cpu))...)
   748  			ret = append(ret, []byte(regs)...)
   749  		} else {
   750  			log.Logf(0, "VM-%v failed reading regs: %v", inst.index, err)
   751  			ret = append(ret, []byte(fmt.Sprintf("Failed reading regs: %v\n", err))...)
   752  		}
   753  	}
   754  	return ret, false
   755  }
   756  
   757  func needsRegisterInfo(rep *report.Report) bool {
   758  	// Do not collect register dump for the listed below report types.
   759  	// By default collect as crash (for unknown types too).
   760  	switch rep.Type {
   761  	case crash.Warning,
   762  		crash.AtomicSleep,
   763  		crash.Hang,
   764  		crash.DoS,
   765  		crash.KCSANAssert,
   766  		crash.KCSANDataRace,
   767  		crash.KCSANUnknown,
   768  		crash.KMSANInfoLeak,
   769  		crash.KMSANUninitValue,
   770  		crash.KMSANUnknown,
   771  		crash.KMSANUseAfterFreeRead,
   772  		crash.LockdepBug,
   773  		crash.MemoryLeak,
   774  		crash.RefcountWARNING,
   775  		crash.LostConnection,
   776  		crash.SyzFailure,
   777  		crash.UnexpectedReboot:
   778  		return false
   779  	default:
   780  		return true
   781  	}
   782  }
   783  
   784  func (inst *instance) ssh(args ...string) ([]byte, error) {
   785  	return osutil.RunCmd(time.Minute*inst.timeouts.Scale, "", "ssh", inst.sshArgs(args...)...)
   786  }
   787  
   788  func (inst *instance) sshArgs(args ...string) []string {
   789  	sshArgs := append(vmimpl.SSHArgs(inst.debug, inst.User, inst.Port, false), inst.User+"@localhost")
   790  	return append(sshArgs, args...)
   791  }
   792  
   793  // nolint: lll
   794  const initScript = `#! /bin/bash
   795  set -eux
   796  mount -t proc none /proc
   797  mount -t sysfs none /sys
   798  mount -t debugfs nodev /sys/kernel/debug/
   799  mount -t tmpfs none /tmp
   800  mount -t tmpfs none /var
   801  mount -t tmpfs none /run
   802  mount -t tmpfs none /etc
   803  mount -t tmpfs none /root
   804  touch /etc/fstab
   805  mkdir /etc/network
   806  mkdir /run/network
   807  printf 'auto lo\niface lo inet loopback\n\n' >> /etc/network/interfaces
   808  printf 'auto eth0\niface eth0 inet static\naddress 10.0.2.15\nnetmask 255.255.255.0\nnetwork 10.0.2.0\ngateway 10.0.2.1\nbroadcast 10.0.2.255\n\n' >> /etc/network/interfaces
   809  printf 'auto eth0\niface eth0 inet6 static\naddress fe80::5054:ff:fe12:3456/64\ngateway 2000:da8:203:612:0:3:0:1\n\n' >> /etc/network/interfaces
   810  mkdir -p /etc/network/if-pre-up.d
   811  mkdir -p /etc/network/if-up.d
   812  ifup lo
   813  ifup eth0 || true
   814  echo "root::0:0:root:/root:/bin/bash" > /etc/passwd
   815  mkdir -p /etc/ssh
   816  cp {{KEY}}.pub /root/
   817  chmod 0700 /root
   818  chmod 0600 /root/key.pub
   819  mkdir -p /var/run/sshd/
   820  chmod 700 /var/run/sshd
   821  groupadd -g 33 sshd
   822  useradd -u 33 -g 33 -c sshd -d / sshd
   823  cat > /etc/ssh/sshd_config <<EOF
   824            Port 22
   825            Protocol 2
   826            UsePrivilegeSeparation no
   827            HostKey {{KEY}}
   828            PermitRootLogin yes
   829            AuthenticationMethods publickey
   830            ChallengeResponseAuthentication no
   831            AuthorizedKeysFile /root/key.pub
   832            IgnoreUserKnownHosts yes
   833            AllowUsers root
   834            LogLevel INFO
   835            TCPKeepAlive yes
   836            RSAAuthentication yes
   837            PubkeyAuthentication yes
   838  EOF
   839  /usr/sbin/sshd -e -D
   840  /sbin/halt -f
   841  `