diff options
| -rw-r--r-- | pkg/aflow/action/crash/reproduce.go | 115 |
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 +} |
