github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/mgrconfig/load.go (about) 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 "bytes" 8 "fmt" 9 "os" 10 "path/filepath" 11 "regexp" 12 "runtime" 13 "strings" 14 15 "github.com/google/syzkaller/pkg/config" 16 "github.com/google/syzkaller/pkg/osutil" 17 "github.com/google/syzkaller/pkg/vminfo" 18 "github.com/google/syzkaller/prog" 19 _ "github.com/google/syzkaller/sys" // most mgrconfig users want targets too 20 "github.com/google/syzkaller/sys/targets" 21 ) 22 23 // Derived config values that are handy to keep with the config, filled after reading user config. 24 type Derived struct { 25 Target *prog.Target 26 SysTarget *targets.Target 27 28 // Parsed Target: 29 TargetOS string 30 TargetArch string 31 TargetVMArch string 32 33 // Full paths to binaries we are going to use: 34 ExecprogBin string 35 ExecutorBin string 36 37 Syscalls []int 38 NoMutateCalls map[int]bool // Set of IDs of syscalls which should not be mutated. 39 Timeouts targets.Timeouts 40 41 // Special debugging/development mode specified by VM type "none". 42 // In this mode syz-manager does not start any VMs, but instead a user is supposed 43 // to start syz-executor process in a VM manually. 44 VMLess bool 45 46 LocalModules []*vminfo.KernelModule 47 } 48 49 func LoadData(data []byte) (*Config, error) { 50 cfg, err := LoadPartialData(data) 51 if err != nil { 52 return nil, err 53 } 54 if err := Complete(cfg); err != nil { 55 return nil, err 56 } 57 return cfg, nil 58 } 59 60 func LoadFile(filename string) (*Config, error) { 61 cfg, err := LoadPartialFile(filename) 62 if err != nil { 63 return nil, err 64 } 65 if err := Complete(cfg); err != nil { 66 return nil, err 67 } 68 return cfg, nil 69 } 70 71 func LoadPartialData(data []byte) (*Config, error) { 72 cfg := defaultValues() 73 if err := config.LoadData(data, cfg); err != nil { 74 return nil, err 75 } 76 if err := SetTargets(cfg); err != nil { 77 return nil, err 78 } 79 return cfg, nil 80 } 81 82 func LoadPartialFile(filename string) (*Config, error) { 83 cfg := defaultValues() 84 if err := config.LoadFile(filename, cfg); err != nil { 85 return nil, err 86 } 87 if err := SetTargets(cfg); err != nil { 88 return nil, err 89 } 90 return cfg, nil 91 } 92 93 func defaultValues() *Config { 94 return &Config{ 95 SSHUser: "root", 96 Cover: true, 97 Reproduce: true, 98 Sandbox: "none", 99 RPC: ":0", 100 MaxCrashLogs: 100, 101 Procs: 6, 102 PreserveCorpus: true, 103 RunFsck: true, 104 Experimental: Experimental{ 105 RemoteCover: true, 106 CoverEdges: true, 107 DescriptionsMode: manualDescriptions, 108 }, 109 } 110 } 111 112 type DescriptionsMode int 113 114 const ( 115 invalidDescriptions = iota 116 ManualDescriptions 117 AutoDescriptions 118 AnyDescriptions 119 ) 120 121 const manualDescriptions = "manual" 122 123 var ( 124 strToDescriptionsMode = map[string]DescriptionsMode{ 125 manualDescriptions: ManualDescriptions, 126 "auto": AutoDescriptions, 127 "any": AnyDescriptions, 128 } 129 ) 130 131 func SetTargets(cfg *Config) error { 132 var err error 133 cfg.TargetOS, cfg.TargetVMArch, cfg.TargetArch, cfg.Target, cfg.SysTarget, 134 err = SplitTarget(cfg.RawTarget) 135 return err 136 } 137 138 func Complete(cfg *Config) error { 139 if err := checkNonEmpty( 140 cfg.TargetOS, "target", 141 cfg.TargetVMArch, "target", 142 cfg.TargetArch, "target", 143 cfg.Workdir, "workdir", 144 cfg.Syzkaller, "syzkaller", 145 cfg.Type, "type", 146 cfg.SSHUser, "ssh_user", 147 ); err != nil { 148 return err 149 } 150 cfg.Workdir = osutil.Abs(cfg.Workdir) 151 if cfg.WorkdirTemplate != "" { 152 cfg.WorkdirTemplate = osutil.Abs(cfg.WorkdirTemplate) 153 if _, err := os.ReadDir(cfg.WorkdirTemplate); err != nil { 154 return fmt.Errorf("failed to read workdir_template: %w", err) 155 } 156 } 157 if cfg.Image != "" { 158 if !osutil.IsExist(cfg.Image) { 159 return fmt.Errorf("bad config param image: can't find %v", cfg.Image) 160 } 161 cfg.Image = osutil.Abs(cfg.Image) 162 } 163 if err := cfg.completeBinaries(); err != nil { 164 return err 165 } 166 if cfg.Procs < 1 || cfg.Procs > prog.MaxPids { 167 return fmt.Errorf("bad config param procs: '%v', want [1, %v]", cfg.Procs, prog.MaxPids) 168 } 169 switch cfg.Sandbox { 170 case "none", "setuid", "namespace", "android": 171 default: 172 return fmt.Errorf("config param sandbox must contain one of none/setuid/namespace/android") 173 } 174 if err := cfg.checkSSHParams(); err != nil { 175 return err 176 } 177 cfg.CompleteKernelDirs() 178 179 if err := cfg.completeServices(); err != nil { 180 return nil 181 } 182 183 if cfg.FuzzingVMs < 0 { 184 return fmt.Errorf("fuzzing_vms cannot be less than 0") 185 } 186 187 var err error 188 cfg.Syscalls, err = ParseEnabledSyscalls(cfg.Target, cfg.EnabledSyscalls, cfg.DisabledSyscalls, 189 strToDescriptionsMode[cfg.Experimental.DescriptionsMode]) 190 if err != nil { 191 return err 192 } 193 cfg.NoMutateCalls, err = ParseNoMutateSyscalls(cfg.Target, cfg.NoMutateSyscalls) 194 if err != nil { 195 return err 196 } 197 if err := cfg.completeFocusAreas(); err != nil { 198 return err 199 } 200 cfg.initTimeouts() 201 cfg.VMLess = cfg.Type == "none" 202 203 if cfg.VMLess && cfg.Reproduce { 204 return fmt.Errorf("if config param type is none, reproduce must be false") 205 } 206 207 return nil 208 } 209 210 func (cfg *Config) completeServices() error { 211 if cfg.HubClient != "" { 212 if err := checkNonEmpty( 213 cfg.Name, "name", 214 cfg.HubAddr, "hub_addr", 215 ); err != nil { 216 return err 217 } 218 } 219 if cfg.HubDomain != "" && 220 !regexp.MustCompile(`^[a-zA-Z0-9-_.]{2,50}(/[a-zA-Z0-9-_.]{2,50})?$`).MatchString(cfg.HubDomain) { 221 return fmt.Errorf("bad value for hub_domain") 222 } 223 if cfg.DashboardClient != "" { 224 if err := checkNonEmpty( 225 cfg.Name, "name", 226 cfg.DashboardAddr, "dashboard_addr", 227 ); err != nil { 228 return err 229 } 230 } 231 if !cfg.AssetStorage.IsEmpty() { 232 if cfg.DashboardClient == "" { 233 return fmt.Errorf("asset storage also requires dashboard client") 234 } 235 if err := cfg.AssetStorage.Validate(); err != nil { 236 return err 237 } 238 } 239 return nil 240 } 241 242 func (cfg *Config) initTimeouts() { 243 slowdown := 1 244 switch { 245 case cfg.Type == "qemu" && (runtime.GOARCH == cfg.SysTarget.Arch || runtime.GOARCH == cfg.SysTarget.VMArch): 246 // If TCG is enabled for QEMU, increase the slowdown. 247 if bytes.Contains(cfg.VM, []byte("-accel tcg")) { 248 slowdown = 10 249 } 250 case cfg.Type == "qemu" && runtime.GOARCH != cfg.SysTarget.Arch && runtime.GOARCH != cfg.SysTarget.VMArch: 251 // Assuming qemu emulation. 252 // Quick tests of mmap syscall on arm64 show ~9x slowdown. 253 slowdown = 10 254 case cfg.Type == targets.GVisor && cfg.Cover && strings.Contains(cfg.Name, "-race"): 255 // Go coverage+race has insane slowdown of ~350x. We can't afford such large value, 256 // but a smaller value should be enough to finish at least some syscalls. 257 // Note: the name check is a hack. 258 slowdown = 10 259 } 260 // Note: we could also consider heavy debug tools (KASAN/KMSAN/KCSAN/KMEMLEAK) if necessary. 261 cfg.Timeouts = cfg.SysTarget.Timeouts(slowdown) 262 } 263 264 func checkNonEmpty(fields ...string) error { 265 for i := 0; i < len(fields); i += 2 { 266 if fields[i] == "" { 267 return fmt.Errorf("config param %v is empty", fields[i+1]) 268 } 269 } 270 return nil 271 } 272 273 func (cov *CovFilterCfg) Empty() bool { 274 return len(cov.Functions)+len(cov.Files)+len(cov.RawPCs) == 0 275 } 276 277 func (cfg *Config) CompleteKernelDirs() { 278 cfg.KernelObj = osutil.Abs(cfg.KernelObj) 279 if cfg.KernelSrc == "" { 280 cfg.KernelSrc = cfg.KernelObj // assume in-tree build by default 281 } 282 cfg.KernelSrc = osutil.Abs(cfg.KernelSrc) 283 if cfg.KernelBuildSrc == "" { 284 cfg.KernelBuildSrc = cfg.KernelSrc 285 } 286 cfg.KernelBuildSrc = osutil.Abs(cfg.KernelBuildSrc) 287 } 288 289 type KernelDirs struct { 290 Src string 291 Obj string 292 BuildSrc string 293 } 294 295 func (cfg *Config) KernelDirs() *KernelDirs { 296 return &KernelDirs{ 297 Src: cfg.KernelSrc, 298 Obj: cfg.KernelObj, 299 BuildSrc: cfg.KernelBuildSrc, 300 } 301 } 302 303 func (cfg *Config) checkSSHParams() error { 304 if cfg.SSHKey == "" { 305 return nil 306 } 307 info, err := os.Stat(cfg.SSHKey) 308 if err != nil { 309 return err 310 } 311 if info.Mode()&0077 != 0 { 312 return fmt.Errorf("sshkey %v is unprotected, ssh will reject it, do chmod 0600", cfg.SSHKey) 313 } 314 cfg.SSHKey = osutil.Abs(cfg.SSHKey) 315 return nil 316 } 317 318 func (cfg *Config) completeBinaries() error { 319 cfg.Syzkaller = osutil.Abs(cfg.Syzkaller) 320 exe := cfg.SysTarget.ExeExtension 321 targetBin := func(name, arch string) string { 322 return filepath.Join(cfg.Syzkaller, "bin", cfg.TargetOS+"_"+arch, name+exe) 323 } 324 cfg.ExecprogBin = targetBin("syz-execprog", cfg.TargetVMArch) 325 cfg.ExecutorBin = targetBin("syz-executor", cfg.TargetArch) 326 327 if cfg.ExecprogBinOnTarget != "" { 328 cfg.SysTarget.ExecprogBin = cfg.ExecprogBinOnTarget 329 } 330 if cfg.ExecutorBinOnTarget != "" { 331 cfg.SysTarget.ExecutorBin = cfg.ExecutorBinOnTarget 332 } 333 if cfg.StraceBinOnTarget && cfg.StraceBin == "" { 334 cfg.StraceBin = "strace" 335 } 336 337 // If the target already provides binaries, we don't need to copy them. 338 if cfg.SysTarget.ExecprogBin != "" { 339 cfg.ExecprogBin = "" 340 } 341 if cfg.SysTarget.ExecutorBin != "" { 342 cfg.ExecutorBin = "" 343 } 344 if cfg.ExecprogBin != "" && !osutil.IsExist(cfg.ExecprogBin) { 345 return fmt.Errorf("bad config syzkaller param: can't find %v", cfg.ExecprogBin) 346 } 347 if cfg.ExecutorBin != "" && !osutil.IsExist(cfg.ExecutorBin) { 348 return fmt.Errorf("bad config syzkaller param: can't find %v", cfg.ExecutorBin) 349 } 350 if !cfg.StraceBinOnTarget && cfg.StraceBin != "" { 351 if !osutil.IsExist(cfg.StraceBin) { 352 return fmt.Errorf("bad config param strace_bin: can't find %v", cfg.StraceBin) 353 } 354 cfg.StraceBin = osutil.Abs(cfg.StraceBin) 355 } 356 return nil 357 } 358 359 func (cfg *Config) completeFocusAreas() error { 360 names := map[string]bool{} 361 seenEmptyFilter := false 362 for i, area := range cfg.Experimental.FocusAreas { 363 if area.Name != "" { 364 if names[area.Name] { 365 return fmt.Errorf("duplicate focus area name: %q", area.Name) 366 } 367 names[area.Name] = true 368 } 369 if area.Weight <= 0 { 370 return fmt.Errorf("focus area #%d: negative weight", i) 371 } 372 if area.Filter.Empty() { 373 if seenEmptyFilter { 374 return fmt.Errorf("there must be only one focus area with an empty filter") 375 } 376 seenEmptyFilter = true 377 } 378 } 379 if !cfg.CovFilter.Empty() { 380 if len(cfg.Experimental.FocusAreas) > 0 { 381 return fmt.Errorf("you cannot use both cov_filter and focus_areas") 382 } 383 cfg.Experimental.FocusAreas = []FocusArea{ 384 { 385 Name: "filtered", 386 Filter: cfg.CovFilter, 387 Weight: 1.0, 388 }, 389 } 390 cfg.CovFilter = CovFilterCfg{} 391 } 392 return nil 393 } 394 395 func SplitTarget(str string) (os, vmarch, arch string, target *prog.Target, sysTarget *targets.Target, err error) { 396 if str == "" { 397 err = fmt.Errorf("target is empty") 398 return 399 } 400 targetParts := strings.Split(str, "/") 401 if len(targetParts) != 2 && len(targetParts) != 3 { 402 err = fmt.Errorf("bad config param target") 403 return 404 } 405 os = targetParts[0] 406 vmarch = targetParts[1] 407 arch = targetParts[1] 408 if len(targetParts) == 3 { 409 arch = targetParts[2] 410 } 411 sysTarget = targets.Get(os, vmarch) 412 if sysTarget == nil { 413 err = fmt.Errorf("unsupported OS/arch: %v/%v", os, vmarch) 414 return 415 } 416 target, err = prog.GetTarget(os, arch) 417 return 418 } 419 420 func ParseEnabledSyscalls(target *prog.Target, enabled, disabled []string, 421 descriptionsMode DescriptionsMode) ([]int, error) { 422 if descriptionsMode == invalidDescriptions { 423 return nil, fmt.Errorf("config param descriptions_mode must contain one of auto/manual/any") 424 } 425 426 syscalls := make(map[int]bool) 427 if len(enabled) != 0 { 428 for _, c := range enabled { 429 n := 0 430 for _, call := range target.Syscalls { 431 if MatchSyscall(call.Name, c) { 432 syscalls[call.ID] = true 433 n++ 434 } 435 } 436 if n == 0 { 437 return nil, fmt.Errorf("unknown enabled syscall: %v", c) 438 } 439 } 440 } else { 441 for _, call := range target.Syscalls { 442 syscalls[call.ID] = true 443 } 444 } 445 446 for call := range syscalls { 447 if target.Syscalls[call].Attrs.Disabled || 448 descriptionsMode == ManualDescriptions && target.Syscalls[call].Attrs.Automatic || 449 descriptionsMode == AutoDescriptions && 450 !target.Syscalls[call].Attrs.Automatic && !target.Syscalls[call].Attrs.AutomaticHelper { 451 delete(syscalls, call) 452 } 453 } 454 for _, c := range disabled { 455 n := 0 456 for _, call := range target.Syscalls { 457 if MatchSyscall(call.Name, c) { 458 delete(syscalls, call.ID) 459 n++ 460 } 461 } 462 if n == 0 { 463 return nil, fmt.Errorf("unknown disabled syscall: %v", c) 464 } 465 } 466 if len(syscalls) == 0 { 467 return nil, fmt.Errorf("all syscalls are disabled by disable_syscalls in config") 468 } 469 var arr []int 470 for id := range syscalls { 471 arr = append(arr, id) 472 } 473 return arr, nil 474 } 475 476 func ParseNoMutateSyscalls(target *prog.Target, syscalls []string) (map[int]bool, error) { 477 var result = make(map[int]bool) 478 479 for _, c := range syscalls { 480 n := 0 481 for _, call := range target.Syscalls { 482 if MatchSyscall(call.Name, c) { 483 result[call.ID] = true 484 n++ 485 } 486 } 487 if n == 0 { 488 return nil, fmt.Errorf("unknown no_mutate syscall: %v", c) 489 } 490 } 491 492 return result, nil 493 } 494 495 func MatchSyscall(name, pattern string) bool { 496 if pattern == name || strings.HasPrefix(name, pattern+"$") { 497 return true 498 } 499 if len(pattern) > 1 && pattern[len(pattern)-1] == '*' && 500 strings.HasPrefix(name, pattern[:len(pattern)-1]) { 501 return true 502 } 503 return false 504 }