From 3d100869856aed9bca7cac3ab7c9c162b9fee802 Mon Sep 17 00:00:00 2001 From: Florent Revest Date: Mon, 26 Jan 2026 15:59:20 +0100 Subject: pkg/aflow/action/crash/test: implement patch testing --- pkg/aflow/action/crash/reproduce.go | 94 ++++++++++++++++++++----------------- pkg/aflow/action/crash/test.go | 72 +++++++++++++++++++++++++++- pkg/aflow/action/kernel/build.go | 71 +++++++++++++++------------- 3 files changed, 160 insertions(+), 77 deletions(-) (limited to 'pkg') diff --git a/pkg/aflow/action/crash/reproduce.go b/pkg/aflow/action/crash/reproduce.go index 05816dfed..1d5952ced 100644 --- a/pkg/aflow/action/crash/reproduce.go +++ b/pkg/aflow/action/crash/reproduce.go @@ -23,7 +23,7 @@ import ( // If the reproducer does not trigger a crash, action fails. var Reproduce = aflow.NewFuncAction("crash-reproducer", reproduce) -type reproduceArgs struct { +type ReproduceArgs struct { Syzkaller string Image string Type string @@ -42,11 +42,55 @@ type reproduceResult struct { CrashReport string } -func reproduce(ctx *aflow.Context, args reproduceArgs) (reproduceResult, error) { +func ReproduceCrash(args ReproduceArgs, workdir string) (string, string, 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") + return "", "", errors.New("only qemu VM type is supported") } + + 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 = 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 + } + if results[0].Error != nil { + if crashErr := new(instance.CrashError); errors.As(results[0].Error, &crashErr) { + return string(crashErr.Report.Report), "", nil + } else { + return "", results[0].Error.Error(), nil + } + } + return "", "", nil +} + +func reproduce(ctx *aflow.Context, args ReproduceArgs) (reproduceResult, error) { imageData, err := os.ReadFile(args.Image) if err != nil { return reproduceResult{}, err @@ -61,50 +105,12 @@ func reproduce(ctx *aflow.Context, args reproduceArgs) (reproduceResult, error) } cached, err := aflow.CacheObject(ctx, "repro", desc, func() (Cached, error) { var res Cached - var vmConfig map[string]any - if err := json.Unmarshal(args.VM, &vmConfig); err != nil { - return res, 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 res, fmt.Errorf("failed to serialize VM config: %w", err) - } workdir, err := ctx.TempDir() if err != nil { return res, err } - cfg := mgrconfig.DefaultValues() - cfg.RawTarget = "linux/amd64" - cfg.Workdir = 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 res, err - } - if err := mgrconfig.Complete(cfg); err != nil { - return res, err - } - env, err := instance.NewEnv(cfg, nil, nil) - if err != nil { - return res, err - } - results, err := env.Test(1, nil, nil, []byte(args.ReproC)) - if err != nil { - return res, err - } - if results[0].Error != nil { - if crashErr := new(instance.CrashError); errors.As(results[0].Error, &crashErr) { - res.Report = string(crashErr.Report.Report) - } else { - res.Error = results[0].Error.Error() - } - } - return res, nil + res.Error, res.Report, err = ReproduceCrash(args, workdir) + return res, err }) if err != nil { return reproduceResult{}, err diff --git a/pkg/aflow/action/crash/test.go b/pkg/aflow/action/crash/test.go index 127d8144e..5c7d831b5 100644 --- a/pkg/aflow/action/crash/test.go +++ b/pkg/aflow/action/crash/test.go @@ -5,8 +5,12 @@ package crash import ( "encoding/json" + "fmt" + "time" "github.com/google/syzkaller/pkg/aflow" + "github.com/google/syzkaller/pkg/aflow/action/kernel" + "github.com/google/syzkaller/pkg/osutil" ) // TestPatch action does an in-tree kernel build in KernelScratchSrc dir, @@ -36,5 +40,71 @@ type testResult struct { } func testPatch(ctx *aflow.Context, args testArgs) (testResult, error) { - return testResult{}, nil + res := testResult{} + defer undoChanges(args.KernelScratchSrc) + + diff, err := currentDiff(args.KernelScratchSrc) + if err != nil { + return res, err + } + res.PatchDiff = diff + + if err := kernel.BuildKernel(args.KernelScratchSrc, args.KernelScratchSrc, args.KernelConfig, false); err != nil { + res.TestError = fmt.Sprintf("Building the kernel failed with %v", err) + return res, nil + } + + workdir, err := ctx.TempDir() + if err != nil { + return res, err + } + reproduceArgs := ReproduceArgs{ + Syzkaller: args.Syzkaller, + Image: args.Image, + Type: args.Type, + VM: args.VM, + ReproOpts: args.ReproOpts, + ReproSyz: args.ReproSyz, + ReproC: args.ReproC, + SyzkallerCommit: args.SyzkallerCommit, + KernelSrc: args.KernelScratchSrc, + KernelObj: args.KernelScratchSrc, + KernelCommit: args.KernelCommit, + KernelConfig: args.KernelConfig, + } + errorLog, reportLog, err := ReproduceCrash(reproduceArgs, workdir) + if errorLog != "" { + res.TestError = errorLog + } else { + res.TestError = reportLog + } + return res, err +} + +func currentDiff(repo string) (string, error) { + // Mark the "intent to add" on all files so git diff also shows currently untracked files. + _, err := osutil.RunCmd(time.Minute, repo, "git", "add", "-N", ".") + if err != nil { + return "", err + } + diff, err := osutil.RunCmd(time.Minute, repo, "git", "diff") + if err != nil { + return "", err + } + return string(diff), nil +} + +func undoChanges(repo string) error { + // Unset the "intent to add", otherwise git clean doesn't remove these files. + _, err := osutil.RunCmd(time.Minute, repo, "git", "reset") + if err != nil { + return err + } + _, err = osutil.RunCmd(time.Minute, repo, "git", "checkout", "--", ".") + if err != nil { + return err + } + // We do not use -fdx to keep object files around and make the next tool call faster. + _, err = osutil.RunCmd(time.Minute, repo, "git", "clean", "-fd") + return err } diff --git a/pkg/aflow/action/kernel/build.go b/pkg/aflow/action/kernel/build.go index 575eec6be..564339867 100644 --- a/pkg/aflow/action/kernel/build.go +++ b/pkg/aflow/action/kernel/build.go @@ -34,42 +34,49 @@ type buildResult struct { KernelObj string // Directory with build artifacts. } -func buildKernel(ctx *aflow.Context, args buildArgs) (buildResult, error) { - desc := fmt.Sprintf("kernel commit %v, kernel config hash %v", - args.KernelCommit, hash.String(args.KernelConfig)) - dir, err := ctx.Cache("build", desc, func(dir string) error { - if err := osutil.WriteFile(filepath.Join(dir, ".config"), []byte(args.KernelConfig)); err != nil { +func BuildKernel(buildDir, srcDir, cfg string, cleanup bool) error { + if err := osutil.WriteFile(filepath.Join(buildDir, ".config"), []byte(cfg)); err != nil { + return err + } + target := targets.List[targets.Linux][targets.AMD64] + image := filepath.FromSlash(build.LinuxKernelImage(targets.AMD64)) + makeArgs := build.LinuxMakeArgs(target, targets.DefaultLLVMCompiler, targets.DefaultLLVMLinker, + "ccache", buildDir, runtime.NumCPU()) + const compileCommands = "compile_commands.json" + makeArgs = append(makeArgs, "-s", path.Base(image), compileCommands) + if out, err := osutil.RunCmd(time.Hour, srcDir, "make", makeArgs...); err != nil { + return aflow.FlowError(fmt.Errorf("make failed: %w\n%s", err, out)) + } + if !cleanup { + return nil + } + // Remove main intermediate build files, we don't need them anymore + // and they take lots of space. But keep generated source files. + keepFiles := map[string]bool{ + image: true, + target.KernelObject: true, + compileCommands: true, + } + return filepath.WalkDir(buildDir, func(path string, d fs.DirEntry, err error) error { + if err != nil { return err } - target := targets.List[targets.Linux][targets.AMD64] - image := filepath.FromSlash(build.LinuxKernelImage(targets.AMD64)) - makeArgs := build.LinuxMakeArgs(target, targets.DefaultLLVMCompiler, targets.DefaultLLVMLinker, - "ccache", dir, runtime.NumCPU()) - const compileCommands = "compile_commands.json" - makeArgs = append(makeArgs, "-s", path.Base(image), compileCommands) - if out, err := osutil.RunCmd(time.Hour, args.KernelSrc, "make", makeArgs...); err != nil { - return aflow.FlowError(fmt.Errorf("make failed: %w\n%s", err, out)) + relative, err := filepath.Rel(buildDir, path) + if err != nil { + return err } - // Remove main intermediate build files, we don't need them anymore - // and they take lots of space. But keep generated source files. - keepFiles := map[string]bool{ - image: true, - target.KernelObject: true, - compileCommands: true, + if d.IsDir() || keepFiles[relative] || codesearch.IsSourceFile(relative) { + return nil } - return filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - relative, err := filepath.Rel(dir, path) - if err != nil { - return err - } - if d.IsDir() || keepFiles[relative] || codesearch.IsSourceFile(relative) { - return nil - } - return os.Remove(path) - }) + return os.Remove(path) + }) +} + +func buildKernel(ctx *aflow.Context, args buildArgs) (buildResult, error) { + desc := fmt.Sprintf("kernel commit %v, kernel config hash %v", + args.KernelCommit, hash.String(args.KernelConfig)) + dir, err := ctx.Cache("build", desc, func(dir string) error { + return BuildKernel(dir, args.KernelSrc, args.KernelConfig, true) }) return buildResult{KernelObj: dir}, err } -- cgit mrf-deployment