blob: 87562c3ddd607380490a27cd6dd28e3785b4b11f [file] [log] [blame]
// 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"
"path/filepath"
"regexp"
"strings"
"github.com/google/syzkaller/pkg/config"
"github.com/google/syzkaller/pkg/osutil"
"github.com/google/syzkaller/prog"
_ "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)
Target string // Target OS/arch, e.g. "linux/arm64" or "linux/amd64/386" (amd64 OS with 386 test process)
Http string // TCP address to serve HTTP stats page (e.g. "localhost:50000")
Rpc string // TCP address to serve RPC for fuzzer processes (optional)
Workdir string
Vmlinux string
Kernel_Src string // kernel source directory
Tag string // arbitrary optional tag that is saved along with crash reports (e.g. branch/commit)
Image string // linux image for VMs
Sshkey string // ssh key for the image (may be empty for some VM types)
Ssh_User string // ssh user ("root" by default)
Hub_Client string
Hub_Addr string
Hub_Key string
Dashboard_Client string
Dashboard_Addr string
Dashboard_Key string
Syzkaller string // path to syzkaller checkout (syz-manager will look for binaries in bin subdir)
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.
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)
Type string // VM type (qemu, kvm, local)
VM json.RawMessage // VM-type-specific config
// Implementation details beyond this point.
ParsedSuppressions []*regexp.Regexp `json:"-"`
ParsedIgnores []*regexp.Regexp `json:"-"`
// 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) {
return load(data, "")
}
func LoadFile(filename string) (*Config, error) {
return load(nil, filename)
}
func DefaultValues() *Config {
return &Config{
Ssh_User: "root",
Cover: true,
Reproduce: true,
Sandbox: "setuid",
Rpc: ":0",
Procs: 1,
}
}
func load(data []byte, filename string) (*Config, error) {
cfg := DefaultValues()
if data != nil {
if err := config.LoadData(data, cfg); err != nil {
return nil, err
}
} else {
if err := config.LoadFile(filename, cfg); err != nil {
return nil, err
}
}
var err error
cfg.TargetOS, cfg.TargetVMArch, cfg.TargetArch, err = SplitTarget(cfg.Target)
if err != nil {
return nil, err
}
targetBin := func(name, arch string) string {
exe := ""
if cfg.TargetOS == "windows" {
exe = ".exe"
}
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 nil, fmt.Errorf("bad config syzkaller param: can't find %v", cfg.SyzFuzzerBin)
}
if !osutil.IsExist(cfg.SyzExecprogBin) {
return nil, fmt.Errorf("bad config syzkaller param: can't find %v", cfg.SyzExecprogBin)
}
if !osutil.IsExist(cfg.SyzExecutorBin) {
return nil, fmt.Errorf("bad config syzkaller param: can't find %v", cfg.SyzExecutorBin)
}
if cfg.Http == "" {
return nil, fmt.Errorf("config param http is empty")
}
if cfg.Workdir == "" {
return nil, fmt.Errorf("config param workdir is empty")
}
if cfg.Type == "" {
return nil, fmt.Errorf("config param type is empty")
}
if cfg.Procs < 1 || cfg.Procs > 32 {
return nil, fmt.Errorf("bad config param procs: '%v', want [1, 32]", cfg.Procs)
}
switch cfg.Sandbox {
case "none", "setuid", "namespace":
default:
return nil, fmt.Errorf("config param sandbox must contain one of none/setuid/namespace")
}
cfg.Workdir = osutil.Abs(cfg.Workdir)
cfg.Vmlinux = osutil.Abs(cfg.Vmlinux)
cfg.Syzkaller = osutil.Abs(cfg.Syzkaller)
if cfg.Kernel_Src == "" {
cfg.Kernel_Src = filepath.Dir(cfg.Vmlinux) // assume in-tree build by default
}
if err := parseSuppressions(cfg); err != nil {
return nil, err
}
if cfg.Hub_Client != "" && (cfg.Name == "" || cfg.Hub_Addr == "" || cfg.Hub_Key == "") {
return nil, fmt.Errorf("hub_client is set, but name/hub_addr/hub_key is empty")
}
if cfg.Dashboard_Client != "" && (cfg.Name == "" ||
cfg.Dashboard_Addr == "" ||
cfg.Dashboard_Key == "") {
return nil, fmt.Errorf("dashboard_client is set, but name/dashboard_addr/dashboard_key is empty")
}
return cfg, 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(cfg *Config) (map[int]bool, error) {
match := func(call *prog.Syscall, 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
}
target, err := prog.GetTarget(cfg.TargetOS, cfg.TargetArch)
if err != nil {
return nil, err
}
syscalls := make(map[int]bool)
if len(cfg.Enable_Syscalls) != 0 {
for _, c := range cfg.Enable_Syscalls {
n := 0
for _, call := range target.Syscalls {
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 target.Syscalls {
syscalls[call.ID] = true
}
}
for _, c := range cfg.Disable_Syscalls {
n := 0
for _, call := range target.Syscalls {
if match(call, c) {
delete(syscalls, call.ID)
n++
}
}
if n == 0 {
return nil, fmt.Errorf("unknown disabled syscall: %v", c)
}
}
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\\)",
"Out of memory: Kill process .* \\(sshd\\)",
"lowmemorykiller: Killing 'syz-fuzzer'",
"lowmemorykiller: Killing 'sshd'",
}...)
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 CreateVMEnv(cfg *Config, debug bool) *vm.Env {
return &vm.Env{
Name: cfg.Name,
OS: cfg.TargetOS,
Arch: cfg.TargetVMArch,
Workdir: cfg.Workdir,
Image: cfg.Image,
SshKey: cfg.Sshkey,
SshUser: cfg.Ssh_User,
Debug: debug,
Config: cfg.VM,
}
}