// 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 mgrconfig import ( "encoding/json" "fmt" "os" "path/filepath" "strings" "github.com/google/syzkaller/pkg/config" "github.com/google/syzkaller/pkg/osutil" "github.com/google/syzkaller/prog" _ "github.com/google/syzkaller/sys" // most mgrconfig users want targets too "github.com/google/syzkaller/sys/targets" ) type Config struct { // Instance name (used for identification and as GCE instance prefix). Name string `json:"name"` // Target OS/arch, e.g. "linux/arm64" or "linux/amd64/386" (amd64 OS with 386 test process). Target string `json:"target"` // TCP address to serve HTTP stats page (e.g. "localhost:50000"). HTTP string `json:"http"` // TCP address to serve RPC for fuzzer processes (optional). RPC string `json:"rpc"` Workdir string `json:"workdir"` // Directory with kernel object files. KernelObj string `json:"kernel_obj"` // Kernel source directory (if not set defaults to KernelObj). KernelSrc string `json:"kernel_src"` // Arbitrary optional tag that is saved along with crash reports (e.g. branch/commit). Tag string `json:"tag"` // Linux image for VMs. Image string `json:"image"` // SSH key for the image (may be empty for some VM types). SSHKey string `json:"sshkey"` // SSH user ("root" by default). SSHUser string `json:"ssh_user"` HubClient string `json:"hub_client"` HubAddr string `json:"hub_addr"` HubKey string `json:"hub_key"` // syz-manager will send crash emails to this list of emails using mailx (optional). EmailAddrs []string `json:"email_addrs"` DashboardClient string `json:"dashboard_client"` DashboardAddr string `json:"dashboard_addr"` DashboardKey string `json:"dashboard_key"` // Path to syzkaller checkout (syz-manager will look for binaries in bin subdir). Syzkaller string `json:"syzkaller"` // Number of parallel processes inside of every VM. Procs int `json:"procs"` // Type of sandbox to use during fuzzing: // "none": don't do anything special (has false positives, e.g. due to killing init), default // "setuid": impersonate into user nobody (65534) // "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. Sandbox string `json:"sandbox"` // Use KCOV coverage (default: true). Cover bool `json:"cover"` // Reproduce, localize and minimize crashers (default: true). Reproduce bool `json:"reproduce"` EnabledSyscalls []string `json:"enable_syscalls"` DisabledSyscalls []string `json:"disable_syscalls"` // Don't save reports matching these regexps, but reboot VM after them, // matched against whole report output. Suppressions []string `json:"suppressions"` // Completely ignore reports matching these regexps (don't save nor reboot), // must match the first line of crash message. Ignores []string `json:"ignores"` // VM type (qemu, gce, android, isolated, etc). Type string `json:"type"` // VM-type-specific config. VM json.RawMessage `json:"vm"` // Implementation details beyond this point. // Parsed Target: TargetOS string `json:"-"` TargetArch string `json:"-"` TargetVMArch string `json:"-"` // Syzkaller binaries that we are going to use: SyzFuzzerBin string `json:"-"` SyzExecprogBin string `json:"-"` SyzExecutorBin string `json:"-"` } func LoadData(data []byte) (*Config, error) { cfg, err := LoadPartialData(data) if err != nil { return nil, err } if err := Complete(cfg); err != nil { return nil, err } return cfg, nil } func LoadFile(filename string) (*Config, error) { cfg, err := LoadPartialFile(filename) if err != nil { return nil, err } if err := Complete(cfg); err != nil { return nil, err } return cfg, nil } func LoadPartialData(data []byte) (*Config, error) { cfg := defaultValues() if err := config.LoadData(data, cfg); err != nil { return nil, err } return loadPartial(cfg) } func LoadPartialFile(filename string) (*Config, error) { cfg := defaultValues() if err := config.LoadFile(filename, cfg); err != nil { return nil, err } return loadPartial(cfg) } func defaultValues() *Config { return &Config{ SSHUser: "root", Cover: true, Reproduce: true, Sandbox: "none", RPC: ":0", Procs: 1, } } func loadPartial(cfg *Config) (*Config, error) { var err error cfg.TargetOS, cfg.TargetVMArch, cfg.TargetArch, err = splitTarget(cfg.Target) if err != nil { return nil, err } return cfg, nil } func Complete(cfg *Config) error { if cfg.TargetOS == "" || cfg.TargetVMArch == "" || cfg.TargetArch == "" { return fmt.Errorf("target parameters are not filled in") } if cfg.Workdir == "" { return fmt.Errorf("config param workdir is empty") } cfg.Workdir = osutil.Abs(cfg.Workdir) if cfg.Syzkaller == "" { return fmt.Errorf("config param syzkaller is empty") } if err := completeBinaries(cfg); err != nil { return err } if cfg.HTTP == "" { return fmt.Errorf("config param http is empty") } if cfg.Type == "" { return fmt.Errorf("config param type is empty") } if cfg.Procs < 1 || cfg.Procs > 32 { return fmt.Errorf("bad config param procs: '%v', want [1, 32]", cfg.Procs) } switch cfg.Sandbox { case "none", "setuid", "namespace": default: return fmt.Errorf("config param sandbox must contain one of none/setuid/namespace") } if err := checkSSHParams(cfg); err != nil { return err } cfg.KernelObj = osutil.Abs(cfg.KernelObj) if cfg.KernelSrc == "" { cfg.KernelSrc = cfg.KernelObj // assume in-tree build by default } cfg.KernelSrc = osutil.Abs(cfg.KernelSrc) if cfg.HubClient != "" && (cfg.Name == "" || cfg.HubAddr == "" || cfg.HubKey == "") { return fmt.Errorf("hub_client is set, but name/hub_addr/hub_key is empty") } if cfg.DashboardClient != "" && (cfg.Name == "" || cfg.DashboardAddr == "" || cfg.DashboardKey == "") { return fmt.Errorf("dashboard_client is set, but name/dashboard_addr/dashboard_key is empty") } return nil } func checkSSHParams(cfg *Config) error { if cfg.SSHUser == "" { return fmt.Errorf("bad config syzkaller param: ssh user is empty") } if cfg.SSHKey == "" { return nil } info, err := os.Stat(cfg.SSHKey) if err != nil { return err } if info.Mode()&0077 != 0 { return fmt.Errorf("sshkey %v is unprotected, ssh will reject it, do chmod 0600", cfg.SSHKey) } return nil } func completeBinaries(cfg *Config) error { sysTarget := targets.Get(cfg.TargetOS, cfg.TargetArch) if sysTarget == nil { return fmt.Errorf("unsupported OS/arch: %v/%v", cfg.TargetOS, cfg.TargetArch) } cfg.Syzkaller = osutil.Abs(cfg.Syzkaller) exe := sysTarget.ExeExtension targetBin := func(name, arch string) string { return filepath.Join(cfg.Syzkaller, "bin", cfg.TargetOS+"_"+arch, name+exe) } cfg.SyzFuzzerBin = targetBin("syz-fuzzer", cfg.TargetVMArch) cfg.SyzExecprogBin = targetBin("syz-execprog", cfg.TargetVMArch) cfg.SyzExecutorBin = targetBin("syz-executor", cfg.TargetArch) if !osutil.IsExist(cfg.SyzFuzzerBin) { return fmt.Errorf("bad config syzkaller param: can't find %v", cfg.SyzFuzzerBin) } if !osutil.IsExist(cfg.SyzExecprogBin) { return fmt.Errorf("bad config syzkaller param: can't find %v", cfg.SyzExecprogBin) } if !osutil.IsExist(cfg.SyzExecutorBin) { return fmt.Errorf("bad config syzkaller param: can't find %v", cfg.SyzExecutorBin) } return nil } func splitTarget(target string) (string, string, string, error) { if target == "" { return "", "", "", fmt.Errorf("target is empty") } targetParts := strings.Split(target, "/") if len(targetParts) != 2 && len(targetParts) != 3 { return "", "", "", fmt.Errorf("bad config param target") } os := targetParts[0] vmarch := targetParts[1] arch := targetParts[1] if len(targetParts) == 3 { arch = targetParts[2] } return os, vmarch, arch, nil } func ParseEnabledSyscalls(target *prog.Target, enabled, disabled []string) (map[int]bool, error) { syscalls := make(map[int]bool) if len(enabled) != 0 { for _, c := range enabled { n := 0 for _, call := range target.Syscalls { if matchSyscall(call.Name, c) { syscalls[call.ID] = true n++ } } if n == 0 { return nil, fmt.Errorf("unknown enabled syscall: %v", c) } } } else { for _, call := range target.Syscalls { syscalls[call.ID] = true } } for _, c := range disabled { n := 0 for _, call := range target.Syscalls { if matchSyscall(call.Name, c) { delete(syscalls, call.ID) n++ } } if n == 0 { return nil, fmt.Errorf("unknown disabled syscall: %v", c) } } if len(syscalls) == 0 { return nil, fmt.Errorf("all syscalls are disabled by disable_syscalls in config") } return syscalls, nil } func matchSyscall(name, pattern string) bool { if pattern == name || strings.HasPrefix(name, pattern+"$") { return true } if len(pattern) > 1 && pattern[len(pattern)-1] == '*' && strings.HasPrefix(name, pattern[:len(pattern)-1]) { return true } return false }