Skip to content

Commit a1c8a7c

Browse files
committed
pkg/aflow/action/crash/test: implement patch testing
1 parent b1fba58 commit a1c8a7c

File tree

3 files changed

+142
-73
lines changed

3 files changed

+142
-73
lines changed

pkg/aflow/action/crash/reproduce.go

Lines changed: 46 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,50 @@ type reproduceResult struct {
4242
CrashReport string
4343
}
4444

45+
func ReproduceCrash(syzkaller, kernelObj, kernelSrc, image, Type, reproC, workdir string, vm []byte) (string, string, error) {
46+
var vmConfig map[string]any
47+
if err := json.Unmarshal(vm, &vmConfig); err != nil {
48+
return "", "", fmt.Errorf("failed to parse VM config: %w", err)
49+
}
50+
vmConfig["kernel"] = filepath.Join(kernelObj, filepath.FromSlash(build.LinuxKernelImage(targets.AMD64)))
51+
vmCfg, err := json.Marshal(vmConfig)
52+
if err != nil {
53+
return "", "", fmt.Errorf("failed to serialize VM config: %w", err)
54+
}
55+
56+
cfg := mgrconfig.DefaultValues()
57+
cfg.RawTarget = "linux/amd64"
58+
cfg.Workdir = workdir
59+
cfg.Syzkaller = syzkaller
60+
cfg.KernelObj = kernelObj
61+
cfg.KernelSrc = kernelSrc
62+
cfg.Image = image
63+
cfg.Type = Type
64+
cfg.VM = vmCfg
65+
if err := mgrconfig.SetTargets(cfg); err != nil {
66+
return "", "", err
67+
}
68+
if err := mgrconfig.Complete(cfg); err != nil {
69+
return "", "", err
70+
}
71+
env, err := instance.NewEnv(cfg, nil, nil)
72+
if err != nil {
73+
return "", "", err
74+
}
75+
results, err := env.Test(1, nil, nil, []byte(reproC))
76+
if err != nil {
77+
return "", "", err
78+
}
79+
if results[0].Error != nil {
80+
if crashErr := new(instance.CrashError); errors.As(results[0].Error, &crashErr) {
81+
return string(crashErr.Report.Report), "", nil
82+
} else {
83+
return "", results[0].Error.Error(), nil
84+
}
85+
}
86+
return "", "", nil
87+
}
88+
4589
func reproduce(ctx *aflow.Context, args reproduceArgs) (reproduceResult, error) {
4690
if args.Type != "qemu" {
4791
// Since we use injected kernel boot, and don't build full disk image.
@@ -61,50 +105,12 @@ func reproduce(ctx *aflow.Context, args reproduceArgs) (reproduceResult, error)
61105
}
62106
cached, err := aflow.CacheObject(ctx, "repro", desc, func() (Cached, error) {
63107
var res Cached
64-
var vmConfig map[string]any
65-
if err := json.Unmarshal(args.VM, &vmConfig); err != nil {
66-
return res, fmt.Errorf("failed to parse VM config: %w", err)
67-
}
68-
vmConfig["kernel"] = filepath.Join(args.KernelObj, filepath.FromSlash(build.LinuxKernelImage(targets.AMD64)))
69-
vmCfg, err := json.Marshal(vmConfig)
70-
if err != nil {
71-
return res, fmt.Errorf("failed to serialize VM config: %w", err)
72-
}
73108
workdir, err := ctx.TempDir()
74109
if err != nil {
75110
return res, err
76111
}
77-
cfg := mgrconfig.DefaultValues()
78-
cfg.RawTarget = "linux/amd64"
79-
cfg.Workdir = workdir
80-
cfg.Syzkaller = args.Syzkaller
81-
cfg.KernelObj = args.KernelObj
82-
cfg.KernelSrc = args.KernelSrc
83-
cfg.Image = args.Image
84-
cfg.Type = args.Type
85-
cfg.VM = vmCfg
86-
if err := mgrconfig.SetTargets(cfg); err != nil {
87-
return res, err
88-
}
89-
if err := mgrconfig.Complete(cfg); err != nil {
90-
return res, err
91-
}
92-
env, err := instance.NewEnv(cfg, nil, nil)
93-
if err != nil {
94-
return res, err
95-
}
96-
results, err := env.Test(1, nil, nil, []byte(args.ReproC))
97-
if err != nil {
98-
return res, err
99-
}
100-
if results[0].Error != nil {
101-
if crashErr := new(instance.CrashError); errors.As(results[0].Error, &crashErr) {
102-
res.Report = string(crashErr.Report.Report)
103-
} else {
104-
res.Error = results[0].Error.Error()
105-
}
106-
}
107-
return res, nil
112+
res.Error, res.Report, err = ReproduceCrash(args.Syzkaller, args.KernelObj, args.KernelSrc, args.Image, args.Type, args.ReproC, workdir, args.VM)
113+
return res, err
108114
})
109115
if err != nil {
110116
return reproduceResult{}, err

pkg/aflow/action/crash/test.go

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,13 @@ package crash
55

66
import (
77
"encoding/json"
8+
"errors"
9+
"fmt"
10+
"time"
811

912
"github.com/google/syzkaller/pkg/aflow"
13+
"github.com/google/syzkaller/pkg/aflow/action/kernel"
14+
"github.com/google/syzkaller/pkg/osutil"
1015
)
1116

1217
// TestPatch action does an in-tree kernel build in KernelScratchSrc dir,
@@ -35,6 +40,57 @@ type testResult struct {
3540
TestError string
3641
}
3742

43+
func undoChanges(repo string) error {
44+
_, err := osutil.RunCmd(time.Minute, repo, "git", "checkout", "--", ".")
45+
if err != nil {
46+
return err
47+
}
48+
// We do not use -fdx to keep object files around and make the next tool call faster
49+
_, err = osutil.RunCmd(time.Minute, repo, "git", "clean", "-fd")
50+
return err
51+
}
52+
53+
func currentDiff(repo string) (byte[], error) {
54+
// Mark the "intent to add" on all files so git diff also shows currently untracked files
55+
_, err := osutil.RunCmd(time.Minute, repo, "git", "add", "-N", ".")
56+
if err != nil {
57+
return "", err
58+
}
59+
diff, err := osutil.RunCmd(time.Minute, repo, "git", "diff")
60+
if err != nil {
61+
return "", aflow.FlowError(fmt.Errorf("capturing diff failed: %w\n%s", err, diff))
62+
}
63+
return diff, nil
64+
}
65+
3866
func testPatch(ctx *aflow.Context, args testArgs) (testResult, error) {
39-
return testResult{}, nil
67+
res := testResult{}
68+
defer undoChanges(args.KernelScratchSrc)
69+
70+
diff, err := currentDiff(args.KernelScratchSrc)
71+
if err != nil {
72+
return res, err
73+
}
74+
res.PatchDiff = diff
75+
76+
if args.Type != "qemu" {
77+
return res, errors.New("only qemu VM type is supported")
78+
}
79+
80+
if err := kernel.BuildKernel(args.KernelScratchSrc, args.KernelScratchSrc, args.KernelConfig, false); err != nil {
81+
res.TestError = fmt.Sprintf("Building the kernel failed with %v", err)
82+
return res, nil
83+
}
84+
85+
workdir, err := ctx.TempDir()
86+
if err != nil {
87+
return res, err
88+
}
89+
errorLog, reportLog, err := ReproduceCrash(args.Syzkaller, args.KernelScratchSrc, args.KernelScratchSrc, args.Image, args.Type, args.ReproC, workdir, args.VM)
90+
if errorLog != "" {
91+
res.TestError = errorLog
92+
} else {
93+
res.TestError = reportLog
94+
}
95+
return res, err
4096
}

pkg/aflow/action/kernel/build.go

Lines changed: 39 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -34,42 +34,49 @@ type buildResult struct {
3434
KernelObj string // Directory with build artifacts.
3535
}
3636

37-
func buildKernel(ctx *aflow.Context, args buildArgs) (buildResult, error) {
38-
desc := fmt.Sprintf("kernel commit %v, kernel config hash %v",
39-
args.KernelCommit, hash.String(args.KernelConfig))
40-
dir, err := ctx.Cache("build", desc, func(dir string) error {
41-
if err := osutil.WriteFile(filepath.Join(dir, ".config"), []byte(args.KernelConfig)); err != nil {
37+
func BuildKernel(buildDir, srcDir, cfg string, cleanup bool) error {
38+
if err := osutil.WriteFile(filepath.Join(buildDir, ".config"), []byte(cfg)); err != nil {
39+
return err
40+
}
41+
target := targets.List[targets.Linux][targets.AMD64]
42+
image := filepath.FromSlash(build.LinuxKernelImage(targets.AMD64))
43+
makeArgs := build.LinuxMakeArgs(target, targets.DefaultLLVMCompiler, targets.DefaultLLVMLinker,
44+
"ccache", buildDir, runtime.NumCPU())
45+
const compileCommands = "compile_commands.json"
46+
makeArgs = append(makeArgs, "-s", path.Base(image), compileCommands)
47+
if out, err := osutil.RunCmd(time.Hour, srcDir, "make", makeArgs...); err != nil {
48+
return aflow.FlowError(fmt.Errorf("make failed: %w\n%s", err, out))
49+
}
50+
if !cleanup {
51+
return nil
52+
}
53+
// Remove main intermediate build files, we don't need them anymore
54+
// and they take lots of space. But keep generated source files.
55+
keepFiles := map[string]bool{
56+
image: true,
57+
target.KernelObject: true,
58+
compileCommands: true,
59+
}
60+
return filepath.WalkDir(buildDir, func(path string, d fs.DirEntry, err error) error {
61+
if err != nil {
4262
return err
4363
}
44-
target := targets.List[targets.Linux][targets.AMD64]
45-
image := filepath.FromSlash(build.LinuxKernelImage(targets.AMD64))
46-
makeArgs := build.LinuxMakeArgs(target, targets.DefaultLLVMCompiler, targets.DefaultLLVMLinker,
47-
"ccache", dir, runtime.NumCPU())
48-
const compileCommands = "compile_commands.json"
49-
makeArgs = append(makeArgs, "-s", path.Base(image), compileCommands)
50-
if out, err := osutil.RunCmd(time.Hour, args.KernelSrc, "make", makeArgs...); err != nil {
51-
return aflow.FlowError(fmt.Errorf("make failed: %w\n%s", err, out))
64+
relative, err := filepath.Rel(buildDir, path)
65+
if err != nil {
66+
return err
5267
}
53-
// Remove main intermediate build files, we don't need them anymore
54-
// and they take lots of space. But keep generated source files.
55-
keepFiles := map[string]bool{
56-
image: true,
57-
target.KernelObject: true,
58-
compileCommands: true,
68+
if d.IsDir() || keepFiles[relative] || codesearch.IsSourceFile(relative) {
69+
return nil
5970
}
60-
return filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
61-
if err != nil {
62-
return err
63-
}
64-
relative, err := filepath.Rel(dir, path)
65-
if err != nil {
66-
return err
67-
}
68-
if d.IsDir() || keepFiles[relative] || codesearch.IsSourceFile(relative) {
69-
return nil
70-
}
71-
return os.Remove(path)
72-
})
71+
return os.Remove(path)
72+
})
73+
}
74+
75+
func buildKernel(ctx *aflow.Context, args buildArgs) (buildResult, error) {
76+
desc := fmt.Sprintf("kernel commit %v, kernel config hash %v",
77+
args.KernelCommit, hash.String(args.KernelConfig))
78+
dir, err := ctx.Cache("build", desc, func(dir string) error {
79+
return BuildKernel(dir, args.KernelSrc, args.KernelConfig, true)
7380
})
7481
return buildResult{KernelObj: dir}, err
7582
}

0 commit comments

Comments
 (0)