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
|
// 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
}
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("reproducer did not crash")
}
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 {
return reproduceResult{}, errors.New(string(data))
}
data, err := os.ReadFile(filepath.Join(dir, "report"))
return reproduceResult{
CrashReport: string(data),
}, err
}
|