• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2015 syzkaller project authors. All rights reserved.
2// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3
4package mgrconfig
5
6import (
7	"encoding/json"
8	"fmt"
9	"os"
10	"path/filepath"
11	"strings"
12
13	"github.com/google/syzkaller/pkg/config"
14	"github.com/google/syzkaller/pkg/osutil"
15	"github.com/google/syzkaller/prog"
16	_ "github.com/google/syzkaller/sys" // most mgrconfig users want targets too
17	"github.com/google/syzkaller/sys/targets"
18)
19
20type Config struct {
21	// Instance name (used for identification and as GCE instance prefix).
22	Name string `json:"name"`
23	// Target OS/arch, e.g. "linux/arm64" or "linux/amd64/386" (amd64 OS with 386 test process).
24	Target string `json:"target"`
25	// TCP address to serve HTTP stats page (e.g. "localhost:50000").
26	HTTP string `json:"http"`
27	// TCP address to serve RPC for fuzzer processes (optional).
28	RPC     string `json:"rpc"`
29	Workdir string `json:"workdir"`
30	// Directory with kernel object files.
31	KernelObj string `json:"kernel_obj"`
32	// Kernel source directory (if not set defaults to KernelObj).
33	KernelSrc string `json:"kernel_src"`
34	// Arbitrary optional tag that is saved along with crash reports (e.g. branch/commit).
35	Tag string `json:"tag"`
36	// Linux image for VMs.
37	Image string `json:"image"`
38	// SSH key for the image (may be empty for some VM types).
39	SSHKey string `json:"sshkey"`
40	// SSH user ("root" by default).
41	SSHUser string `json:"ssh_user"`
42
43	HubClient string `json:"hub_client"`
44	HubAddr   string `json:"hub_addr"`
45	HubKey    string `json:"hub_key"`
46
47	// syz-manager will send crash emails to this list of emails using mailx (optional).
48	EmailAddrs []string `json:"email_addrs"`
49
50	DashboardClient string `json:"dashboard_client"`
51	DashboardAddr   string `json:"dashboard_addr"`
52	DashboardKey    string `json:"dashboard_key"`
53
54	// Path to syzkaller checkout (syz-manager will look for binaries in bin subdir).
55	Syzkaller string `json:"syzkaller"`
56	// Number of parallel processes inside of every VM.
57	Procs int `json:"procs"`
58
59	// Type of sandbox to use during fuzzing:
60	// "none": don't do anything special (has false positives, e.g. due to killing init), default
61	// "setuid": impersonate into user nobody (65534)
62	// "namespace": create a new namespace for fuzzer using CLONE_NEWNS/CLONE_NEWNET/CLONE_NEWPID/etc,
63	//	requires building kernel with CONFIG_NAMESPACES, CONFIG_UTS_NS, CONFIG_USER_NS,
64	//	CONFIG_PID_NS and CONFIG_NET_NS.
65	Sandbox string `json:"sandbox"`
66
67	// Use KCOV coverage (default: true).
68	Cover bool `json:"cover"`
69	// Reproduce, localize and minimize crashers (default: true).
70	Reproduce bool `json:"reproduce"`
71
72	EnabledSyscalls  []string `json:"enable_syscalls"`
73	DisabledSyscalls []string `json:"disable_syscalls"`
74	// Don't save reports matching these regexps, but reboot VM after them,
75	// matched against whole report output.
76	Suppressions []string `json:"suppressions"`
77	// Completely ignore reports matching these regexps (don't save nor reboot),
78	// must match the first line of crash message.
79	Ignores []string `json:"ignores"`
80
81	// VM type (qemu, gce, android, isolated, etc).
82	Type string `json:"type"`
83	// VM-type-specific config.
84	VM json.RawMessage `json:"vm"`
85
86	// Implementation details beyond this point.
87	// Parsed Target:
88	TargetOS     string `json:"-"`
89	TargetArch   string `json:"-"`
90	TargetVMArch string `json:"-"`
91	// Syzkaller binaries that we are going to use:
92	SyzFuzzerBin   string `json:"-"`
93	SyzExecprogBin string `json:"-"`
94	SyzExecutorBin string `json:"-"`
95}
96
97func LoadData(data []byte) (*Config, error) {
98	cfg, err := LoadPartialData(data)
99	if err != nil {
100		return nil, err
101	}
102	if err := Complete(cfg); err != nil {
103		return nil, err
104	}
105	return cfg, nil
106}
107
108func LoadFile(filename string) (*Config, error) {
109	cfg, err := LoadPartialFile(filename)
110	if err != nil {
111		return nil, err
112	}
113	if err := Complete(cfg); err != nil {
114		return nil, err
115	}
116	return cfg, nil
117}
118
119func LoadPartialData(data []byte) (*Config, error) {
120	cfg := defaultValues()
121	if err := config.LoadData(data, cfg); err != nil {
122		return nil, err
123	}
124	return loadPartial(cfg)
125}
126
127func LoadPartialFile(filename string) (*Config, error) {
128	cfg := defaultValues()
129	if err := config.LoadFile(filename, cfg); err != nil {
130		return nil, err
131	}
132	return loadPartial(cfg)
133}
134
135func defaultValues() *Config {
136	return &Config{
137		SSHUser:   "root",
138		Cover:     true,
139		Reproduce: true,
140		Sandbox:   "none",
141		RPC:       ":0",
142		Procs:     1,
143	}
144}
145
146func loadPartial(cfg *Config) (*Config, error) {
147	var err error
148	cfg.TargetOS, cfg.TargetVMArch, cfg.TargetArch, err = splitTarget(cfg.Target)
149	if err != nil {
150		return nil, err
151	}
152	return cfg, nil
153}
154
155func Complete(cfg *Config) error {
156	if cfg.TargetOS == "" || cfg.TargetVMArch == "" || cfg.TargetArch == "" {
157		return fmt.Errorf("target parameters are not filled in")
158	}
159	if cfg.Workdir == "" {
160		return fmt.Errorf("config param workdir is empty")
161	}
162	cfg.Workdir = osutil.Abs(cfg.Workdir)
163	if cfg.Syzkaller == "" {
164		return fmt.Errorf("config param syzkaller is empty")
165	}
166	if err := completeBinaries(cfg); err != nil {
167		return err
168	}
169	if cfg.HTTP == "" {
170		return fmt.Errorf("config param http is empty")
171	}
172	if cfg.Type == "" {
173		return fmt.Errorf("config param type is empty")
174	}
175	if cfg.Procs < 1 || cfg.Procs > 32 {
176		return fmt.Errorf("bad config param procs: '%v', want [1, 32]", cfg.Procs)
177	}
178	switch cfg.Sandbox {
179	case "none", "setuid", "namespace":
180	default:
181		return fmt.Errorf("config param sandbox must contain one of none/setuid/namespace")
182	}
183	if err := checkSSHParams(cfg); err != nil {
184		return err
185	}
186
187	cfg.KernelObj = osutil.Abs(cfg.KernelObj)
188	if cfg.KernelSrc == "" {
189		cfg.KernelSrc = cfg.KernelObj // assume in-tree build by default
190	}
191	cfg.KernelSrc = osutil.Abs(cfg.KernelSrc)
192	if cfg.HubClient != "" && (cfg.Name == "" || cfg.HubAddr == "" || cfg.HubKey == "") {
193		return fmt.Errorf("hub_client is set, but name/hub_addr/hub_key is empty")
194	}
195	if cfg.DashboardClient != "" && (cfg.Name == "" ||
196		cfg.DashboardAddr == "" ||
197		cfg.DashboardKey == "") {
198		return fmt.Errorf("dashboard_client is set, but name/dashboard_addr/dashboard_key is empty")
199	}
200
201	return nil
202}
203
204func checkSSHParams(cfg *Config) error {
205	if cfg.SSHUser == "" {
206		return fmt.Errorf("bad config syzkaller param: ssh user is empty")
207	}
208	if cfg.SSHKey == "" {
209		return nil
210	}
211	info, err := os.Stat(cfg.SSHKey)
212	if err != nil {
213		return err
214	}
215	if info.Mode()&0077 != 0 {
216		return fmt.Errorf("sshkey %v is unprotected, ssh will reject it, do chmod 0600", cfg.SSHKey)
217	}
218	return nil
219}
220
221func completeBinaries(cfg *Config) error {
222	sysTarget := targets.Get(cfg.TargetOS, cfg.TargetArch)
223	if sysTarget == nil {
224		return fmt.Errorf("unsupported OS/arch: %v/%v", cfg.TargetOS, cfg.TargetArch)
225	}
226	cfg.Syzkaller = osutil.Abs(cfg.Syzkaller)
227	exe := sysTarget.ExeExtension
228	targetBin := func(name, arch string) string {
229		return filepath.Join(cfg.Syzkaller, "bin", cfg.TargetOS+"_"+arch, name+exe)
230	}
231	cfg.SyzFuzzerBin = targetBin("syz-fuzzer", cfg.TargetVMArch)
232	cfg.SyzExecprogBin = targetBin("syz-execprog", cfg.TargetVMArch)
233	cfg.SyzExecutorBin = targetBin("syz-executor", cfg.TargetArch)
234	if !osutil.IsExist(cfg.SyzFuzzerBin) {
235		return fmt.Errorf("bad config syzkaller param: can't find %v", cfg.SyzFuzzerBin)
236	}
237	if !osutil.IsExist(cfg.SyzExecprogBin) {
238		return fmt.Errorf("bad config syzkaller param: can't find %v", cfg.SyzExecprogBin)
239	}
240	if !osutil.IsExist(cfg.SyzExecutorBin) {
241		return fmt.Errorf("bad config syzkaller param: can't find %v", cfg.SyzExecutorBin)
242	}
243	return nil
244}
245
246func splitTarget(target string) (string, string, string, error) {
247	if target == "" {
248		return "", "", "", fmt.Errorf("target is empty")
249	}
250	targetParts := strings.Split(target, "/")
251	if len(targetParts) != 2 && len(targetParts) != 3 {
252		return "", "", "", fmt.Errorf("bad config param target")
253	}
254	os := targetParts[0]
255	vmarch := targetParts[1]
256	arch := targetParts[1]
257	if len(targetParts) == 3 {
258		arch = targetParts[2]
259	}
260	return os, vmarch, arch, nil
261}
262
263func ParseEnabledSyscalls(target *prog.Target, enabled, disabled []string) (map[int]bool, error) {
264	syscalls := make(map[int]bool)
265	if len(enabled) != 0 {
266		for _, c := range enabled {
267			n := 0
268			for _, call := range target.Syscalls {
269				if matchSyscall(call.Name, c) {
270					syscalls[call.ID] = true
271					n++
272				}
273			}
274			if n == 0 {
275				return nil, fmt.Errorf("unknown enabled syscall: %v", c)
276			}
277		}
278	} else {
279		for _, call := range target.Syscalls {
280			syscalls[call.ID] = true
281		}
282	}
283	for _, c := range disabled {
284		n := 0
285		for _, call := range target.Syscalls {
286			if matchSyscall(call.Name, c) {
287				delete(syscalls, call.ID)
288				n++
289			}
290		}
291		if n == 0 {
292			return nil, fmt.Errorf("unknown disabled syscall: %v", c)
293		}
294	}
295	if len(syscalls) == 0 {
296		return nil, fmt.Errorf("all syscalls are disabled by disable_syscalls in config")
297	}
298	return syscalls, nil
299}
300
301func matchSyscall(name, pattern string) bool {
302	if pattern == name || strings.HasPrefix(name, pattern+"$") {
303		return true
304	}
305	if len(pattern) > 1 && pattern[len(pattern)-1] == '*' &&
306		strings.HasPrefix(name, pattern[:len(pattern)-1]) {
307		return true
308	}
309	return false
310}
311