aboutsummaryrefslogtreecommitdiffstats
path: root/pkg/aflow/action/crash/reproduce.go
blob: 15ada378d7098e7152429293aaeb0c37b14fdb50 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
// 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
	}
	const noCrash = "reproducer did not crash"
	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(noCrash)
		}
		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 {
		err := errors.New(string(data))
		if err.Error() == noCrash {
			err = aflow.FlowError(err)
		}
		return reproduceResult{}, err
	}
	data, err := os.ReadFile(filepath.Join(dir, "report"))
	return reproduceResult{
		CrashReport: string(data),
	}, err
}