Home | History | Annotate | Download | only in mgrconfig
      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 
      4 package mgrconfig
      5 
      6 import (
      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 
     20 type 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 
     97 func 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 
    108 func 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 
    119 func 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 
    127 func 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 
    135 func 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 
    146 func 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 
    155 func 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 
    204 func 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 
    221 func 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 
    246 func 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 
    263 func 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 
    301 func 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