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