aboutsummaryrefslogtreecommitdiffstats
path: root/pkg/manager/diff/repro.go
diff options
context:
space:
mode:
authorAleksandr Nogikh <nogikh@google.com>2026-01-23 17:18:49 +0100
committerAleksandr Nogikh <nogikh@google.com>2026-01-23 20:35:29 +0000
commit4f25b9b48bdfbc7adeaf320f8d92067afe509b49 (patch)
treee66a51fc3786e8bb8abba5ca89cfc152a59e971f /pkg/manager/diff/repro.go
parentb4afeb6fb8cde041ba03048f5e123ed3f304a5e6 (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.go116
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():
+ }
+}