aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2026-01-02 17:08:46 +0100
committerDmitry Vyukov <dvyukov@google.com>2026-01-09 12:51:45 +0000
commit7470d8c5d6e719b4e003ae617b6865b7c784236a (patch)
treed631bb63bd8d8b294dadbab32bdd8cc9cedccd69
parent18d20041d81e47628ff18d54a2a7f7c6bf51ae8a (diff)
pkg/aflow/action/crash: add crash repro tool
-rw-r--r--pkg/aflow/action/crash/reproduce.go115
1 files changed, 115 insertions, 0 deletions
diff --git a/pkg/aflow/action/crash/reproduce.go b/pkg/aflow/action/crash/reproduce.go
new file mode 100644
index 000000000..33be02b27
--- /dev/null
+++ b/pkg/aflow/action/crash/reproduce.go
@@ -0,0 +1,115 @@
+// Copyright 2025 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 crash
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "os"
+ "path/filepath"
+
+ "github.com/google/syzkaller/pkg/aflow"
+ "github.com/google/syzkaller/pkg/build"
+ "github.com/google/syzkaller/pkg/hash"
+ "github.com/google/syzkaller/pkg/instance"
+ "github.com/google/syzkaller/pkg/mgrconfig"
+ "github.com/google/syzkaller/pkg/osutil"
+ "github.com/google/syzkaller/sys/targets"
+)
+
+// Reproduce action tries to reproduce a crash with the given reproducer,
+// and outputs the resulting crash report.
+// If the reproducer does not trigger a crash, action fails.
+var Reproduce = aflow.NewFuncAction("crash-reproducer", reproduce)
+
+type reproduceArgs struct {
+ Syzkaller string
+ Image string
+ Type string
+ VM json.RawMessage
+ ReproOpts string
+ ReproSyz string
+ ReproC string
+ SyzkallerCommit string
+ KernelSrc string
+ KernelObj string
+ KernelCommit string
+ KernelConfig string
+}
+
+type reproduceResult struct {
+ CrashReport string
+}
+
+func reproduce(ctx *aflow.Context, args reproduceArgs) (reproduceResult, error) {
+ if args.Type != "qemu" {
+ // Since we use injected kernel boot, and don't build full disk image.
+ return reproduceResult{}, errors.New("only qemu VM type is supported")
+ }
+ imageData, err := os.ReadFile(args.Image)
+ if err != nil {
+ return reproduceResult{}, err
+ }
+ desc := fmt.Sprintf("kernel commit %v, kernel config hash %v, image hash %v,"+
+ " vm %v, vm config hash %v, C repro hash %v",
+ args.KernelCommit, hash.String(args.KernelConfig), hash.String(imageData),
+ args.Type, hash.String(args.VM), hash.String(args.ReproC))
+ dir, err := ctx.Cache("repro", desc, func(dir string) error {
+ var vmConfig map[string]any
+ if err := json.Unmarshal(args.VM, &vmConfig); err != nil {
+ return fmt.Errorf("failed to parse VM config: %w", err)
+ }
+ vmConfig["kernel"] = filepath.Join(args.KernelObj, filepath.FromSlash(build.LinuxKernelImage(targets.AMD64)))
+ vmCfg, err := json.Marshal(vmConfig)
+ if err != nil {
+ return fmt.Errorf("failed to serialize VM config: %w", err)
+ }
+ cfg := mgrconfig.DefaultValues()
+ cfg.RawTarget = "linux/amd64"
+ cfg.Workdir = filepath.Join(dir, "workdir")
+ cfg.Syzkaller = args.Syzkaller
+ cfg.KernelObj = args.KernelObj
+ cfg.KernelSrc = args.KernelSrc
+ cfg.Image = args.Image
+ cfg.Type = args.Type
+ cfg.VM = vmCfg
+ if err := mgrconfig.SetTargets(cfg); err != nil {
+ return err
+ }
+ if err := mgrconfig.Complete(cfg); err != nil {
+ return err
+ }
+ env, err := instance.NewEnv(cfg, nil, nil)
+ if err != nil {
+ return err
+ }
+ results, err := env.Test(1, nil, nil, []byte(args.ReproC))
+ if err != nil {
+ return err
+ }
+ os.RemoveAll(cfg.Workdir)
+ if results[0].Error == nil {
+ results[0].Error = errors.New("reproducer did not crash")
+ }
+ file, data := "", []byte(nil)
+ var crashErr *instance.CrashError
+ if errors.As(results[0].Error, &crashErr) {
+ file, data = "report", crashErr.Report.Report
+ } else {
+ file, data = "error", []byte(results[0].Error.Error())
+ }
+ return osutil.WriteFile(filepath.Join(dir, file), data)
+ })
+ if err != nil {
+ return reproduceResult{}, err
+ }
+ if data, err := os.ReadFile(filepath.Join(dir, "error")); err == nil {
+ return reproduceResult{}, errors.New(string(data))
+ }
+ data, err := os.ReadFile(filepath.Join(dir, "report"))
+ return reproduceResult{
+ CrashReport: string(data),
+ }, err
+}