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 `