github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/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 "context" 10 "encoding/json" 11 "errors" 12 "fmt" 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/config" 21 "github.com/google/syzkaller/pkg/csource" 22 "github.com/google/syzkaller/pkg/log" 23 "github.com/google/syzkaller/pkg/mgrconfig" 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/pkg/tool" 28 "github.com/google/syzkaller/pkg/vcs" 29 "github.com/google/syzkaller/sys/targets" 30 "github.com/google/syzkaller/vm" 31 ) 32 33 type Env interface { 34 BuildSyzkaller(string, string) (string, error) 35 CleanKernel(*BuildKernelConfig) error 36 BuildKernel(*BuildKernelConfig) (string, build.ImageDetails, error) 37 Test(numVMs int, reproSyz, reproOpts, reproC []byte) ([]EnvTestResult, error) 38 } 39 40 type env struct { 41 cfg *mgrconfig.Config 42 optionalFlags bool 43 buildSem *osutil.Semaphore 44 testSem *osutil.Semaphore 45 } 46 47 type BuildKernelConfig struct { 48 MakeBin string 49 CompilerBin string 50 LinkerBin string 51 CcacheBin string 52 UserspaceDir string 53 CmdlineFile string 54 SysctlFile string 55 KernelConfig []byte 56 BuildCPUs int 57 } 58 59 func NewEnv(cfg *mgrconfig.Config, buildSem, testSem *osutil.Semaphore) (Env, error) { 60 if !vm.AllowsOvercommit(cfg.Type) { 61 return nil, fmt.Errorf("test instances are not supported for %v VMs", cfg.Type) 62 } 63 if cfg.Workdir == "" { 64 return nil, fmt.Errorf("workdir path is empty") 65 } 66 if cfg.KernelSrc == "" { 67 return nil, fmt.Errorf("kernel src path is empty") 68 } 69 if cfg.Syzkaller == "" { 70 return nil, fmt.Errorf("syzkaller path is empty") 71 } 72 if err := osutil.MkdirAll(cfg.Workdir); err != nil { 73 return nil, fmt.Errorf("failed to create tmp dir: %w", err) 74 } 75 env := &env{ 76 cfg: cfg, 77 optionalFlags: true, 78 buildSem: buildSem, 79 testSem: testSem, 80 } 81 return env, nil 82 } 83 84 func (env *env) BuildSyzkaller(repoURL, commit string) (string, error) { 85 if env.buildSem != nil { 86 env.buildSem.Wait() 87 defer env.buildSem.Signal() 88 } 89 cfg := env.cfg 90 srcIndex := strings.LastIndex(cfg.Syzkaller, "/src/") 91 if srcIndex == -1 { 92 return "", fmt.Errorf("syzkaller path %q is not in GOPATH", cfg.Syzkaller) 93 } 94 repo := vcs.NewSyzkallerRepo(cfg.Syzkaller) 95 if _, err := repo.CheckoutCommit(repoURL, commit); err != nil { 96 return "", fmt.Errorf("failed to checkout syzkaller repo: %w", err) 97 } 98 // The following commit ("syz-fuzzer: support optional flags") adds support for optional flags 99 // in syz-execprog. This is required to invoke older binaries with newer flags 100 // without failing due to unknown flags. 101 optionalFlags, err := repo.Contains("64435345f0891706a7e0c7885f5f7487581e6005") 102 if err != nil { 103 return "", fmt.Errorf("optional flags check failed: %w", err) 104 } 105 env.optionalFlags = optionalFlags 106 cmd := osutil.Command(MakeBin, "target") 107 cmd.Dir = cfg.Syzkaller 108 goEnvOptions := []string{ 109 "GOPATH=" + cfg.Syzkaller[:srcIndex], 110 "GO111MODULE=auto", 111 } 112 cmd.Env = append(append([]string{}, os.Environ()...), goEnvOptions...) 113 cmd.Env = append(cmd.Env, 114 "TARGETOS="+cfg.TargetOS, 115 "TARGETVMARCH="+cfg.TargetVMArch, 116 "TARGETARCH="+cfg.TargetArch, 117 // Since we can be building very old revisions for bisection here, 118 // make the build as permissive as possible. 119 // Newer compilers tend to produce more warnings also kernel headers may be broken, e.g.: 120 // ebtables.h:197:19: error: invalid conversion from ‘void*’ to ‘ebt_entry_target*’ 121 "CFLAGS=-fpermissive -w", 122 ) 123 124 // We collect the potentially useful debug info here unconditionally, because we will 125 // only figure out later whether we actually need it (e.g. if the patch testing fails). 126 goEnvCmd := osutil.Command("go", "env") 127 goEnvCmd.Dir = cfg.Syzkaller 128 goEnvCmd.Env = append(append([]string{}, os.Environ()...), goEnvOptions...) 129 goEnvOut, goEnvErr := osutil.Run(time.Hour, goEnvCmd) 130 gitStatusOut, gitStatusErr := osutil.RunCmd(time.Hour, cfg.Syzkaller, "git", "status") 131 // Compile syzkaller. 132 buildOutput, buildErr := osutil.Run(time.Hour, cmd) 133 buildLog := fmt.Sprintf("go env (err=%v)\n%s\ngit status (err=%v)\n%s\n\n%s", 134 goEnvErr, goEnvOut, gitStatusErr, gitStatusOut, buildOutput) 135 if buildErr != nil { 136 return buildLog, fmt.Errorf("syzkaller build failed: %w\n%s", buildErr, buildLog) 137 } 138 return buildLog, nil 139 } 140 141 func (env *env) buildParamsFromCfg(buildCfg *BuildKernelConfig) build.Params { 142 return build.Params{ 143 TargetOS: env.cfg.TargetOS, 144 TargetArch: env.cfg.TargetVMArch, 145 VMType: env.cfg.Type, 146 KernelDir: env.cfg.KernelSrc, 147 OutputDir: filepath.Join(env.cfg.Workdir, "image"), 148 Make: buildCfg.MakeBin, 149 Compiler: buildCfg.CompilerBin, 150 Linker: buildCfg.LinkerBin, 151 Ccache: buildCfg.CcacheBin, 152 UserspaceDir: buildCfg.UserspaceDir, 153 CmdlineFile: buildCfg.CmdlineFile, 154 SysctlFile: buildCfg.SysctlFile, 155 Config: buildCfg.KernelConfig, 156 BuildCPUs: buildCfg.BuildCPUs, 157 } 158 } 159 160 func (env *env) BuildKernel(buildCfg *BuildKernelConfig) ( 161 string, build.ImageDetails, error) { 162 if env.buildSem != nil { 163 env.buildSem.Wait() 164 defer env.buildSem.Signal() 165 } 166 params := env.buildParamsFromCfg(buildCfg) 167 details, err := build.Image(params) 168 if err != nil { 169 return "", details, err 170 } 171 if err := SetConfigImage(env.cfg, params.OutputDir, true); err != nil { 172 return "", details, err 173 } 174 kernelConfigFile := filepath.Join(params.OutputDir, "kernel.config") 175 if !osutil.IsExist(kernelConfigFile) { 176 kernelConfigFile = "" 177 } 178 return kernelConfigFile, details, nil 179 } 180 181 func (env *env) CleanKernel(buildCfg *BuildKernelConfig) error { 182 if env.buildSem != nil { 183 env.buildSem.Wait() 184 defer env.buildSem.Signal() 185 } 186 params := env.buildParamsFromCfg(buildCfg) 187 return build.Clean(params) 188 } 189 190 func SetConfigImage(cfg *mgrconfig.Config, imageDir string, reliable bool) error { 191 cfg.KernelObj = filepath.Join(imageDir, "obj") 192 cfg.Image = filepath.Join(imageDir, "image") 193 if keyFile := filepath.Join(imageDir, "key"); osutil.IsExist(keyFile) { 194 cfg.SSHKey = keyFile 195 } 196 vmConfig := make(map[string]interface{}) 197 if err := json.Unmarshal(cfg.VM, &vmConfig); err != nil { 198 return fmt.Errorf("failed to parse VM config: %w", err) 199 } 200 if cfg.Type == "qemu" || cfg.Type == "vmm" { 201 if kernel := filepath.Join(imageDir, "kernel"); osutil.IsExist(kernel) { 202 vmConfig["kernel"] = kernel 203 } 204 if initrd := filepath.Join(imageDir, "initrd"); osutil.IsExist(initrd) { 205 vmConfig["initrd"] = initrd 206 } 207 } 208 if cfg.Type == "gce" { 209 // Don't use preemptible VMs for image testing, patch testing and bisection. 210 vmConfig["preemptible"] = !reliable 211 } 212 vmCfg, err := json.Marshal(vmConfig) 213 if err != nil { 214 return fmt.Errorf("failed to serialize VM config: %w", err) 215 } 216 cfg.VM = vmCfg 217 return nil 218 } 219 220 func OverrideVMCount(cfg *mgrconfig.Config, n int) error { 221 vmConfig := make(map[string]interface{}) 222 if err := json.Unmarshal(cfg.VM, &vmConfig); err != nil { 223 return fmt.Errorf("failed to parse VM config: %w", err) 224 } 225 if vmConfig["count"] == nil || !vm.AllowsOvercommit(cfg.Type) { 226 return nil 227 } 228 vmConfig["count"] = n 229 vmCfg, err := json.Marshal(vmConfig) 230 if err != nil { 231 return fmt.Errorf("failed to serialize VM config: %w", err) 232 } 233 cfg.VM = vmCfg 234 cfg.FuzzingVMs = min(cfg.FuzzingVMs, n) 235 return nil 236 } 237 238 type TestError struct { 239 Boot bool // says if the error happened during booting or during instance testing 240 Infra bool // whether the problem is related to some infrastructure problems 241 Title string 242 Output []byte 243 Report *report.Report 244 } 245 246 func (err *TestError) Error() string { 247 return err.Title 248 } 249 250 type CrashError struct { 251 Report *report.Report 252 } 253 254 func (err *CrashError) Error() string { 255 return err.Report.Title 256 } 257 258 // Test boots numVMs VMs, tests basic kernel operation, and optionally tests the provided reproducer. 259 // TestError is returned if there is a problem with kernel/image (crash, reboot loop, etc). 260 // CrashError is returned if the reproducer crashes kernel. 261 func (env *env) Test(numVMs int, reproSyz, reproOpts, reproC []byte) ([]EnvTestResult, error) { 262 if env.testSem != nil { 263 env.testSem.Wait() 264 defer env.testSem.Signal() 265 } 266 if err := mgrconfig.Complete(env.cfg); err != nil { 267 return nil, err 268 } 269 reporter, err := report.NewReporter(env.cfg) 270 if err != nil { 271 return nil, err 272 } 273 vmPool, err := vm.Create(env.cfg, false) 274 if err != nil { 275 return nil, fmt.Errorf("failed to create VM pool: %w", err) 276 } 277 defer vmPool.Close() 278 numVMs = min(numVMs, vmPool.Count()) 279 res := make(chan EnvTestResult, numVMs) 280 for i := 0; i < numVMs; i++ { 281 inst := &inst{ 282 cfg: env.cfg, 283 optionalFlags: env.optionalFlags, 284 reporter: reporter, 285 vmPool: vmPool, 286 vmIndex: i, 287 reproSyz: reproSyz, 288 reproOpts: reproOpts, 289 reproC: reproC, 290 } 291 go func() { res <- inst.test() }() 292 } 293 var ret []EnvTestResult 294 for i := 0; i < numVMs; i++ { 295 ret = append(ret, <-res) 296 } 297 return ret, nil 298 } 299 300 type inst struct { 301 cfg *mgrconfig.Config 302 optionalFlags bool 303 reporter *report.Reporter 304 vmPool *vm.Pool 305 vm *vm.Instance 306 vmIndex int 307 reproSyz []byte 308 reproOpts []byte 309 reproC []byte 310 } 311 312 type EnvTestResult struct { 313 Error error 314 RawOutput []byte 315 } 316 317 func (inst *inst) test() EnvTestResult { 318 vmInst, err := inst.vmPool.Create(context.Background(), inst.vmIndex) 319 if err != nil { 320 testErr := &TestError{ 321 Boot: true, 322 Title: err.Error(), 323 } 324 ret := EnvTestResult{ 325 Error: testErr, 326 } 327 var bootErr vm.BootErrorer 328 if errors.As(err, &bootErr) { 329 testErr.Title, testErr.Output = bootErr.BootError() 330 ret.RawOutput = testErr.Output 331 rep := inst.reporter.Parse(testErr.Output) 332 if rep != nil && rep.Type == crash.UnexpectedReboot { 333 // Avoid detecting any boot crash as "unexpected kernel reboot". 334 rep = inst.reporter.ParseFrom(testErr.Output, rep.SkipPos) 335 } 336 if rep == nil { 337 rep = &report.Report{ 338 Title: testErr.Title, 339 Output: testErr.Output, 340 } 341 } 342 if err := inst.reporter.Symbolize(rep); err != nil { 343 // TODO(dvyukov): send such errors to dashboard. 344 log.Logf(0, "failed to symbolize report: %v", err) 345 } 346 testErr.Report = rep 347 testErr.Title = rep.Title 348 } else { 349 testErr.Infra = true 350 var infraErr vm.InfraErrorer 351 if errors.As(err, &infraErr) { 352 // In case there's more info available. 353 testErr.Title, testErr.Output = infraErr.InfraError() 354 } 355 } 356 return ret 357 } 358 defer vmInst.Close() 359 inst.vm = vmInst 360 ret := EnvTestResult{} 361 if ret.Error = inst.testInstance(); ret.Error != nil { 362 return ret 363 } 364 if len(inst.reproSyz) != 0 || len(inst.reproC) != 0 { 365 ret.RawOutput, ret.Error = inst.testRepro() 366 } 367 return ret 368 } 369 370 // testInstance tests that the VM does not crash on a simple program. 371 // TestError is returned if there is a problem with the kernel (e.g. crash). 372 func (inst *inst) testInstance() error { 373 execProg, err := SetupExecProg(inst.vm, inst.cfg, inst.reporter, &OptionalConfig{ 374 OldFlagsCompatMode: !inst.optionalFlags, 375 }) 376 if err != nil { 377 return err 378 } 379 // Note: we create the test program on a newer syzkaller revision and pass it to the old execprog. 380 // We rely on the non-strict program parsing to parse it successfully. 381 testProg := inst.cfg.Target.DataMmapProg().Serialize() 382 // Use the same options as the target reproducer. 383 // E.g. if it does not use wifi, we won't test it, which reduces changes of unrelated kernel bugs. 384 // Note: we keep fault injection if it's enabled in the reproducer to test that fault injection works 385 // (does not produce some kernel oops when activated). 386 opts, err := inst.csourceOptions() 387 if err != nil { 388 return err 389 } 390 opts.Repeat = false 391 out, err := execProg.RunSyzProg(ExecParams{ 392 SyzProg: testProg, 393 Duration: inst.cfg.Timeouts.NoOutputRunningTime, 394 Opts: opts, 395 ExitConditions: vm.ExitNormal, 396 }) 397 if err != nil { 398 return &TestError{Title: err.Error()} 399 } 400 if out.Report != nil { 401 return &TestError{Title: out.Report.Title, Report: out.Report} 402 } 403 return nil 404 } 405 406 func (inst *inst) testRepro() ([]byte, error) { 407 execProg, err := SetupExecProg(inst.vm, inst.cfg, inst.reporter, &OptionalConfig{ 408 OldFlagsCompatMode: !inst.optionalFlags, 409 }) 410 if err != nil { 411 return nil, err 412 } 413 transformError := func(res *RunResult, err error) ([]byte, error) { 414 if err != nil { 415 return nil, err 416 } 417 if res != nil && res.Report != nil { 418 return res.Output, &CrashError{Report: res.Report} 419 } 420 return res.Output, nil 421 } 422 out := []byte{} 423 if len(inst.reproSyz) > 0 { 424 opts, err := inst.csourceOptions() 425 if err != nil { 426 return nil, err 427 } 428 out, err = transformError(execProg.RunSyzProg(ExecParams{ 429 SyzProg: inst.reproSyz, 430 Duration: inst.cfg.Timeouts.NoOutputRunningTime, 431 Opts: opts, 432 })) 433 if err != nil { 434 return out, err 435 } 436 } 437 if len(inst.reproC) > 0 { 438 // We should test for more than full "no output" timeout, but the problem is that C reproducers 439 // don't print anything, so we will get a false "no output" crash. 440 out, err = transformError(execProg.RunCProgRaw(inst.reproC, inst.cfg.Target, 441 inst.cfg.Timeouts.NoOutput/2)) 442 } 443 return out, err 444 } 445 446 func (inst *inst) csourceOptions() (csource.Options, error) { 447 if len(inst.reproSyz) == 0 { 448 // If no syz repro is provided, the functionality is likely being used to test 449 // for the crashes that don't need a reproducer (e.g. kernel build/boot/test errors). 450 // Use the default options, that's the best we can do. 451 return csource.DefaultOpts(inst.cfg), nil 452 } 453 opts, err := csource.DeserializeOptions(inst.reproOpts) 454 if err != nil { 455 return opts, err 456 } 457 // Combine repro options and default options in a way that increases chances to reproduce the crash. 458 // We always enable threaded/collide as it should be [almost] strictly better. 459 opts.Repeat, opts.Threaded = true, true 460 return opts, nil 461 } 462 463 // nolint:revive 464 func ExecprogCmd(execprog, executor, OS, arch, vmType string, opts csource.Options, 465 optionalFlags bool, slowdown int, progFile string) string { 466 repeatCount := 1 467 if opts.Repeat { 468 repeatCount = 0 469 } 470 sandbox := opts.Sandbox 471 if sandbox == "" { 472 // Executor does not support empty sandbox, so we use none instead. 473 sandbox = "none" 474 } 475 osArg := "" 476 if targets.Get(OS, arch).HostFuzzer { 477 osArg = " -os=" + OS 478 } 479 optionalArg := "" 480 if opts.Fault && opts.FaultCall >= 0 { 481 optionalArg = fmt.Sprintf(" -fault_call=%v -fault_nth=%v", 482 opts.FaultCall, opts.FaultNth) 483 } 484 if optionalFlags { 485 optionalArg += " " + tool.OptionalFlags([]tool.Flag{ 486 {Name: "slowdown", Value: fmt.Sprint(slowdown)}, 487 {Name: "sandbox_arg", Value: fmt.Sprint(opts.SandboxArg)}, 488 {Name: "type", Value: fmt.Sprint(vmType)}, 489 }) 490 } 491 return fmt.Sprintf("%v -executor=%v -arch=%v%v -sandbox=%v"+ 492 " -procs=%v -repeat=%v -threaded=%v -collide=%v -cover=0%v %v", 493 execprog, executor, arch, osArg, sandbox, 494 opts.Procs, repeatCount, opts.Threaded, opts.Collide, 495 optionalArg, progFile) 496 } 497 498 var MakeBin = func() string { 499 if runtime.GOOS == targets.FreeBSD || runtime.GOOS == targets.OpenBSD { 500 return "gmake" 501 } 502 return "make" 503 }() 504 505 // nolint:revive 506 func RunnerCmd(prog, fwdAddr, os, arch string, poolIdx, vmIdx int, threaded, newEnv bool) string { 507 return fmt.Sprintf("%s -addr=%s -os=%s -arch=%s -pool=%d -vm=%d "+ 508 "-threaded=%t -new-env=%t", prog, fwdAddr, os, arch, poolIdx, vmIdx, threaded, newEnv) 509 } 510 511 // RunSmokeTest executes syz-manager in the smoke test mode and returns two values: 512 // The crash report, if the testing failed. 513 // An error if there was a problem not related to testing the kernel. 514 func RunSmokeTest(cfg *mgrconfig.Config) (*report.Report, error) { 515 if !vm.AllowsOvercommit(cfg.Type) { 516 return nil, nil // No support for creating machines out of thin air. 517 } 518 osutil.MkdirAll(cfg.Workdir) 519 configFile := filepath.Join(cfg.Workdir, "manager.cfg") 520 if err := config.SaveFile(configFile, cfg); err != nil { 521 return nil, err 522 } 523 timeout := 30 * time.Minute * cfg.Timeouts.Scale 524 bin := filepath.Join(cfg.Syzkaller, "bin", "syz-manager") 525 output, retErr := osutil.RunCmd(timeout, "", bin, "-config", configFile, "-mode=smoke-test") 526 if retErr == nil { 527 return nil, nil 528 } 529 // If there was a kernel bug, report it to dashboard. 530 // Otherwise just save the output in a temp file and log an error, unclear what else we can do. 531 reportData, err := os.ReadFile(filepath.Join(cfg.Workdir, "report.json")) 532 if err != nil { 533 if os.IsNotExist(err) { 534 var verboseErr *osutil.VerboseError 535 if errors.As(retErr, &verboseErr) { 536 // Include more details into the report. 537 prefix := fmt.Sprintf("%s, exit code %d\n\n", verboseErr, verboseErr.ExitCode) 538 output = append([]byte(prefix), output...) 539 } 540 rep := &report.Report{ 541 Title: "SYZFATAL: image testing failed w/o kernel bug", 542 Output: output, 543 } 544 return rep, nil 545 } 546 return nil, err 547 } 548 rep := new(report.Report) 549 if err := json.Unmarshal(reportData, rep); err != nil { 550 return nil, fmt.Errorf("failed to unmarshal smoke test report: %w", err) 551 } 552 return rep, nil 553 }