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