diff options
| author | Aleksandr Nogikh <nogikh@google.com> | 2026-01-23 17:18:49 +0100 |
|---|---|---|
| committer | Aleksandr Nogikh <nogikh@google.com> | 2026-01-23 20:35:29 +0000 |
| commit | 4f25b9b48bdfbc7adeaf320f8d92067afe509b49 (patch) | |
| tree | e66a51fc3786e8bb8abba5ca89cfc152a59e971f /pkg/manager/diff/repro.go | |
| parent | b4afeb6fb8cde041ba03048f5e123ed3f304a5e6 (diff) | |
pkg/manager: split off diff fuzzer functionality
Move the code to a separate pkg/manager/diff package. Split the
code into several files.
Diffstat (limited to 'pkg/manager/diff/repro.go')
| -rw-r--r-- | pkg/manager/diff/repro.go | 116 |
1 files changed, 116 insertions, 0 deletions
diff --git a/pkg/manager/diff/repro.go b/pkg/manager/diff/repro.go new file mode 100644 index 000000000..8dbf907da --- /dev/null +++ b/pkg/manager/diff/repro.go @@ -0,0 +1,116 @@ +// Copyright 2026 syzkaller project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +package diff + +import ( + "context" + "errors" + "fmt" + "sync/atomic" + "time" + + "github.com/google/syzkaller/pkg/instance" + "github.com/google/syzkaller/pkg/log" + "github.com/google/syzkaller/pkg/report" + "github.com/google/syzkaller/pkg/repro" + "github.com/google/syzkaller/vm" + "github.com/google/syzkaller/vm/dispatcher" +) + +// reproRunner is used to run reproducers on the base kernel to determine whether it is affected. +type reproRunner struct { + done chan reproRunnerResult + running atomic.Int64 + kernel *kernelContext +} + +type reproRunnerResult struct { + reproReport *report.Report + crashReport *report.Report + repro *repro.Result + fullRepro bool // whether this was a full reproduction +} + +const ( + // We want to avoid false positives as much as possible, so let's use + // a stricter relibability cut-off than what's used inside pkg/repro. + reliabilityCutOff = 0.4 + // 80% reliability x 3 runs is a 0.8% chance of false positives. + // 6 runs at 40% reproducibility gives a ~4% false positive chance. + reliabilityThreshold = 0.8 +) + +// Run executes the reproducer 3 times with slightly different options. +// The objective is to verify whether the bug triggered by the reproducer affects the base kernel. +// To avoid reporting false positives, the function does not require the kernel to crash with exactly +// the same crash title as in the original crash report. Any single crash is accepted. +// The result is sent back over the rr.done channel. +func (rr *reproRunner) Run(ctx context.Context, r *repro.Result, fullRepro bool) { + if r.Reliability < reliabilityCutOff { + log.Logf(1, "%s: repro is too unreliable, skipping", r.Report.Title) + return + } + needRuns := 3 + if r.Reliability < reliabilityThreshold { + needRuns = 6 + } + + pool := rr.kernel.pool + cnt := int(rr.running.Add(1)) + pool.ReserveForRun(min(cnt, pool.Total())) + defer func() { + cnt := int(rr.running.Add(-1)) + rr.kernel.pool.ReserveForRun(min(cnt, pool.Total())) + }() + + ret := reproRunnerResult{reproReport: r.Report, repro: r, fullRepro: fullRepro} + for doneRuns := 0; doneRuns < needRuns; { + if ctx.Err() != nil { + return + } + opts := r.Opts + opts.Repeat = true + if doneRuns%3 != 2 { + // Two times out of 3, test with Threaded=true. + // The third time we leave it as it was in the reproducer (in case it was important). + opts.Threaded = true + } + var err error + var result *instance.RunResult + runErr := pool.Run(ctx, func(ctx context.Context, inst *vm.Instance, updInfo dispatcher.UpdateInfo) { + var ret *instance.ExecProgInstance + ret, err = instance.SetupExecProg(inst, rr.kernel.cfg, rr.kernel.reporter, nil) + if err != nil { + return + } + result, err = ret.RunSyzProg(instance.ExecParams{ + SyzProg: r.Prog.Serialize(), + Duration: max(r.Duration, time.Minute), + Opts: opts, + }) + }) + logPrefix := fmt.Sprintf("attempt #%d to run %q on base", doneRuns, ret.reproReport.Title) + if errors.Is(runErr, context.Canceled) { + // Just exit without sending anything over the channel. + log.Logf(1, "%s: aborting due to context cancelation", logPrefix) + return + } + if runErr != nil || err != nil { + log.Logf(1, "%s: skipping due to errors: %v / %v", logPrefix, runErr, err) + continue + } + doneRuns++ + if result != nil && result.Report != nil { + log.Logf(1, "%s: crashed with %s", logPrefix, result.Report.Title) + ret.crashReport = result.Report + break + } else { + log.Logf(1, "%s: did not crash", logPrefix) + } + } + select { + case rr.done <- ret: + case <-ctx.Done(): + } +} |
