aboutsummaryrefslogtreecommitdiffstats
path: root/pkg
diff options
context:
space:
mode:
authorFlorent Revest <revest@chromium.org>2026-01-26 15:59:20 +0100
committerDmitry Vyukov <dvyukov@google.com>2026-01-26 18:04:21 +0000
commit3d100869856aed9bca7cac3ab7c9c162b9fee802 (patch)
treec639d611e1792163c9dd441296858ee35db82dee /pkg
parent28819515a548ec25957603ae1fac0620c3eaa9a0 (diff)
pkg/aflow/action/crash/test: implement patch testing
Diffstat (limited to 'pkg')
-rw-r--r--pkg/aflow/action/crash/reproduce.go94
-rw-r--r--pkg/aflow/action/crash/test.go72
-rw-r--r--pkg/aflow/action/kernel/build.go71
3 files changed, 160 insertions, 77 deletions
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
}