Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 50 additions & 44 deletions pkg/aflow/action/crash/reproduce.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
72 changes: 71 additions & 1 deletion pkg/aflow/action/crash/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
}
71 changes: 39 additions & 32 deletions pkg/aflow/action/kernel/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Loading