From 84eb5fd389bc4ae247048d1f20ec9577935f4f04 Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Thu, 1 Jun 2017 11:17:54 +0200 Subject: config: split and refactor Introduce generic config.Load function that can be reused across multiple programs (syz-manager, syz-gce, etc). Move the generic config functionality to pkg/config package. The idea is to move all helper (non-main) packages to pkg/ dir, because we have more and more of them and they pollute the top dir. Move the syz-manager config parts into syz-manager/config package. --- config/config.go | 386 ------------------------------------------- config/config_test.go | 24 --- pkg/config/config.go | 61 +++++++ pkg/config/config_test.go | 65 ++++++++ repro/repro.go | 2 +- syz-gce/syz-gce.go | 26 +-- syz-manager/config/config.go | 348 ++++++++++++++++++++++++++++++++++++++ syz-manager/manager.go | 2 +- tools/syz-crush/crush.go | 2 +- tools/syz-repro/repro.go | 2 +- 10 files changed, 486 insertions(+), 432 deletions(-) delete mode 100644 config/config.go delete mode 100644 config/config_test.go create mode 100644 pkg/config/config.go create mode 100644 pkg/config/config_test.go create mode 100644 syz-manager/config/config.go diff --git a/config/config.go b/config/config.go deleted file mode 100644 index 99314249b..000000000 --- a/config/config.go +++ /dev/null @@ -1,386 +0,0 @@ -// Copyright 2015 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 config - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "os" - "path/filepath" - "reflect" - "regexp" - "strings" - - "github.com/google/syzkaller/fileutil" - "github.com/google/syzkaller/sys" - "github.com/google/syzkaller/vm" -) - -type Config struct { - Name string // Instance name (used for identification and as GCE instance prefix) - Http string // TCP address to serve HTTP stats page (e.g. "localhost:50000") - Rpc string // TCP address to serve RPC for fuzzer processes (optional, only useful for type "none") - Workdir string - Vmlinux string - Kernel string // e.g. arch/x86/boot/bzImage - Tag string // arbitrary optional tag that is saved along with crash reports (e.g. kernel branch/commit) - Cmdline string // kernel command line - Image string // linux image for VMs - Initrd string // linux initial ramdisk. (optional) - Cpu int // number of VM CPUs - Mem int // amount of VM memory in MBs - Sshkey string // root ssh key for the image - Bin string // qemu/lkvm binary name - Bin_Args string // additional command line arguments for qemu/lkvm binary - Debug bool // dump all VM output to console - Output string // one of stdout/dmesg/file (useful only for local VM) - - Hub_Addr string - Hub_Key string - - Dashboard_Addr string - Dashboard_Key string - - Syzkaller string // path to syzkaller checkout (syz-manager will look for binaries in bin subdir) - Type string // VM type (qemu, kvm, local) - Count int // number of VMs (don't secify for adb, instead specify devices) - Devices []string // device IDs for adb - Procs int // number of parallel processes inside of every VM - - Sandbox string // type of sandbox to use during fuzzing: - // "none": don't do anything special (has false positives, e.g. due to killing init) - // "setuid": impersonate into user nobody (65534), default - // "namespace": create a new namespace for fuzzer using CLONE_NEWNS/CLONE_NEWNET/CLONE_NEWPID/etc, - // requires building kernel with CONFIG_NAMESPACES, CONFIG_UTS_NS, CONFIG_USER_NS, CONFIG_PID_NS and CONFIG_NET_NS. - - Machine_Type string // GCE machine type (e.g. "n1-highcpu-2") - - Odroid_Host_Addr string // ip address of the host machine - Odroid_Slave_Addr string // ip address of the Odroid board - Odroid_Console string // console device name (e.g. "/dev/ttyUSB0") - Odroid_Hub_Bus int // host USB bus number for the USB hub - Odroid_Hub_Device int // host USB device number for the USB hub - Odroid_Hub_Port int // port on the USB hub to which Odroid is connected - - Cover bool // use kcov coverage (default: true) - Leak bool // do memory leak checking - Reproduce bool // reproduce, localize and minimize crashers (on by default) - - Enable_Syscalls []string - Disable_Syscalls []string - Suppressions []string // don't save reports matching these regexps, but reboot VM after them - Ignores []string // completely ignore reports matching these regexps (don't save nor reboot) - - // Implementation details beyond this point. - ParsedSuppressions []*regexp.Regexp `json:"-"` - ParsedIgnores []*regexp.Regexp `json:"-"` -} - -func Parse(filename string) (*Config, map[int]bool, error) { - if filename == "" { - return nil, nil, fmt.Errorf("supply config in -config flag") - } - data, err := ioutil.ReadFile(filename) - if err != nil { - return nil, nil, fmt.Errorf("failed to read config file: %v", err) - } - return parse(data) -} - -func parse(data []byte) (*Config, map[int]bool, error) { - err := checkUnknownFields(data) - if err != nil { - return nil, nil, err - } - cfg := new(Config) - cfg.Cover = true - cfg.Reproduce = true - cfg.Sandbox = "setuid" - if err := json.Unmarshal(data, cfg); err != nil { - return nil, nil, fmt.Errorf("failed to parse config file: %v", err) - } - if _, err := os.Stat(filepath.Join(cfg.Syzkaller, "bin/syz-fuzzer")); err != nil { - return nil, nil, fmt.Errorf("bad config syzkaller param: can't find bin/syz-fuzzer") - } - if _, err := os.Stat(filepath.Join(cfg.Syzkaller, "bin/syz-executor")); err != nil { - return nil, nil, fmt.Errorf("bad config syzkaller param: can't find bin/syz-executor") - } - if cfg.Http == "" { - return nil, nil, fmt.Errorf("config param http is empty") - } - if cfg.Workdir == "" { - return nil, nil, fmt.Errorf("config param workdir is empty") - } - if cfg.Vmlinux == "" { - return nil, nil, fmt.Errorf("config param vmlinux is empty") - } - if cfg.Type == "" { - return nil, nil, fmt.Errorf("config param type is empty") - } - switch cfg.Type { - case "none": - if cfg.Count != 0 { - return nil, nil, fmt.Errorf("invalid config param count: %v, type \"none\" does not support param count", cfg.Count) - } - if cfg.Rpc == "" { - return nil, nil, fmt.Errorf("config param rpc is empty (required for type \"none\")") - } - if len(cfg.Devices) != 0 { - return nil, nil, fmt.Errorf("type %v does not support devices param", cfg.Type) - } - case "adb": - if cfg.Count != 0 { - return nil, nil, fmt.Errorf("don't specify count for adb, instead specify devices") - } - if len(cfg.Devices) == 0 { - return nil, nil, fmt.Errorf("specify at least 1 adb device") - } - cfg.Count = len(cfg.Devices) - case "odroid": - if cfg.Count != 1 { - return nil, nil, fmt.Errorf("no support for multiple Odroid devices yet, count should be 1") - } - if cfg.Odroid_Host_Addr == "" { - return nil, nil, fmt.Errorf("config param odroid_host_addr is empty") - } - if cfg.Odroid_Slave_Addr == "" { - return nil, nil, fmt.Errorf("config param odroid_slave_addr is empty") - } - if cfg.Odroid_Console == "" { - return nil, nil, fmt.Errorf("config param odroid_console is empty") - } - if cfg.Odroid_Hub_Bus == 0 { - return nil, nil, fmt.Errorf("config param odroid_hub_bus is empty") - } - if cfg.Odroid_Hub_Device == 0 { - return nil, nil, fmt.Errorf("config param odroid_hub_device is empty") - } - if cfg.Odroid_Hub_Port == 0 { - return nil, nil, fmt.Errorf("config param odroid_hub_port is empty") - } - case "gce": - if cfg.Machine_Type == "" { - return nil, nil, fmt.Errorf("machine_type parameter is empty (required for gce)") - } - fallthrough - default: - if cfg.Count <= 0 || cfg.Count > 1000 { - return nil, nil, fmt.Errorf("invalid config param count: %v, want (1, 1000]", cfg.Count) - } - if len(cfg.Devices) != 0 { - return nil, nil, fmt.Errorf("type %v does not support devices param", cfg.Type) - } - } - if cfg.Rpc == "" { - cfg.Rpc = "localhost:0" - } - if cfg.Procs <= 0 { - cfg.Procs = 1 - } - if cfg.Procs > 32 { - return nil, nil, fmt.Errorf("config param procs has higher value '%v' then the max supported 32", cfg.Procs) - } - if cfg.Output == "" { - if cfg.Type == "local" { - cfg.Output = "none" - } else { - cfg.Output = "stdout" - } - } - switch cfg.Output { - case "none", "stdout", "dmesg", "file": - default: - return nil, nil, fmt.Errorf("config param output must contain one of none/stdout/dmesg/file") - } - switch cfg.Sandbox { - case "none", "setuid", "namespace": - default: - return nil, nil, fmt.Errorf("config param sandbox must contain one of none/setuid/namespace") - } - - wd, err := os.Getwd() - if err != nil { - return nil, nil, fmt.Errorf("failed to get wd: %v", err) - } - abs := func(path string) string { - if path != "" && !filepath.IsAbs(path) { - path = filepath.Join(wd, path) - } - return path - } - cfg.Workdir = abs(cfg.Workdir) - cfg.Kernel = abs(cfg.Kernel) - cfg.Vmlinux = abs(cfg.Vmlinux) - cfg.Syzkaller = abs(cfg.Syzkaller) - cfg.Initrd = abs(cfg.Initrd) - cfg.Sshkey = abs(cfg.Sshkey) - cfg.Bin = abs(cfg.Bin) - - syscalls, err := parseSyscalls(cfg) - if err != nil { - return nil, nil, err - } - - if err := parseSuppressions(cfg); err != nil { - return nil, nil, err - } - - if cfg.Hub_Addr != "" { - if cfg.Name == "" { - return nil, nil, fmt.Errorf("hub_addr is set, but name is empty") - } - if cfg.Hub_Key == "" { - return nil, nil, fmt.Errorf("hub_addr is set, but hub_key is empty") - } - } - if cfg.Dashboard_Addr != "" { - if cfg.Name == "" { - return nil, nil, fmt.Errorf("dashboard_addr is set, but name is empty") - } - if cfg.Dashboard_Key == "" { - return nil, nil, fmt.Errorf("dashboard_addr is set, but dashboard_key is empty") - } - } - - return cfg, syscalls, nil -} - -func parseSyscalls(cfg *Config) (map[int]bool, error) { - match := func(call *sys.Call, str string) bool { - if str == call.CallName || str == call.Name { - return true - } - if len(str) > 1 && str[len(str)-1] == '*' && strings.HasPrefix(call.Name, str[:len(str)-1]) { - return true - } - return false - } - - syscalls := make(map[int]bool) - if len(cfg.Enable_Syscalls) != 0 { - for _, c := range cfg.Enable_Syscalls { - n := 0 - for _, call := range sys.Calls { - if match(call, c) { - syscalls[call.ID] = true - n++ - } - } - if n == 0 { - return nil, fmt.Errorf("unknown enabled syscall: %v", c) - } - } - } else { - for _, call := range sys.Calls { - syscalls[call.ID] = true - } - } - for _, c := range cfg.Disable_Syscalls { - n := 0 - for _, call := range sys.Calls { - if match(call, c) { - delete(syscalls, call.ID) - n++ - } - } - if n == 0 { - return nil, fmt.Errorf("unknown disabled syscall: %v", c) - } - } - // mmap is used to allocate memory. - syscalls[sys.CallMap["mmap"].ID] = true - - return syscalls, nil -} - -func parseSuppressions(cfg *Config) error { - // Add some builtin suppressions. - supp := append(cfg.Suppressions, []string{ - "panic: failed to start executor binary", - "panic: executor failed: pthread_create failed", - "panic: failed to create temp dir", - "fatal error: runtime: out of memory", - "fatal error: runtime: cannot allocate memory", - "fatal error: unexpected signal during runtime execution", // presubmably OOM turned into SIGBUS - "signal SIGBUS: bus error", // presubmably OOM turned into SIGBUS - "Out of memory: Kill process .* \\(syz-fuzzer\\)", - "lowmemorykiller: Killing 'syz-fuzzer'", - //"INFO: lockdep is turned off", // printed by some sysrq that dumps scheduler state, but also on all lockdep reports - }...) - for _, s := range supp { - re, err := regexp.Compile(s) - if err != nil { - return fmt.Errorf("failed to compile suppression '%v': %v", s, err) - } - cfg.ParsedSuppressions = append(cfg.ParsedSuppressions, re) - } - for _, ignore := range cfg.Ignores { - re, err := regexp.Compile(ignore) - if err != nil { - return fmt.Errorf("failed to compile ignore '%v': %v", ignore, err) - } - cfg.ParsedIgnores = append(cfg.ParsedIgnores, re) - } - return nil -} - -func CreateVMConfig(cfg *Config, index int) (*vm.Config, error) { - if index < 0 || index >= cfg.Count { - return nil, fmt.Errorf("invalid VM index %v (count %v)", index, cfg.Count) - } - workdir, err := fileutil.ProcessTempDir(cfg.Workdir) - if err != nil { - return nil, fmt.Errorf("failed to create instance temp dir: %v", err) - } - vmCfg := &vm.Config{ - Name: fmt.Sprintf("%v-%v-%v", cfg.Type, cfg.Name, index), - Index: index, - Workdir: workdir, - Bin: cfg.Bin, - BinArgs: cfg.Bin_Args, - Kernel: cfg.Kernel, - Cmdline: cfg.Cmdline, - Image: cfg.Image, - Initrd: cfg.Initrd, - Sshkey: cfg.Sshkey, - Executor: filepath.Join(cfg.Syzkaller, "bin", "syz-executor"), - Cpu: cfg.Cpu, - Mem: cfg.Mem, - Debug: cfg.Debug, - MachineType: cfg.Machine_Type, - OdroidHostAddr: cfg.Odroid_Host_Addr, - OdroidSlaveAddr: cfg.Odroid_Slave_Addr, - OdroidConsole: cfg.Odroid_Console, - OdroidHubBus: cfg.Odroid_Hub_Bus, - OdroidHubDevice: cfg.Odroid_Hub_Device, - OdroidHubPort: cfg.Odroid_Hub_Port, - } - if len(cfg.Devices) != 0 { - vmCfg.Device = cfg.Devices[index] - } - return vmCfg, nil -} - -func checkUnknownFields(data []byte) error { - structType := reflect.ValueOf(Config{}).Type() - fields := make(map[string]bool) - for i := 0; i < structType.NumField(); i++ { - field := structType.Field(i) - if field.Tag.Get("json") == "-" { - continue - } - fields[strings.ToLower(field.Name)] = true - } - f := make(map[string]interface{}) - if err := json.Unmarshal(data, &f); err != nil { - return fmt.Errorf("failed to parse config file: %v", err) - } - for k := range f { - if !fields[strings.ToLower(k)] { - return fmt.Errorf("unknown field '%v' in config", k) - } - } - return nil -} diff --git a/config/config_test.go b/config/config_test.go deleted file mode 100644 index 67bcd5059..000000000 --- a/config/config_test.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2016 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 config - -import ( - "testing" -) - -func TestUnknown(t *testing.T) { - data := `{"foo": "bar"}` - _, _, err := parse([]byte(data)) - if err == nil || err.Error() != "unknown field 'foo' in config" { - t.Fatalf("unknown field is not detected (%v)", err) - } -} - -func TestReflectUnknown(t *testing.T) { - data := `{"http": "localhost:56741","ParsedIgnores": [], "Odroid_Hub_Bus": "bus"}` - _, _, err := parse([]byte(data)) - if err == nil || err.Error() != "unknown field 'ParsedIgnores' in config" { - t.Fatalf("unknown field is not detected (%v)", err) - } -} diff --git a/pkg/config/config.go b/pkg/config/config.go new file mode 100644 index 000000000..feb031c71 --- /dev/null +++ b/pkg/config/config.go @@ -0,0 +1,61 @@ +// Copyright 2017 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 config + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "reflect" + "strings" +) + +func Load(filename string, cfg interface{}) error { + if filename == "" { + return fmt.Errorf("no config file specified") + } + data, err := ioutil.ReadFile(filename) + if err != nil { + return fmt.Errorf("failed to read config file: %v", err) + } + return load(data, cfg) +} + +func load(data []byte, cfg interface{}) error { + if err := checkUnknownFields(data, reflect.ValueOf(cfg).Type()); err != nil { + return err + } + if err := json.Unmarshal(data, cfg); err != nil { + return fmt.Errorf("failed to parse config file: %v", err) + } + return nil +} + +func checkUnknownFields(data []byte, typ reflect.Type) error { + if typ.Kind() != reflect.Ptr { + return fmt.Errorf("config type is not pointer to struct") + } + typ = typ.Elem() + if typ.Kind() != reflect.Struct { + return fmt.Errorf("config type is not pointer to struct") + } + fields := make(map[string]bool) + for i := 0; i < typ.NumField(); i++ { + field := typ.Field(i) + if field.Tag.Get("json") == "-" { + continue + } + fields[strings.ToLower(field.Name)] = true + } + f := make(map[string]interface{}) + if err := json.Unmarshal(data, &f); err != nil { + return fmt.Errorf("failed to parse config file: %v", err) + } + for k := range f { + if !fields[strings.ToLower(k)] { + return fmt.Errorf("unknown field '%v' in config", k) + } + } + return nil +} diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go new file mode 100644 index 000000000..73ad3502c --- /dev/null +++ b/pkg/config/config_test.go @@ -0,0 +1,65 @@ +// Copyright 2016 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 config + +import ( + "fmt" + "reflect" + "testing" +) + +func TestUnknown(t *testing.T) { + type Config struct { + Foo int + Bar string + Baz string `json:"-"` + } + tests := []struct { + input string + output Config + err string + }{ + { + `{"foo": 42}`, + Config{ + Foo: 42, + }, + "", + }, + { + `{"BAR": "Baz", "foo": 42}`, + Config{ + Foo: 42, + Bar: "Baz", + }, + "", + }, + { + `{"foobar": 42}`, + Config{}, + "unknown field 'foobar' in config", + }, + { + `{"foo": 1, "baz": "baz", "bar": "bar"}`, + Config{}, + "unknown field 'baz' in config", + }, + } + for i, test := range tests { + t.Run(fmt.Sprint(i), func(t *testing.T) { + var cfg Config + err := load([]byte(test.input), &cfg) + errStr := "" + if err != nil { + errStr = err.Error() + } + if test.err != errStr { + t.Fatalf("bad err: want '%v', got '%v'", test.err, errStr) + } + if !reflect.DeepEqual(test.output, cfg) { + t.Fatalf("bad output: want '%#v', got '%#v'", test.output, cfg) + } + }) + } +} diff --git a/repro/repro.go b/repro/repro.go index b9124942e..1d7f39518 100644 --- a/repro/repro.go +++ b/repro/repro.go @@ -11,12 +11,12 @@ import ( "sync" "time" - "github.com/google/syzkaller/config" "github.com/google/syzkaller/csource" "github.com/google/syzkaller/fileutil" . "github.com/google/syzkaller/log" "github.com/google/syzkaller/prog" "github.com/google/syzkaller/report" + "github.com/google/syzkaller/syz-manager/config" "github.com/google/syzkaller/vm" ) diff --git a/syz-gce/syz-gce.go b/syz-gce/syz-gce.go index 2e254556c..9bdedbe5b 100644 --- a/syz-gce/syz-gce.go +++ b/syz-gce/syz-gce.go @@ -38,10 +38,11 @@ import ( "time" "cloud.google.com/go/storage" - "github.com/google/syzkaller/config" "github.com/google/syzkaller/dashboard" "github.com/google/syzkaller/gce" . "github.com/google/syzkaller/log" + pkgconfig "github.com/google/syzkaller/pkg/config" + "github.com/google/syzkaller/syz-manager/config" "golang.org/x/net/context" ) @@ -89,7 +90,12 @@ type Action interface { func main() { flag.Parse() - cfg = readConfig(*flagConfig) + cfg = &Config{ + Use_Dashboard_Patches: true, + } + if err := pkgconfig.Load(*flagConfig, cfg); err != nil { + Fatalf("failed to load config file: %v", err) + } EnableLogCaching(1000, 1<<20) initHttp(fmt.Sprintf(":%v", cfg.Http_Port)) @@ -493,22 +499,6 @@ func (a *GCSImageAction) Build() error { return nil } -func readConfig(filename string) *Config { - if filename == "" { - Fatalf("supply config in -config flag") - } - data, err := ioutil.ReadFile(filename) - if err != nil { - Fatalf("failed to read config file: %v", err) - } - cfg := new(Config) - cfg.Use_Dashboard_Patches = true - if err := json.Unmarshal(data, cfg); err != nil { - Fatalf("failed to parse config file: %v", err) - } - return cfg -} - func writeManagerConfig(cfg *Config, httpPort int, file string) error { tag, err := ioutil.ReadFile("image/tag") if err != nil { diff --git a/syz-manager/config/config.go b/syz-manager/config/config.go new file mode 100644 index 000000000..12db5696d --- /dev/null +++ b/syz-manager/config/config.go @@ -0,0 +1,348 @@ +// Copyright 2015 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 config + +import ( + "fmt" + "os" + "path/filepath" + "regexp" + "strings" + + "github.com/google/syzkaller/fileutil" + pkgconfig "github.com/google/syzkaller/pkg/config" + "github.com/google/syzkaller/sys" + "github.com/google/syzkaller/vm" +) + +type Config struct { + Name string // Instance name (used for identification and as GCE instance prefix) + Http string // TCP address to serve HTTP stats page (e.g. "localhost:50000") + Rpc string // TCP address to serve RPC for fuzzer processes (optional, only useful for type "none") + Workdir string + Vmlinux string + Kernel string // e.g. arch/x86/boot/bzImage + Tag string // arbitrary optional tag that is saved along with crash reports (e.g. kernel branch/commit) + Cmdline string // kernel command line + Image string // linux image for VMs + Initrd string // linux initial ramdisk. (optional) + Cpu int // number of VM CPUs + Mem int // amount of VM memory in MBs + Sshkey string // root ssh key for the image + Bin string // qemu/lkvm binary name + Bin_Args string // additional command line arguments for qemu/lkvm binary + Debug bool // dump all VM output to console + Output string // one of stdout/dmesg/file (useful only for local VM) + + Hub_Addr string + Hub_Key string + + Dashboard_Addr string + Dashboard_Key string + + Syzkaller string // path to syzkaller checkout (syz-manager will look for binaries in bin subdir) + Type string // VM type (qemu, kvm, local) + Count int // number of VMs (don't secify for adb, instead specify devices) + Devices []string // device IDs for adb + Procs int // number of parallel processes inside of every VM + + Sandbox string // type of sandbox to use during fuzzing: + // "none": don't do anything special (has false positives, e.g. due to killing init) + // "setuid": impersonate into user nobody (65534), default + // "namespace": create a new namespace for fuzzer using CLONE_NEWNS/CLONE_NEWNET/CLONE_NEWPID/etc, + // requires building kernel with CONFIG_NAMESPACES, CONFIG_UTS_NS, CONFIG_USER_NS, CONFIG_PID_NS and CONFIG_NET_NS. + + Machine_Type string // GCE machine type (e.g. "n1-highcpu-2") + + Odroid_Host_Addr string // ip address of the host machine + Odroid_Slave_Addr string // ip address of the Odroid board + Odroid_Console string // console device name (e.g. "/dev/ttyUSB0") + Odroid_Hub_Bus int // host USB bus number for the USB hub + Odroid_Hub_Device int // host USB device number for the USB hub + Odroid_Hub_Port int // port on the USB hub to which Odroid is connected + + Cover bool // use kcov coverage (default: true) + Leak bool // do memory leak checking + Reproduce bool // reproduce, localize and minimize crashers (on by default) + + Enable_Syscalls []string + Disable_Syscalls []string + Suppressions []string // don't save reports matching these regexps, but reboot VM after them + Ignores []string // completely ignore reports matching these regexps (don't save nor reboot) + + // Implementation details beyond this point. + ParsedSuppressions []*regexp.Regexp `json:"-"` + ParsedIgnores []*regexp.Regexp `json:"-"` +} + +func Parse(filename string) (*Config, map[int]bool, error) { + cfg := &Config{ + Cover: true, + Reproduce: true, + Sandbox: "setuid", + } + if err := pkgconfig.Load(filename, cfg); err != nil { + return nil, nil, err + } + if _, err := os.Stat(filepath.Join(cfg.Syzkaller, "bin/syz-fuzzer")); err != nil { + return nil, nil, fmt.Errorf("bad config syzkaller param: can't find bin/syz-fuzzer") + } + if _, err := os.Stat(filepath.Join(cfg.Syzkaller, "bin/syz-executor")); err != nil { + return nil, nil, fmt.Errorf("bad config syzkaller param: can't find bin/syz-executor") + } + if cfg.Http == "" { + return nil, nil, fmt.Errorf("config param http is empty") + } + if cfg.Workdir == "" { + return nil, nil, fmt.Errorf("config param workdir is empty") + } + if cfg.Vmlinux == "" { + return nil, nil, fmt.Errorf("config param vmlinux is empty") + } + if cfg.Type == "" { + return nil, nil, fmt.Errorf("config param type is empty") + } + switch cfg.Type { + case "none": + if cfg.Count != 0 { + return nil, nil, fmt.Errorf("invalid config param count: %v, type \"none\" does not support param count", cfg.Count) + } + if cfg.Rpc == "" { + return nil, nil, fmt.Errorf("config param rpc is empty (required for type \"none\")") + } + if len(cfg.Devices) != 0 { + return nil, nil, fmt.Errorf("type %v does not support devices param", cfg.Type) + } + case "adb": + if cfg.Count != 0 { + return nil, nil, fmt.Errorf("don't specify count for adb, instead specify devices") + } + if len(cfg.Devices) == 0 { + return nil, nil, fmt.Errorf("specify at least 1 adb device") + } + cfg.Count = len(cfg.Devices) + case "odroid": + if cfg.Count != 1 { + return nil, nil, fmt.Errorf("no support for multiple Odroid devices yet, count should be 1") + } + if cfg.Odroid_Host_Addr == "" { + return nil, nil, fmt.Errorf("config param odroid_host_addr is empty") + } + if cfg.Odroid_Slave_Addr == "" { + return nil, nil, fmt.Errorf("config param odroid_slave_addr is empty") + } + if cfg.Odroid_Console == "" { + return nil, nil, fmt.Errorf("config param odroid_console is empty") + } + if cfg.Odroid_Hub_Bus == 0 { + return nil, nil, fmt.Errorf("config param odroid_hub_bus is empty") + } + if cfg.Odroid_Hub_Device == 0 { + return nil, nil, fmt.Errorf("config param odroid_hub_device is empty") + } + if cfg.Odroid_Hub_Port == 0 { + return nil, nil, fmt.Errorf("config param odroid_hub_port is empty") + } + case "gce": + if cfg.Machine_Type == "" { + return nil, nil, fmt.Errorf("machine_type parameter is empty (required for gce)") + } + fallthrough + default: + if cfg.Count <= 0 || cfg.Count > 1000 { + return nil, nil, fmt.Errorf("invalid config param count: %v, want (1, 1000]", cfg.Count) + } + if len(cfg.Devices) != 0 { + return nil, nil, fmt.Errorf("type %v does not support devices param", cfg.Type) + } + } + if cfg.Rpc == "" { + cfg.Rpc = "localhost:0" + } + if cfg.Procs <= 0 { + cfg.Procs = 1 + } + if cfg.Procs > 32 { + return nil, nil, fmt.Errorf("config param procs has higher value '%v' then the max supported 32", cfg.Procs) + } + if cfg.Output == "" { + if cfg.Type == "local" { + cfg.Output = "none" + } else { + cfg.Output = "stdout" + } + } + switch cfg.Output { + case "none", "stdout", "dmesg", "file": + default: + return nil, nil, fmt.Errorf("config param output must contain one of none/stdout/dmesg/file") + } + switch cfg.Sandbox { + case "none", "setuid", "namespace": + default: + return nil, nil, fmt.Errorf("config param sandbox must contain one of none/setuid/namespace") + } + + wd, err := os.Getwd() + if err != nil { + return nil, nil, fmt.Errorf("failed to get wd: %v", err) + } + abs := func(path string) string { + if path != "" && !filepath.IsAbs(path) { + path = filepath.Join(wd, path) + } + return path + } + cfg.Workdir = abs(cfg.Workdir) + cfg.Kernel = abs(cfg.Kernel) + cfg.Vmlinux = abs(cfg.Vmlinux) + cfg.Syzkaller = abs(cfg.Syzkaller) + cfg.Initrd = abs(cfg.Initrd) + cfg.Sshkey = abs(cfg.Sshkey) + cfg.Bin = abs(cfg.Bin) + + syscalls, err := parseSyscalls(cfg) + if err != nil { + return nil, nil, err + } + + if err := parseSuppressions(cfg); err != nil { + return nil, nil, err + } + + if cfg.Hub_Addr != "" { + if cfg.Name == "" { + return nil, nil, fmt.Errorf("hub_addr is set, but name is empty") + } + if cfg.Hub_Key == "" { + return nil, nil, fmt.Errorf("hub_addr is set, but hub_key is empty") + } + } + if cfg.Dashboard_Addr != "" { + if cfg.Name == "" { + return nil, nil, fmt.Errorf("dashboard_addr is set, but name is empty") + } + if cfg.Dashboard_Key == "" { + return nil, nil, fmt.Errorf("dashboard_addr is set, but dashboard_key is empty") + } + } + + return cfg, syscalls, nil +} + +func parseSyscalls(cfg *Config) (map[int]bool, error) { + match := func(call *sys.Call, str string) bool { + if str == call.CallName || str == call.Name { + return true + } + if len(str) > 1 && str[len(str)-1] == '*' && strings.HasPrefix(call.Name, str[:len(str)-1]) { + return true + } + return false + } + + syscalls := make(map[int]bool) + if len(cfg.Enable_Syscalls) != 0 { + for _, c := range cfg.Enable_Syscalls { + n := 0 + for _, call := range sys.Calls { + if match(call, c) { + syscalls[call.ID] = true + n++ + } + } + if n == 0 { + return nil, fmt.Errorf("unknown enabled syscall: %v", c) + } + } + } else { + for _, call := range sys.Calls { + syscalls[call.ID] = true + } + } + for _, c := range cfg.Disable_Syscalls { + n := 0 + for _, call := range sys.Calls { + if match(call, c) { + delete(syscalls, call.ID) + n++ + } + } + if n == 0 { + return nil, fmt.Errorf("unknown disabled syscall: %v", c) + } + } + // mmap is used to allocate memory. + syscalls[sys.CallMap["mmap"].ID] = true + + return syscalls, nil +} + +func parseSuppressions(cfg *Config) error { + // Add some builtin suppressions. + supp := append(cfg.Suppressions, []string{ + "panic: failed to start executor binary", + "panic: executor failed: pthread_create failed", + "panic: failed to create temp dir", + "fatal error: runtime: out of memory", + "fatal error: runtime: cannot allocate memory", + "fatal error: unexpected signal during runtime execution", // presubmably OOM turned into SIGBUS + "signal SIGBUS: bus error", // presubmably OOM turned into SIGBUS + "Out of memory: Kill process .* \\(syz-fuzzer\\)", + "lowmemorykiller: Killing 'syz-fuzzer'", + //"INFO: lockdep is turned off", // printed by some sysrq that dumps scheduler state, but also on all lockdep reports + }...) + for _, s := range supp { + re, err := regexp.Compile(s) + if err != nil { + return fmt.Errorf("failed to compile suppression '%v': %v", s, err) + } + cfg.ParsedSuppressions = append(cfg.ParsedSuppressions, re) + } + for _, ignore := range cfg.Ignores { + re, err := regexp.Compile(ignore) + if err != nil { + return fmt.Errorf("failed to compile ignore '%v': %v", ignore, err) + } + cfg.ParsedIgnores = append(cfg.ParsedIgnores, re) + } + return nil +} + +func CreateVMConfig(cfg *Config, index int) (*vm.Config, error) { + if index < 0 || index >= cfg.Count { + return nil, fmt.Errorf("invalid VM index %v (count %v)", index, cfg.Count) + } + workdir, err := fileutil.ProcessTempDir(cfg.Workdir) + if err != nil { + return nil, fmt.Errorf("failed to create instance temp dir: %v", err) + } + vmCfg := &vm.Config{ + Name: fmt.Sprintf("%v-%v-%v", cfg.Type, cfg.Name, index), + Index: index, + Workdir: workdir, + Bin: cfg.Bin, + BinArgs: cfg.Bin_Args, + Kernel: cfg.Kernel, + Cmdline: cfg.Cmdline, + Image: cfg.Image, + Initrd: cfg.Initrd, + Sshkey: cfg.Sshkey, + Executor: filepath.Join(cfg.Syzkaller, "bin", "syz-executor"), + Cpu: cfg.Cpu, + Mem: cfg.Mem, + Debug: cfg.Debug, + MachineType: cfg.Machine_Type, + OdroidHostAddr: cfg.Odroid_Host_Addr, + OdroidSlaveAddr: cfg.Odroid_Slave_Addr, + OdroidConsole: cfg.Odroid_Console, + OdroidHubBus: cfg.Odroid_Hub_Bus, + OdroidHubDevice: cfg.Odroid_Hub_Device, + OdroidHubPort: cfg.Odroid_Hub_Port, + } + if len(cfg.Devices) != 0 { + vmCfg.Device = cfg.Devices[index] + } + return vmCfg, nil +} diff --git a/syz-manager/manager.go b/syz-manager/manager.go index ad24f4c39..0cdfa320d 100644 --- a/syz-manager/manager.go +++ b/syz-manager/manager.go @@ -19,7 +19,6 @@ import ( "syscall" "time" - "github.com/google/syzkaller/config" "github.com/google/syzkaller/cover" "github.com/google/syzkaller/csource" "github.com/google/syzkaller/dashboard" @@ -30,6 +29,7 @@ import ( "github.com/google/syzkaller/report" "github.com/google/syzkaller/repro" . "github.com/google/syzkaller/rpctype" + "github.com/google/syzkaller/syz-manager/config" "github.com/google/syzkaller/vm" _ "github.com/google/syzkaller/vm/adb" _ "github.com/google/syzkaller/vm/gce" diff --git a/tools/syz-crush/crush.go b/tools/syz-crush/crush.go index d519107f9..f1796baf0 100644 --- a/tools/syz-crush/crush.go +++ b/tools/syz-crush/crush.go @@ -18,8 +18,8 @@ import ( "syscall" "time" - "github.com/google/syzkaller/config" . "github.com/google/syzkaller/log" + "github.com/google/syzkaller/syz-manager/config" "github.com/google/syzkaller/vm" _ "github.com/google/syzkaller/vm/adb" _ "github.com/google/syzkaller/vm/gce" diff --git a/tools/syz-repro/repro.go b/tools/syz-repro/repro.go index 841a121d1..064846991 100644 --- a/tools/syz-repro/repro.go +++ b/tools/syz-repro/repro.go @@ -11,10 +11,10 @@ import ( "os/signal" "syscall" - "github.com/google/syzkaller/config" "github.com/google/syzkaller/csource" . "github.com/google/syzkaller/log" "github.com/google/syzkaller/repro" + "github.com/google/syzkaller/syz-manager/config" "github.com/google/syzkaller/vm" _ "github.com/google/syzkaller/vm/adb" _ "github.com/google/syzkaller/vm/gce" -- cgit mrf-deployment