github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/repro/repro.go (about) 1 // Copyright 2016 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 repro 5 6 import ( 7 "bytes" 8 "context" 9 "errors" 10 "fmt" 11 "sort" 12 "time" 13 14 "github.com/google/syzkaller/pkg/bisect/minimize" 15 "github.com/google/syzkaller/pkg/csource" 16 "github.com/google/syzkaller/pkg/flatrpc" 17 "github.com/google/syzkaller/pkg/instance" 18 "github.com/google/syzkaller/pkg/log" 19 "github.com/google/syzkaller/pkg/mgrconfig" 20 "github.com/google/syzkaller/pkg/report" 21 "github.com/google/syzkaller/pkg/report/crash" 22 "github.com/google/syzkaller/prog" 23 "github.com/google/syzkaller/sys/targets" 24 "github.com/google/syzkaller/vm" 25 "github.com/google/syzkaller/vm/dispatcher" 26 ) 27 28 type Result struct { 29 Prog *prog.Prog 30 Duration time.Duration 31 Opts csource.Options 32 CRepro bool 33 // Information about the final (non-symbolized) crash that we reproduced. 34 // Can be different from what we started reproducing. 35 Report *report.Report 36 // A very rough estimate of the probability with which the resulting syz 37 // reproducer crashes the kernel. 38 Reliability float64 39 } 40 41 type Stats struct { 42 Log []byte 43 TotalTime time.Duration 44 ExtractProgTime time.Duration 45 MinimizeProgTime time.Duration 46 SimplifyProgTime time.Duration 47 ExtractCTime time.Duration 48 SimplifyCTime time.Duration 49 } 50 51 type reproContext struct { 52 ctx context.Context 53 exec execInterface 54 logf func(string, ...interface{}) 55 target *targets.Target 56 crashTitle string 57 crashType crash.Type 58 crashStart int 59 crashExecutor *report.ExecutorInfo 60 entries []*prog.LogEntry 61 testTimeouts []time.Duration 62 startOpts csource.Options 63 stats *Stats 64 report *report.Report 65 timeouts targets.Timeouts 66 observedTitles map[string]bool 67 fast bool 68 } 69 70 // execInterface describes the interfaces needed by pkg/repro. 71 type execInterface interface { 72 // Run() will either run a C repro or a syz repro depending on params. 73 Run(ctx context.Context, params instance.ExecParams, logf instance.ExecutorLogger) (*instance.RunResult, error) 74 } 75 76 type Environment struct { 77 Config *mgrconfig.Config 78 Features flatrpc.Feature 79 Reporter *report.Reporter 80 Pool *vm.Dispatcher 81 // The Fast repro mode restricts the repro log bisection, 82 // it skips multiple simpifications and C repro generation. 83 Fast bool 84 85 logf func(string, ...interface{}) 86 } 87 88 func Run(ctx context.Context, log []byte, env Environment) (*Result, *Stats, error) { 89 return runInner(ctx, log, env, &poolWrapper{ 90 cfg: env.Config, 91 reporter: env.Reporter, 92 pool: env.Pool, 93 }) 94 } 95 96 var ErrEmptyCrashLog = errors.New("no programs") 97 98 func runInner(ctx context.Context, crashLog []byte, env Environment, exec execInterface) (*Result, *Stats, error) { 99 cfg := env.Config 100 entries := cfg.Target.ParseLog(crashLog, prog.NonStrict) 101 if len(entries) == 0 { 102 return nil, nil, fmt.Errorf("log (%d bytes) parse failed: %w", len(crashLog), ErrEmptyCrashLog) 103 } 104 crashStart := len(crashLog) 105 crashTitle, crashType := "", crash.UnknownType 106 var crashExecutor *report.ExecutorInfo 107 if rep := env.Reporter.Parse(crashLog); rep != nil { 108 crashStart = rep.StartPos 109 crashTitle = rep.Title 110 crashType = rep.Type 111 crashExecutor = rep.Executor 112 } 113 testTimeouts := []time.Duration{ 114 max(30*time.Second, 3*cfg.Timeouts.Program), // to catch simpler crashes (i.e. no races and no hangs) 115 max(100*time.Second, 20*cfg.Timeouts.Program), 116 cfg.Timeouts.NoOutputRunningTime, // to catch "no output", races and hangs 117 } 118 switch { 119 case crashTitle == "": 120 crashTitle = "no output/lost connection" 121 // Lost connection can be detected faster, 122 // but theoretically if it's caused by a race it may need the largest timeout. 123 // No output can only be reproduced with the max timeout. 124 // As a compromise we use the smallest and the largest timeouts. 125 testTimeouts = []time.Duration{testTimeouts[0], testTimeouts[2]} 126 case crashType == crash.MemoryLeak: 127 // Memory leaks can't be detected quickly because of expensive setup and scanning. 128 testTimeouts = testTimeouts[1:] 129 case crashType == crash.Hang: 130 testTimeouts = testTimeouts[2:] 131 } 132 if env.Fast { 133 testTimeouts = []time.Duration{30 * time.Second, 5 * time.Minute} 134 } 135 reproCtx := &reproContext{ 136 ctx: ctx, 137 exec: exec, 138 target: cfg.SysTarget, 139 crashTitle: crashTitle, 140 crashType: crashType, 141 crashStart: crashStart, 142 crashExecutor: crashExecutor, 143 144 entries: entries, 145 testTimeouts: testTimeouts, 146 startOpts: createStartOptions(cfg, env.Features, crashType), 147 stats: new(Stats), 148 timeouts: cfg.Timeouts, 149 observedTitles: map[string]bool{}, 150 fast: env.Fast, 151 logf: env.logf, 152 } 153 return reproCtx.run() 154 } 155 156 func (ctx *reproContext) run() (*Result, *Stats, error) { 157 res, err := ctx.repro() 158 if err != nil { 159 return nil, nil, err 160 } 161 if res != nil { 162 ctx.reproLogf(3, "repro crashed as (corrupted=%v):\n%s", 163 ctx.report.Corrupted, ctx.report.Report) 164 // Try to rerun the repro if the report is corrupted. 165 for attempts := 0; ctx.report.Corrupted && attempts < 3; attempts++ { 166 ctx.reproLogf(3, "report is corrupted, running repro again") 167 if res.CRepro { 168 _, err = ctx.testCProg(res.Prog, res.Duration, res.Opts, false) 169 } else { 170 _, err = ctx.testProg(res.Prog, res.Duration, res.Opts, false) 171 } 172 if err != nil { 173 return nil, nil, err 174 } 175 } 176 ctx.reproLogf(3, "final repro crashed as (corrupted=%v):\n%s", 177 ctx.report.Corrupted, ctx.report.Report) 178 res.Report = ctx.report 179 } 180 return res, ctx.stats, nil 181 } 182 183 func createStartOptions(cfg *mgrconfig.Config, features flatrpc.Feature, 184 crashType crash.Type) csource.Options { 185 opts := csource.DefaultOpts(cfg) 186 if crashType == crash.MemoryLeak { 187 opts.Leak = true 188 } 189 if features&flatrpc.FeatureNetInjection == 0 { 190 opts.NetInjection = false 191 } 192 if features&flatrpc.FeatureNetDevices == 0 { 193 opts.NetDevices = false 194 } 195 if features&flatrpc.FeatureDevlinkPCI == 0 { 196 opts.DevlinkPCI = false 197 } 198 if features&flatrpc.FeatureNicVF == 0 { 199 opts.NicVF = false 200 } 201 if features&flatrpc.FeatureUSBEmulation == 0 { 202 opts.USB = false 203 } 204 if features&flatrpc.FeatureVhciInjection == 0 { 205 opts.VhciInjection = false 206 } 207 if features&flatrpc.FeatureWifiEmulation == 0 { 208 opts.Wifi = false 209 } 210 if features&flatrpc.FeatureLRWPANEmulation == 0 { 211 opts.IEEE802154 = false 212 } 213 if features&flatrpc.FeatureSwap == 0 { 214 opts.Swap = false 215 } 216 return opts 217 } 218 219 func (ctx *reproContext) repro() (*Result, error) { 220 // Cut programs that were executed after crash. 221 for i, ent := range ctx.entries { 222 if ent.Start > ctx.crashStart { 223 ctx.entries = ctx.entries[:i] 224 break 225 } 226 } 227 228 reproStart := time.Now() 229 defer func() { 230 ctx.reproLogf(3, "reproducing took %s", time.Since(reproStart)) 231 ctx.stats.TotalTime = time.Since(reproStart) 232 }() 233 234 res, err := ctx.extractProg(ctx.entries) 235 if err != nil { 236 return nil, err 237 } 238 if res == nil { 239 return nil, nil 240 } 241 res, err = ctx.minimizeProg(res) 242 if err != nil { 243 return nil, err 244 } 245 246 // Try extracting C repro without simplifying options first. 247 if !ctx.fast { 248 res, err = ctx.extractC(res) 249 if err != nil { 250 return nil, err 251 } 252 253 // Simplify options and try extracting C repro. 254 if !res.CRepro { 255 res, err = ctx.simplifyProg(res) 256 if err != nil { 257 return nil, err 258 } 259 } 260 261 // Simplify C related options. 262 if res.CRepro { 263 res, err = ctx.simplifyC(res) 264 if err != nil { 265 return nil, err 266 } 267 } 268 } 269 // Validate the resulting reproducer - a random rare kernel crash might have diverted the process. 270 res.Reliability, err = calculateReliability(func() (bool, error) { 271 ret, err := ctx.testProg(res.Prog, res.Duration, res.Opts, false) 272 if err != nil { 273 return false, err 274 } 275 ctx.reproLogf(2, "validation run: crashed=%v", ret.Crashed) 276 return ret.Crashed, nil 277 }) 278 if err != nil { 279 ctx.reproLogf(2, "could not calculate reliability, err=%v", err) 280 return nil, err 281 } 282 283 const minReliability = 0.15 284 if res.Reliability < minReliability { 285 ctx.reproLogf(1, "reproducer is too unreliable: %.2f", res.Reliability) 286 return nil, err 287 } 288 289 return res, nil 290 } 291 292 func calculateReliability(cb func() (bool, error)) (float64, error) { 293 const ( 294 maxRuns = 10 295 enoughOK = 3 296 ) 297 total := 0 298 okCount := 0 299 for i := 0; i < maxRuns && okCount < enoughOK; i++ { 300 total++ 301 ok, err := cb() 302 if err != nil { 303 return 0, err 304 } 305 if ok { 306 okCount++ 307 } 308 } 309 return float64(okCount) / float64(total), nil 310 } 311 312 func (ctx *reproContext) extractProg(entries []*prog.LogEntry) (*Result, error) { 313 ctx.reproLogf(2, "extracting reproducer from %v programs", len(entries)) 314 start := time.Now() 315 defer func() { 316 ctx.stats.ExtractProgTime = time.Since(start) 317 }() 318 319 var toTest []*prog.LogEntry 320 if ctx.crashExecutor != nil { 321 for _, entry := range entries { 322 // Note: we don't check ProcID b/c hanged programs are assigned fake unique proc IDs 323 // that don't match "Comm" in the kernel panic message. 324 if entry.ID == ctx.crashExecutor.ExecID { 325 toTest = append(toTest, entry) 326 ctx.reproLogf(3, "first checking the prog from the crash report") 327 break 328 } 329 } 330 } 331 332 if len(toTest) == 0 { 333 ctx.reproLogf(3, "testing a last program of every proc") 334 toTest = lastEntries(entries) 335 } 336 337 for i, timeout := range ctx.testTimeouts { 338 // Execute each program separately to detect simple crashes caused by a single program. 339 // Programs are executed in reverse order, usually the last program is the guilty one. 340 res, err := ctx.extractProgSingle(toTest, timeout) 341 if err != nil { 342 return nil, err 343 } 344 if res != nil { 345 ctx.reproLogf(3, "found reproducer with %d syscalls", len(res.Prog.Calls)) 346 return res, nil 347 } 348 349 // Don't try bisecting if there's only one entry. 350 if len(entries) == 1 { 351 continue 352 } 353 354 if ctx.fast && i+1 < len(ctx.testTimeouts) { 355 // Bisect only under the biggest timeout. 356 continue 357 } 358 359 // Execute all programs and bisect the log to find multiple guilty programs. 360 res, err = ctx.extractProgBisect(entries, timeout) 361 if err != nil { 362 return nil, err 363 } 364 if res != nil { 365 ctx.reproLogf(3, "found reproducer with %d syscalls", len(res.Prog.Calls)) 366 return res, nil 367 } 368 } 369 370 ctx.reproLogf(2, "failed to extract reproducer") 371 return nil, nil 372 } 373 374 // Extract last program on every proc. 375 func lastEntries(entries []*prog.LogEntry) []*prog.LogEntry { 376 procs := make(map[int]int) 377 for i, ent := range entries { 378 procs[ent.Proc] = i 379 } 380 var indices []int 381 for _, idx := range procs { 382 indices = append(indices, idx) 383 } 384 sort.Ints(indices) 385 var lastEntries []*prog.LogEntry 386 for i := len(indices) - 1; i >= 0; i-- { 387 lastEntries = append(lastEntries, entries[indices[i]]) 388 } 389 return lastEntries 390 } 391 392 func (ctx *reproContext) extractProgSingle(entries []*prog.LogEntry, duration time.Duration) (*Result, error) { 393 ctx.reproLogf(3, "single: executing %d programs separately with timeout %s", len(entries), duration) 394 395 opts := ctx.startOpts 396 for _, ent := range entries { 397 ret, err := ctx.testProg(ent.P, duration, opts, false) 398 if err != nil { 399 return nil, err 400 } 401 if ret.Crashed { 402 res := &Result{ 403 Prog: ent.P, 404 Duration: max(duration, ret.Duration*3/2), 405 Opts: opts, 406 } 407 ctx.reproLogf(3, "single: successfully extracted reproducer") 408 return res, nil 409 } 410 } 411 412 ctx.reproLogf(3, "single: failed to extract reproducer") 413 return nil, nil 414 } 415 416 func (ctx *reproContext) extractProgBisect(entries []*prog.LogEntry, baseDuration time.Duration) (*Result, error) { 417 ctx.reproLogf(3, "bisect: bisecting %d programs with base timeout %s", len(entries), baseDuration) 418 419 opts := ctx.startOpts 420 duration := func(entries int) time.Duration { 421 return baseDuration + time.Duration(entries/4)*time.Second 422 } 423 424 // First check if replaying the log may crash the kernel at all. 425 ret, err := ctx.testProgs(entries, duration(len(entries)), opts, false) 426 if !ret.Crashed { 427 ctx.reproLogf(3, "replaying the whole log did not cause a kernel crash") 428 return nil, nil 429 } 430 if err != nil { 431 return nil, err 432 } 433 434 // Bisect the log to find multiple guilty programs. 435 entries, err = ctx.bisectProgs(entries, func(progs []*prog.LogEntry) (bool, error) { 436 ret, err := ctx.testProgs(progs, duration(len(progs)), opts, false) 437 return ret.Crashed, err 438 }) 439 if err != nil { 440 return nil, err 441 } 442 if len(entries) == 0 { 443 return nil, nil 444 } 445 446 // TODO: Minimize each program before concatenation. 447 // TODO: Return multiple programs if concatenation fails. 448 449 ctx.reproLogf(3, "bisect: %d programs left: \n\n%s\n", len(entries), encodeEntries(entries)) 450 ctx.reproLogf(3, "bisect: trying to concatenate") 451 452 // Concatenate all programs into one. 453 dur := duration(len(entries)) * 3 / 2 454 return ctx.concatenateProgs(entries, dur) 455 } 456 457 // The bisected progs may exceed the prog.MaxCalls limit. 458 // So let's first try to drop unneeded calls. 459 func (ctx *reproContext) concatenateProgs(entries []*prog.LogEntry, dur time.Duration) (*Result, error) { 460 ctx.reproLogf(3, "bisect: concatenate %d entries", len(entries)) 461 if len(entries) > 1 { 462 // There's a risk of exceeding prog.MaxCalls, so let's first minimize 463 // all entries separately. 464 for i := 0; i < len(entries); i++ { 465 var testErr error 466 ctx.reproLogf(2, "minimizing program #%d before concatenation", i) 467 callsBefore := len(entries[i].P.Calls) 468 entries[i].P, _ = prog.Minimize(entries[i].P, -1, prog.MinimizeCallsOnly, 469 func(p1 *prog.Prog, _ int) bool { 470 if testErr != nil { 471 return false 472 } 473 var newEntries []*prog.LogEntry 474 if i > 0 { 475 newEntries = append(newEntries, entries[:i]...) 476 } 477 newEntries = append(newEntries, &prog.LogEntry{ 478 P: p1, 479 }) 480 if i+1 < len(entries) { 481 newEntries = append(newEntries, entries[i+1:]...) 482 } 483 ret, err := ctx.testProgs(newEntries, dur, ctx.startOpts, false) 484 if err != nil { 485 testErr = err 486 ctx.reproLogf(0, "concatenation step failed with %v", err) 487 return false 488 } 489 return ret.Crashed 490 }) 491 if testErr != nil { 492 return nil, testErr 493 } 494 ctx.reproLogf(2, "minimized %d calls -> %d calls", callsBefore, len(entries[i].P.Calls)) 495 } 496 } 497 p := &prog.Prog{ 498 Target: entries[0].P.Target, 499 } 500 for _, entry := range entries { 501 p.Calls = append(p.Calls, entry.P.Calls...) 502 } 503 if len(p.Calls) > prog.MaxCalls { 504 ctx.reproLogf(2, "bisect: concatenated prog still exceeds %d calls", prog.MaxCalls) 505 return nil, nil 506 } 507 ret, err := ctx.testProg(p, dur, ctx.startOpts, false) 508 if err != nil { 509 ctx.reproLogf(3, "bisect: error during concatenation testing: %v", err) 510 return nil, err 511 } 512 if !ret.Crashed { 513 ctx.reproLogf(3, "bisect: concatenated prog does not crash") 514 return nil, nil 515 } 516 res := &Result{ 517 Prog: p, 518 Duration: min(dur, ret.Duration*2), 519 Opts: ctx.startOpts, 520 } 521 ctx.reproLogf(3, "bisect: concatenation succeeded") 522 return res, nil 523 } 524 525 // Minimize calls and arguments. 526 func (ctx *reproContext) minimizeProg(res *Result) (*Result, error) { 527 ctx.reproLogf(2, "minimizing guilty program") 528 start := time.Now() 529 defer func() { 530 ctx.stats.MinimizeProgTime = time.Since(start) 531 }() 532 533 mode := prog.MinimizeCrash 534 if ctx.fast { 535 mode = prog.MinimizeCallsOnly 536 } 537 var testErr error 538 res.Prog, _ = prog.Minimize(res.Prog, -1, mode, func(p1 *prog.Prog, callIndex int) bool { 539 if testErr != nil { 540 return false 541 } 542 if len(p1.Calls) == 0 { 543 // We do want to keep at least one call, otherwise tools/syz-execprog 544 // will immediately exit. 545 return false 546 } 547 ret, err := ctx.testProg(p1, res.Duration, res.Opts, false) 548 if err != nil { 549 ctx.reproLogf(2, "minimization failed with %v", err) 550 testErr = err 551 return false 552 } 553 return ret.Crashed 554 }) 555 if testErr != nil { 556 return res, nil 557 } 558 return res, nil 559 } 560 561 // Simplify repro options (threaded, sandbox, etc). 562 func (ctx *reproContext) simplifyProg(res *Result) (*Result, error) { 563 ctx.reproLogf(2, "simplifying guilty program options") 564 start := time.Now() 565 defer func() { 566 ctx.stats.SimplifyProgTime = time.Since(start) 567 }() 568 569 // Do further simplifications. 570 for _, simplify := range progSimplifies { 571 opts := res.Opts 572 if !simplify(&opts) || !checkOpts(&opts, ctx.timeouts, res.Duration) { 573 continue 574 } 575 ret, err := ctx.testProg(res.Prog, res.Duration, opts, true) 576 if err != nil { 577 return nil, err 578 } 579 if !ret.Crashed { 580 continue 581 } 582 res.Opts = opts 583 if ctx.fast { 584 continue 585 } 586 // Simplification successful, try extracting C repro. 587 res, err = ctx.extractC(res) 588 if err != nil { 589 return nil, err 590 } 591 if res.CRepro { 592 return res, nil 593 } 594 } 595 596 return res, nil 597 } 598 599 // Try triggering crash with a C reproducer. 600 func (ctx *reproContext) extractC(res *Result) (*Result, error) { 601 ctx.reproLogf(2, "extracting C reproducer") 602 start := time.Now() 603 defer func() { 604 ctx.stats.ExtractCTime = time.Since(start) 605 }() 606 607 ret, err := ctx.testCProg(res.Prog, res.Duration, res.Opts, true) 608 if err != nil { 609 return nil, err 610 } 611 res.CRepro = ret.Crashed 612 return res, nil 613 } 614 615 // Try to simplify the C reproducer. 616 func (ctx *reproContext) simplifyC(res *Result) (*Result, error) { 617 ctx.reproLogf(2, "simplifying C reproducer") 618 start := time.Now() 619 defer func() { 620 ctx.stats.SimplifyCTime = time.Since(start) 621 }() 622 623 for _, simplify := range cSimplifies { 624 opts := res.Opts 625 if !simplify(&opts) || !checkOpts(&opts, ctx.timeouts, res.Duration) { 626 continue 627 } 628 ret, err := ctx.testCProg(res.Prog, res.Duration, opts, true) 629 if err != nil { 630 return nil, err 631 } 632 if !ret.Crashed { 633 continue 634 } 635 res.Opts = opts 636 } 637 return res, nil 638 } 639 640 func checkOpts(opts *csource.Options, timeouts targets.Timeouts, timeout time.Duration) bool { 641 if !opts.Repeat && timeout >= time.Minute { 642 // If we have a non-repeating C reproducer with timeout > vm.NoOutputTimeout and it hangs 643 // (the reproducer itself does not terminate on its own, note: it does not have builtin timeout), 644 // then we will falsely detect "not output from test machine" kernel bug. 645 // We could fix it by adding a builtin timeout to such reproducers (like we have in all other cases). 646 // However, then it will exit within few seconds and we will finish the test without actually waiting 647 // for full vm.NoOutputTimeout, which breaks the whole reason of using vm.NoOutputTimeout in the first 648 // place. So we would need something more elaborate: let the program exist after few seconds, but 649 // continue waiting for kernel hang errors for minutes, but at the same time somehow ignore "no output" 650 // error because it will be false in this case. 651 // Instead we simply prohibit !Repeat with long timeouts. 652 // It makes sense on its own to some degree: if we are chasing an elusive bug, repeating the test 653 // will increase chances of reproducing it and can make the reproducer less flaky. 654 // Syz repros does not have this problem because they always have internal timeout, however 655 // (1) it makes sense on its own, (2) we will either not use the whole timeout or waste the remaining 656 // time as mentioned above, (3) if we remove repeat for syz repro, we won't be able to handle it 657 // when/if we switch to C repro (we can simplify options, but we can't "complicate" them back). 658 return false 659 } 660 return true 661 } 662 663 func (ctx *reproContext) testProg(p *prog.Prog, duration time.Duration, opts csource.Options, 664 strict bool) (ret verdict, err error) { 665 entry := prog.LogEntry{P: p} 666 return ctx.testProgs([]*prog.LogEntry{&entry}, duration, opts, strict) 667 } 668 669 type verdict struct { 670 Crashed bool 671 Duration time.Duration 672 } 673 674 func (ctx *reproContext) getVerdict(callback func() (rep *instance.RunResult, err error), strict bool) ( 675 verdict, error) { 676 var result *instance.RunResult 677 var err error 678 679 const attempts = 3 680 for i := 0; i < attempts; i++ { 681 // It's hard to classify all kinds of errors into the one worth repeating 682 // and not. So let's just retry runs for all errors. 683 // If the problem is transient, it will likely go away. 684 // If the problem is permanent, it will just be the same. 685 result, err = callback() 686 if err == nil { 687 break 688 } 689 } 690 if err != nil { 691 return verdict{}, err 692 } 693 rep := result.Report 694 if rep == nil { 695 return verdict{false, result.Duration}, nil 696 } 697 if rep.Suppressed { 698 ctx.reproLogf(2, "suppressed program crash: %v", rep.Title) 699 return verdict{false, result.Duration}, nil 700 } 701 if ctx.crashType == crash.MemoryLeak && rep.Type != crash.MemoryLeak { 702 ctx.reproLogf(2, "not a leak crash: %v", rep.Title) 703 return verdict{false, result.Duration}, nil 704 } 705 if strict && len(ctx.observedTitles) > 0 { 706 if !ctx.observedTitles[rep.Title] { 707 ctx.reproLogf(2, "a never seen crash title: %v, ignore", rep.Title) 708 return verdict{false, result.Duration}, nil 709 } 710 } else { 711 ctx.observedTitles[rep.Title] = true 712 } 713 ctx.report = rep 714 return verdict{true, result.Duration}, nil 715 } 716 717 var ErrNoVMs = errors.New("all VMs failed to boot") 718 719 func encodeEntries(entries []*prog.LogEntry) []byte { 720 buf := new(bytes.Buffer) 721 for _, ent := range entries { 722 if len(ent.P.Calls) > prog.MaxCalls { 723 panic("prog.MaxCalls is exceeded") 724 } 725 fmt.Fprintf(buf, "executing program %v:\n%v", ent.Proc, string(ent.P.Serialize())) 726 } 727 return buf.Bytes() 728 } 729 730 func (ctx *reproContext) testProgs(entries []*prog.LogEntry, duration time.Duration, opts csource.Options, 731 strict bool) (ret verdict, err error) { 732 if len(entries) == 0 { 733 return ret, fmt.Errorf("no programs to execute") 734 } 735 pstr := encodeEntries(entries) 736 program := entries[0].P.String() 737 if len(entries) > 1 { 738 program = "[" 739 for i, entry := range entries { 740 program += fmt.Sprintf("%v", len(entry.P.Calls)) 741 if i != len(entries)-1 { 742 program += ", " 743 } 744 } 745 program += "]" 746 } 747 ctx.reproLogf(2, "testing program (duration=%v, %+v): %s", duration, opts, program) 748 ctx.reproLogf(3, "detailed listing:\n%s", pstr) 749 return ctx.getVerdict(func() (*instance.RunResult, error) { 750 return ctx.exec.Run(ctx.ctx, instance.ExecParams{ 751 SyzProg: pstr, 752 Opts: opts, 753 Duration: duration, 754 }, ctx.reproLogf) 755 }, strict) 756 } 757 758 func (ctx *reproContext) testCProg(p *prog.Prog, duration time.Duration, opts csource.Options, 759 strict bool) (ret verdict, err error) { 760 return ctx.getVerdict(func() (*instance.RunResult, error) { 761 return ctx.exec.Run(ctx.ctx, instance.ExecParams{ 762 CProg: p, 763 Opts: opts, 764 Duration: duration, 765 }, ctx.reproLogf) 766 }, strict) 767 } 768 769 func (ctx *reproContext) reproLogf(level int, format string, args ...interface{}) { 770 if ctx.logf != nil { 771 ctx.logf(format, args...) 772 } 773 prefix := fmt.Sprintf("reproducing crash '%v': ", ctx.crashTitle) 774 log.Logf(level, prefix+format, args...) 775 ctx.stats.Log = append(ctx.stats.Log, []byte(fmt.Sprintf(format, args...)+"\n")...) 776 } 777 778 func (ctx *reproContext) bisectProgs(progs []*prog.LogEntry, pred func([]*prog.LogEntry) (bool, error)) ( 779 []*prog.LogEntry, error) { 780 // Set up progs bisection. 781 ctx.reproLogf(3, "bisect: bisecting %d programs", len(progs)) 782 minimizePred := func(progs []*prog.LogEntry) (bool, error) { 783 // Don't waste time testing empty crash log. 784 if len(progs) == 0 { 785 return false, nil 786 } 787 return pred(progs) 788 } 789 // For flaky crashes we usually end up with too many chunks. 790 // Continuing bisection would just take a lot of time and likely produce no result. 791 chunks := 6 792 if ctx.fast { 793 chunks = 2 794 } 795 ret, err := minimize.SliceWithFixed(minimize.Config[*prog.LogEntry]{ 796 Pred: minimizePred, 797 MaxChunks: chunks, 798 Logf: func(msg string, args ...interface{}) { 799 ctx.reproLogf(3, "bisect: "+msg, args...) 800 }, 801 }, progs, func(elem *prog.LogEntry) bool { 802 if ctx.crashExecutor == nil { 803 return false 804 } 805 // If the program was mentioned in the crash report, always keep it during bisection. 806 return elem.ID == ctx.crashExecutor.ExecID 807 }) 808 if err == minimize.ErrTooManyChunks { 809 ctx.reproLogf(3, "bisect: too many guilty chunks, aborting") 810 return nil, nil 811 } 812 return ret, err 813 } 814 815 type poolWrapper struct { 816 cfg *mgrconfig.Config 817 reporter *report.Reporter 818 pool *vm.Dispatcher 819 } 820 821 func (pw *poolWrapper) Run(ctx context.Context, params instance.ExecParams, 822 logf instance.ExecutorLogger) (*instance.RunResult, error) { 823 if err := ctx.Err(); err != nil { 824 // Note that we could also propagate ctx down to SetupExecProg() and RunCProg() operations, 825 // but so far it does not seem to be worth the effort. 826 return nil, err 827 } 828 829 var result *instance.RunResult 830 var err error 831 runErr := pw.pool.Run(ctx, func(ctx context.Context, inst *vm.Instance, updInfo dispatcher.UpdateInfo) { 832 updInfo(func(info *dispatcher.Info) { 833 typ := "syz" 834 if params.CProg != nil { 835 typ = "C" 836 } 837 info.Status = fmt.Sprintf("reproducing (%s, %.1f min)", typ, params.Duration.Minutes()) 838 }) 839 var ret *instance.ExecProgInstance 840 ret, err = instance.SetupExecProg(inst, pw.cfg, pw.reporter, 841 &instance.OptionalConfig{Logf: logf}) 842 if err != nil { 843 return 844 } 845 if params.CProg != nil { 846 result, err = ret.RunCProg(params) 847 } else { 848 result, err = ret.RunSyzProg(params) 849 } 850 }) 851 if runErr != nil { 852 return nil, runErr 853 } 854 return result, err 855 } 856 857 type Simplify func(opts *csource.Options) bool 858 859 var progSimplifies = []Simplify{ 860 func(opts *csource.Options) bool { 861 if opts.Collide || !opts.Threaded { 862 return false 863 } 864 opts.Threaded = false 865 return true 866 }, 867 func(opts *csource.Options) bool { 868 if !opts.Repeat { 869 return false 870 } 871 opts.Repeat = false 872 opts.Cgroups = false 873 opts.NetReset = false 874 opts.Procs = 1 875 return true 876 }, 877 func(opts *csource.Options) bool { 878 if opts.Procs == 1 { 879 return false 880 } 881 opts.Procs = 1 882 return true 883 }, 884 func(opts *csource.Options) bool { 885 if opts.Sandbox == "none" { 886 return false 887 } 888 opts.Sandbox = "none" 889 return true 890 }, 891 } 892 893 var cSimplifies = append(progSimplifies, []Simplify{ 894 func(opts *csource.Options) bool { 895 if opts.Sandbox == "" { 896 return false 897 } 898 opts.Sandbox = "" 899 opts.NetInjection = false 900 opts.NetDevices = false 901 opts.NetReset = false 902 opts.Cgroups = false 903 opts.BinfmtMisc = false 904 opts.CloseFDs = false 905 opts.DevlinkPCI = false 906 opts.NicVF = false 907 opts.USB = false 908 opts.VhciInjection = false 909 opts.Wifi = false 910 opts.Swap = false 911 return true 912 }, 913 func(opts *csource.Options) bool { 914 if !opts.NetInjection { 915 return false 916 } 917 opts.NetInjection = false 918 return true 919 }, 920 func(opts *csource.Options) bool { 921 if !opts.NetDevices { 922 return false 923 } 924 opts.NetDevices = false 925 return true 926 }, 927 func(opts *csource.Options) bool { 928 if !opts.NetReset { 929 return false 930 } 931 opts.NetReset = false 932 return true 933 }, 934 func(opts *csource.Options) bool { 935 if !opts.Cgroups { 936 return false 937 } 938 opts.Cgroups = false 939 return true 940 }, 941 func(opts *csource.Options) bool { 942 if !opts.BinfmtMisc { 943 return false 944 } 945 opts.BinfmtMisc = false 946 return true 947 }, 948 func(opts *csource.Options) bool { 949 // We don't want to remove close_fds() call when repeat is enabled, 950 // since that can lead to deadlocks, see executor/common_linux.h. 951 if !opts.CloseFDs || opts.Repeat { 952 return false 953 } 954 opts.CloseFDs = false 955 return true 956 }, 957 func(opts *csource.Options) bool { 958 if !opts.DevlinkPCI { 959 return false 960 } 961 opts.DevlinkPCI = false 962 return true 963 }, 964 func(opts *csource.Options) bool { 965 if !opts.NicVF { 966 return false 967 } 968 opts.NicVF = false 969 return true 970 }, 971 func(opts *csource.Options) bool { 972 if !opts.USB { 973 return false 974 } 975 opts.USB = false 976 return true 977 }, 978 func(opts *csource.Options) bool { 979 if !opts.VhciInjection { 980 return false 981 } 982 opts.VhciInjection = false 983 return true 984 }, 985 func(opts *csource.Options) bool { 986 if !opts.Wifi { 987 return false 988 } 989 opts.Wifi = false 990 return true 991 }, 992 func(opts *csource.Options) bool { 993 if !opts.IEEE802154 { 994 return false 995 } 996 opts.IEEE802154 = false 997 return true 998 }, 999 func(opts *csource.Options) bool { 1000 if !opts.UseTmpDir || opts.Sandbox == "namespace" || opts.Cgroups { 1001 return false 1002 } 1003 opts.UseTmpDir = false 1004 return true 1005 }, 1006 func(opts *csource.Options) bool { 1007 if !opts.HandleSegv { 1008 return false 1009 } 1010 opts.HandleSegv = false 1011 return true 1012 }, 1013 func(opts *csource.Options) bool { 1014 if !opts.Sysctl { 1015 return false 1016 } 1017 opts.Sysctl = false 1018 return true 1019 }, 1020 func(opts *csource.Options) bool { 1021 if !opts.Swap { 1022 return false 1023 } 1024 opts.Swap = false 1025 return true 1026 }, 1027 }...) 1028 1029 func (stats *Stats) FullLog() []byte { 1030 if stats == nil { 1031 return nil 1032 } 1033 return []byte(fmt.Sprintf("Extracting prog: %v\nMinimizing prog: %v\n"+ 1034 "Simplifying prog options: %v\nExtracting C: %v\nSimplifying C: %v\n\n\n%s", 1035 stats.ExtractProgTime, stats.MinimizeProgTime, 1036 stats.SimplifyProgTime, stats.ExtractCTime, stats.SimplifyCTime, stats.Log)) 1037 } 1038 1039 func (repro *Result) CProgram() ([]byte, error) { 1040 cprog, err := csource.Write(repro.Prog, repro.Opts) 1041 if err == nil { 1042 formatted, err := csource.Format(cprog) 1043 if err == nil { 1044 return formatted, nil 1045 } 1046 return cprog, nil 1047 } 1048 return nil, err 1049 }