github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/pkg/instance/instance.go (about) 1 // Copyright 2018 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 instance provides helper functions for creation of temporal instances 5 // used for testing of images, patches and bisection. 6 package instance 7 8 import ( 9 "encoding/json" 10 "errors" 11 "fmt" 12 "net" 13 "os" 14 "path/filepath" 15 "runtime" 16 "strings" 17 "time" 18 19 "github.com/google/syzkaller/pkg/build" 20 "github.com/google/syzkaller/pkg/csource" 21 "github.com/google/syzkaller/pkg/log" 22 "github.com/google/syzkaller/pkg/mgrconfig" 23 "github.com/google/syzkaller/pkg/osutil" 24 "github.com/google/syzkaller/pkg/report" 25 "github.com/google/syzkaller/pkg/report/crash" 26 "github.com/google/syzkaller/pkg/tool" 27 "github.com/google/syzkaller/pkg/vcs" 28 "github.com/google/syzkaller/sys/targets" 29 "github.com/google/syzkaller/vm" 30 ) 31 32 type Env interface { 33 BuildSyzkaller(string, string) (string, error) 34 BuildKernel(*BuildKernelConfig) (string, build.ImageDetails, error) 35 Test(numVMs int, reproSyz, reproOpts, reproC []byte) ([]EnvTestResult, error) 36 } 37 38 type env struct { 39 cfg *mgrconfig.Config 40 optionalFlags bool 41 buildSem *Semaphore 42 testSem *Semaphore 43 } 44 45 type BuildKernelConfig struct { 46 CompilerBin string 47 LinkerBin string 48 CcacheBin string 49 UserspaceDir string 50 CmdlineFile string 51 SysctlFile string 52 KernelConfig []byte 53 } 54 55 func NewEnv(cfg *mgrconfig.Config, buildSem, testSem *Semaphore) (Env, error) { 56 if !vm.AllowsOvercommit(cfg.Type) { 57 return nil, fmt.Errorf("test instances are not supported for %v VMs", cfg.Type) 58 } 59 if cfg.Workdir == "" { 60 return nil, fmt.Errorf("workdir path is empty") 61 } 62 if cfg.KernelSrc == "" { 63 return nil, fmt.Errorf("kernel src path is empty") 64 } 65 if cfg.Syzkaller == "" { 66 return nil, fmt.Errorf("syzkaller path is empty") 67 } 68 if err := osutil.MkdirAll(cfg.Workdir); err != nil { 69 return nil, fmt.Errorf("failed to create tmp dir: %w", err) 70 } 71 env := &env{ 72 cfg: cfg, 73 optionalFlags: true, 74 buildSem: buildSem, 75 testSem: testSem, 76 } 77 return env, nil 78 } 79 80 func (env *env) BuildSyzkaller(repoURL, commit string) (string, error) { 81 if env.buildSem != nil { 82 env.buildSem.Wait() 83 defer env.buildSem.Signal() 84 } 85 cfg := env.cfg 86 srcIndex := strings.LastIndex(cfg.Syzkaller, "/src/") 87 if srcIndex == -1 { 88 return "", fmt.Errorf("syzkaller path %q is not in GOPATH", cfg.Syzkaller) 89 } 90 repo := vcs.NewSyzkallerRepo(cfg.Syzkaller) 91 if _, err := repo.CheckoutCommit(repoURL, commit); err != nil { 92 return "", fmt.Errorf("failed to checkout syzkaller repo: %w", err) 93 } 94 // The following commit ("syz-fuzzer: support optional flags") adds support for optional flags 95 // in syz-fuzzer and syz-execprog. This is required to invoke older binaries with newer flags 96 // without failing due to unknown flags. 97 optionalFlags, err := repo.Contains("64435345f0891706a7e0c7885f5f7487581e6005") 98 if err != nil { 99 return "", fmt.Errorf("optional flags check failed: %w", err) 100 } 101 env.optionalFlags = optionalFlags 102 cmd := osutil.Command(MakeBin, "target") 103 cmd.Dir = cfg.Syzkaller 104 goEnvOptions := []string{ 105 "GOPATH=" + cfg.Syzkaller[:srcIndex], 106 "GO111MODULE=auto", 107 } 108 cmd.Env = append(append([]string{}, os.Environ()...), goEnvOptions...) 109 cmd.Env = append(cmd.Env, 110 "TARGETOS="+cfg.TargetOS, 111 "TARGETVMARCH="+cfg.TargetVMArch, 112 "TARGETARCH="+cfg.TargetArch, 113 // Since we can be building very old revisions for bisection here, 114 // make the build as permissive as possible. 115 // Newer compilers tend to produce more warnings also kernel headers may be broken, e.g.: 116 // ebtables.h:197:19: error: invalid conversion from ‘void*’ to ‘ebt_entry_target*’ 117 "CFLAGS=-fpermissive -w", 118 ) 119 120 // We collect the potentially useful debug info here unconditionally, because we will 121 // only figure out later whether we actually need it (e.g. if the patch testing fails). 122 goEnvCmd := osutil.Command("go", "env") 123 goEnvCmd.Dir = cfg.Syzkaller 124 goEnvCmd.Env = append(append([]string{}, os.Environ()...), goEnvOptions...) 125 goEnvOut, goEnvErr := osutil.Run(time.Hour, goEnvCmd) 126 gitStatusOut, gitStatusErr := osutil.RunCmd(time.Hour, cfg.Syzkaller, "git", "status") 127 // Compile syzkaller. 128 buildOutput, buildErr := osutil.Run(time.Hour, cmd) 129 buildLog := fmt.Sprintf("go env (err=%v)\n%s\ngit status (err=%v)\n%s\n\n%s", 130 goEnvErr, goEnvOut, gitStatusErr, gitStatusOut, buildOutput) 131 if buildErr != nil { 132 return buildLog, fmt.Errorf("syzkaller build failed: %w\n%s", buildErr, buildLog) 133 } 134 return buildLog, nil 135 } 136 137 func (env *env) BuildKernel(buildCfg *BuildKernelConfig) ( 138 string, build.ImageDetails, error) { 139 if env.buildSem != nil { 140 env.buildSem.Wait() 141 defer env.buildSem.Signal() 142 } 143 imageDir := filepath.Join(env.cfg.Workdir, "image") 144 params := build.Params{ 145 TargetOS: env.cfg.TargetOS, 146 TargetArch: env.cfg.TargetVMArch, 147 VMType: env.cfg.Type, 148 KernelDir: env.cfg.KernelSrc, 149 OutputDir: imageDir, 150 Compiler: buildCfg.CompilerBin, 151 Linker: buildCfg.LinkerBin, 152 Ccache: buildCfg.CcacheBin, 153 UserspaceDir: buildCfg.UserspaceDir, 154 CmdlineFile: buildCfg.CmdlineFile, 155 SysctlFile: buildCfg.SysctlFile, 156 Config: buildCfg.KernelConfig, 157 } 158 details, err := build.Image(params) 159 if err != nil { 160 return "", details, err 161 } 162 if err := SetConfigImage(env.cfg, imageDir, true); err != nil { 163 return "", details, err 164 } 165 kernelConfigFile := filepath.Join(imageDir, "kernel.config") 166 if !osutil.IsExist(kernelConfigFile) { 167 kernelConfigFile = "" 168 } 169 return kernelConfigFile, details, nil 170 } 171 172 func SetConfigImage(cfg *mgrconfig.Config, imageDir string, reliable bool) error { 173 cfg.KernelObj = filepath.Join(imageDir, "obj") 174 cfg.Image = filepath.Join(imageDir, "image") 175 if keyFile := filepath.Join(imageDir, "key"); osutil.IsExist(keyFile) { 176 cfg.SSHKey = keyFile 177 } 178 vmConfig := make(map[string]interface{}) 179 if err := json.Unmarshal(cfg.VM, &vmConfig); err != nil { 180 return fmt.Errorf("failed to parse VM config: %w", err) 181 } 182 if cfg.Type == "qemu" || cfg.Type == "vmm" { 183 if kernel := filepath.Join(imageDir, "kernel"); osutil.IsExist(kernel) { 184 vmConfig["kernel"] = kernel 185 } 186 if initrd := filepath.Join(imageDir, "initrd"); osutil.IsExist(initrd) { 187 vmConfig["initrd"] = initrd 188 } 189 } 190 if cfg.Type == "gce" { 191 // Don't use preemptible VMs for image testing, patch testing and bisection. 192 vmConfig["preemptible"] = !reliable 193 } 194 vmCfg, err := json.Marshal(vmConfig) 195 if err != nil { 196 return fmt.Errorf("failed to serialize VM config: %w", err) 197 } 198 cfg.VM = vmCfg 199 return nil 200 } 201 202 func OverrideVMCount(cfg *mgrconfig.Config, n int) error { 203 vmConfig := make(map[string]interface{}) 204 if err := json.Unmarshal(cfg.VM, &vmConfig); err != nil { 205 return fmt.Errorf("failed to parse VM config: %w", err) 206 } 207 if vmConfig["count"] == nil || !vm.AllowsOvercommit(cfg.Type) { 208 return nil 209 } 210 vmConfig["count"] = n 211 vmCfg, err := json.Marshal(vmConfig) 212 if err != nil { 213 return fmt.Errorf("failed to serialize VM config: %w", err) 214 } 215 cfg.VM = vmCfg 216 return nil 217 } 218 219 type TestError struct { 220 Boot bool // says if the error happened during booting or during instance testing 221 Infra bool // whether the problem is related to some infrastructure problems 222 Title string 223 Output []byte 224 Report *report.Report 225 } 226 227 func (err *TestError) Error() string { 228 return err.Title 229 } 230 231 type CrashError struct { 232 Report *report.Report 233 } 234 235 func (err *CrashError) Error() string { 236 return err.Report.Title 237 } 238 239 // Test boots numVMs VMs, tests basic kernel operation, and optionally tests the provided reproducer. 240 // TestError is returned if there is a problem with kernel/image (crash, reboot loop, etc). 241 // CrashError is returned if the reproducer crashes kernel. 242 func (env *env) Test(numVMs int, reproSyz, reproOpts, reproC []byte) ([]EnvTestResult, error) { 243 if env.testSem != nil { 244 env.testSem.Wait() 245 defer env.testSem.Signal() 246 } 247 if err := mgrconfig.Complete(env.cfg); err != nil { 248 return nil, err 249 } 250 reporter, err := report.NewReporter(env.cfg) 251 if err != nil { 252 return nil, err 253 } 254 vmPool, err := vm.Create(env.cfg, false) 255 if err != nil { 256 return nil, fmt.Errorf("failed to create VM pool: %w", err) 257 } 258 if n := vmPool.Count(); numVMs > n { 259 numVMs = n 260 } 261 res := make(chan EnvTestResult, numVMs) 262 for i := 0; i < numVMs; i++ { 263 inst := &inst{ 264 cfg: env.cfg, 265 optionalFlags: env.optionalFlags, 266 reporter: reporter, 267 vmPool: vmPool, 268 vmIndex: i, 269 reproSyz: reproSyz, 270 reproOpts: reproOpts, 271 reproC: reproC, 272 } 273 go func() { res <- inst.test() }() 274 } 275 var ret []EnvTestResult 276 for i := 0; i < numVMs; i++ { 277 ret = append(ret, <-res) 278 } 279 return ret, nil 280 } 281 282 type inst struct { 283 cfg *mgrconfig.Config 284 optionalFlags bool 285 reporter *report.Reporter 286 vmPool *vm.Pool 287 vm *vm.Instance 288 vmIndex int 289 reproSyz []byte 290 reproOpts []byte 291 reproC []byte 292 } 293 294 type EnvTestResult struct { 295 Error error 296 RawOutput []byte 297 } 298 299 func (inst *inst) test() EnvTestResult { 300 vmInst, err := inst.vmPool.Create(inst.vmIndex) 301 if err != nil { 302 testErr := &TestError{ 303 Boot: true, 304 Title: err.Error(), 305 } 306 ret := EnvTestResult{ 307 Error: testErr, 308 } 309 var bootErr vm.BootErrorer 310 if errors.As(err, &bootErr) { 311 testErr.Title, testErr.Output = bootErr.BootError() 312 ret.RawOutput = testErr.Output 313 rep := inst.reporter.Parse(testErr.Output) 314 if rep != nil && rep.Type == crash.UnexpectedReboot { 315 // Avoid detecting any boot crash as "unexpected kernel reboot". 316 rep = inst.reporter.ParseFrom(testErr.Output, rep.SkipPos) 317 } 318 if rep == nil { 319 rep = &report.Report{ 320 Title: testErr.Title, 321 Output: testErr.Output, 322 } 323 } 324 if err := inst.reporter.Symbolize(rep); err != nil { 325 // TODO(dvyukov): send such errors to dashboard. 326 log.Logf(0, "failed to symbolize report: %v", err) 327 } 328 testErr.Report = rep 329 testErr.Title = rep.Title 330 } else { 331 testErr.Infra = true 332 var infraErr vm.InfraErrorer 333 if errors.As(err, &infraErr) { 334 // In case there's more info available. 335 testErr.Title, testErr.Output = infraErr.InfraError() 336 } 337 } 338 return ret 339 } 340 defer vmInst.Close() 341 inst.vm = vmInst 342 ret := EnvTestResult{} 343 if ret.Error = inst.testInstance(); ret.Error != nil { 344 return ret 345 } 346 if len(inst.reproSyz) != 0 || len(inst.reproC) != 0 { 347 ret.RawOutput, ret.Error = inst.testRepro() 348 } 349 return ret 350 } 351 352 // testInstance tests basic operation of the provided VM 353 // (that we can copy binaries, run binaries, they can connect to host, run syzkaller programs, etc). 354 // TestError is returned if there is a problem with the kernel (e.g. crash). 355 func (inst *inst) testInstance() error { 356 ln, err := net.Listen("tcp", ":") 357 if err != nil { 358 return fmt.Errorf("failed to open listening socket: %w", err) 359 } 360 defer ln.Close() 361 acceptErr := make(chan error, 1) 362 go func() { 363 conn, err := ln.Accept() 364 if err == nil { 365 conn.Close() 366 } 367 acceptErr <- err 368 }() 369 fwdAddr, err := inst.vm.Forward(ln.Addr().(*net.TCPAddr).Port) 370 if err != nil { 371 return fmt.Errorf("failed to setup port forwarding: %w", err) 372 } 373 374 fuzzerBin, err := inst.vm.Copy(inst.cfg.FuzzerBin) 375 if err != nil { 376 return &TestError{Title: fmt.Sprintf("failed to copy test binary to VM: %v", err)} 377 } 378 379 // If ExecutorBin is provided, it means that syz-executor is already in the image, 380 // so no need to copy it. 381 executorBin := inst.cfg.SysTarget.ExecutorBin 382 if executorBin == "" { 383 executorBin, err = inst.vm.Copy(inst.cfg.ExecutorBin) 384 if err != nil { 385 return &TestError{Title: fmt.Sprintf("failed to copy test binary to VM: %v", err)} 386 } 387 } 388 389 cmd := OldFuzzerCmd(fuzzerBin, executorBin, targets.TestOS, inst.cfg.TargetOS, inst.cfg.TargetArch, fwdAddr, 390 inst.cfg.Sandbox, inst.cfg.SandboxArg, 0, inst.cfg.Cover, true, inst.optionalFlags, inst.cfg.Timeouts.Slowdown) 391 timeout := 10 * time.Minute * inst.cfg.Timeouts.Scale 392 _, rep, err := inst.vm.Run(timeout, inst.reporter, cmd) 393 if err != nil { 394 return fmt.Errorf("failed to run binary in VM: %w", err) 395 } 396 if rep != nil { 397 if err := inst.reporter.Symbolize(rep); err != nil { 398 // TODO(dvyukov): send such errors to dashboard. 399 log.Logf(0, "failed to symbolize report: %v", err) 400 } 401 return &TestError{ 402 Title: rep.Title, 403 Report: rep, 404 } 405 } 406 select { 407 case err := <-acceptErr: 408 return err 409 case <-time.After(10 * time.Second): 410 return fmt.Errorf("test machine failed to connect to host") 411 } 412 } 413 414 func (inst *inst) testRepro() ([]byte, error) { 415 var err error 416 execProg, err := SetupExecProg(inst.vm, inst.cfg, inst.reporter, &OptionalConfig{ 417 OldFlagsCompatMode: !inst.optionalFlags, 418 }) 419 if err != nil { 420 return nil, err 421 } 422 transformError := func(res *RunResult, err error) ([]byte, error) { 423 if err != nil { 424 return nil, err 425 } 426 if res != nil && res.Report != nil { 427 return res.Output, &CrashError{Report: res.Report} 428 } 429 return res.Output, nil 430 } 431 out := []byte{} 432 if len(inst.reproSyz) > 0 { 433 var opts csource.Options 434 opts, err = csource.DeserializeOptions(inst.reproOpts) 435 if err != nil { 436 return nil, err 437 } 438 // Combine repro options and default options in a way that increases chances to reproduce the crash. 439 // First, we always enable threaded/collide as it should be [almost] strictly better. 440 // Executor does not support empty sandbox, so we use none instead. 441 // Finally, always use repeat and multiple procs. 442 if opts.Sandbox == "" { 443 opts.Sandbox = "none" 444 } 445 opts.Repeat, opts.Threaded = true, true 446 out, err = transformError(execProg.RunSyzProg(inst.reproSyz, 447 inst.cfg.Timeouts.NoOutputRunningTime, opts)) 448 } 449 if err == nil && len(inst.reproC) > 0 { 450 // We should test for more than full "no output" timeout, but the problem is that C reproducers 451 // don't print anything, so we will get a false "no output" crash. 452 out, err = transformError(execProg.RunCProgRaw(inst.reproC, inst.cfg.Target, 453 inst.cfg.Timeouts.NoOutput/2)) 454 } 455 return out, err 456 } 457 458 type OptionalFuzzerArgs struct { 459 Slowdown int 460 SandboxArg int 461 PprofPort int 462 } 463 464 type FuzzerCmdArgs struct { 465 Fuzzer string 466 Executor string 467 Name string 468 OS string 469 Arch string 470 FwdAddr string 471 Sandbox string 472 Procs int 473 Verbosity int 474 Cover bool 475 Debug bool 476 Test bool 477 Optional *OptionalFuzzerArgs 478 } 479 480 func FuzzerCmd(args *FuzzerCmdArgs) string { 481 osArg := "" 482 if targets.Get(args.OS, args.Arch).HostFuzzer { 483 // Only these OSes need the flag, because the rest assume host OS. 484 // But speciying OS for all OSes breaks patch testing on syzbot 485 // because old execprog does not have os flag. 486 osArg = " -os=" + args.OS 487 } 488 verbosityArg := "" 489 if args.Verbosity != 0 { 490 verbosityArg = fmt.Sprintf(" -vv=%v", args.Verbosity) 491 } 492 optionalArg := "" 493 if args.Optional != nil { 494 flags := []tool.Flag{ 495 {Name: "slowdown", Value: fmt.Sprint(args.Optional.Slowdown)}, 496 {Name: "sandbox_arg", Value: fmt.Sprint(args.Optional.SandboxArg)}, 497 {Name: "pprof_port", Value: fmt.Sprint(args.Optional.PprofPort)}, 498 } 499 optionalArg = " " + tool.OptionalFlags(flags) 500 } 501 return fmt.Sprintf("%v -executor=%v -name=%v -arch=%v%v -manager=%v -sandbox=%v"+ 502 " -procs=%v -cover=%v -debug=%v -test=%v%v%v", 503 args.Fuzzer, args.Executor, args.Name, args.Arch, osArg, args.FwdAddr, args.Sandbox, 504 args.Procs, args.Cover, args.Debug, args.Test, verbosityArg, optionalArg) 505 } 506 507 func OldFuzzerCmd(fuzzer, executor, name, OS, arch, fwdAddr, sandbox string, sandboxArg, procs int, 508 cover, test, optionalFlags bool, slowdown int) string { 509 var optional *OptionalFuzzerArgs 510 if optionalFlags { 511 optional = &OptionalFuzzerArgs{Slowdown: slowdown, SandboxArg: sandboxArg} 512 } 513 return FuzzerCmd(&FuzzerCmdArgs{Fuzzer: fuzzer, Executor: executor, Name: name, 514 OS: OS, Arch: arch, FwdAddr: fwdAddr, Sandbox: sandbox, 515 Procs: procs, Verbosity: 0, Cover: cover, Debug: false, Test: test, 516 Optional: optional}) 517 } 518 519 func ExecprogCmd(execprog, executor, OS, arch, sandbox string, sandboxArg int, repeat, threaded, collide bool, 520 procs, faultCall, faultNth int, optionalFlags bool, slowdown int, progFile string) string { 521 repeatCount := 1 522 if repeat { 523 repeatCount = 0 524 } 525 osArg := "" 526 if targets.Get(OS, arch).HostFuzzer { 527 osArg = " -os=" + OS 528 } 529 optionalArg := "" 530 531 if faultCall >= 0 { 532 optionalArg = fmt.Sprintf(" -fault_call=%v -fault_nth=%v", 533 faultCall, faultNth) 534 } 535 536 if optionalFlags { 537 optionalArg += " " + tool.OptionalFlags([]tool.Flag{ 538 {Name: "slowdown", Value: fmt.Sprint(slowdown)}, 539 {Name: "sandboxArg", Value: fmt.Sprint(sandboxArg)}, 540 }) 541 } 542 543 return fmt.Sprintf("%v -executor=%v -arch=%v%v -sandbox=%v"+ 544 " -procs=%v -repeat=%v -threaded=%v -collide=%v -cover=0%v %v", 545 execprog, executor, arch, osArg, sandbox, 546 procs, repeatCount, threaded, collide, 547 optionalArg, progFile) 548 } 549 550 var MakeBin = func() string { 551 if runtime.GOOS == targets.FreeBSD || runtime.GOOS == targets.OpenBSD { 552 return "gmake" 553 } 554 return "make" 555 }() 556 557 func RunnerCmd(prog, fwdAddr, os, arch string, poolIdx, vmIdx int, threaded, newEnv bool) string { 558 return fmt.Sprintf("%s -addr=%s -os=%s -arch=%s -pool=%d -vm=%d "+ 559 "-threaded=%t -new-env=%t", prog, fwdAddr, os, arch, poolIdx, vmIdx, threaded, newEnv) 560 } 561 562 type Semaphore struct { 563 ch chan struct{} 564 } 565 566 func NewSemaphore(count int) *Semaphore { 567 s := &Semaphore{ 568 ch: make(chan struct{}, count), 569 } 570 for i := 0; i < count; i++ { 571 s.Signal() 572 } 573 return s 574 } 575 576 func (s *Semaphore) Wait() { 577 <-s.ch 578 } 579 580 func (s *Semaphore) WaitC() <-chan struct{} { 581 return s.ch 582 } 583 584 func (s *Semaphore) Available() int { 585 return len(s.ch) 586 } 587 588 func (s *Semaphore) Signal() { 589 if av := s.Available(); av == cap(s.ch) { 590 // Not super reliable, but let it be here just in case. 591 panic(fmt.Sprintf("semaphore capacity (%d) is exceeded (%d)", cap(s.ch), av)) 592 } 593 s.ch <- struct{}{} 594 }