github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/tools/syz-kconf/kconf.go (about) 1 // Copyright 2020 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 // syz-kconf generates Linux kernel configs in dashboard/config/linux. 5 // See dashboard/config/linux/README.md for details. 6 package main 7 8 import ( 9 "errors" 10 "flag" 11 "fmt" 12 "os" 13 "path/filepath" 14 "regexp" 15 "runtime" 16 "strconv" 17 "strings" 18 "time" 19 20 "github.com/google/syzkaller/pkg/kconfig" 21 "github.com/google/syzkaller/pkg/osutil" 22 "github.com/google/syzkaller/pkg/tool" 23 "github.com/google/syzkaller/pkg/vcs" 24 "github.com/google/syzkaller/sys/targets" 25 ) 26 27 const ( 28 featOverride = "override" 29 featOptional = "optional" 30 featAppend = "append" 31 featWeak = "weak" 32 featBaseline = "baseline" // disables extra configs 33 featBaseConfig = "base-config" // only set for `-base.config` files 34 featModules = "modules" 35 featReduced = "reduced" 36 featClang = "clang" 37 featAndroid = "android" 38 featChromeos = "chromeos" 39 ) 40 41 func main() { 42 var ( 43 flagSourceDir = flag.String("sourcedir", "", "sourcedir") 44 flagConfig = flag.String("config", "", "config") 45 flagInstance = flag.String("instance", "", "instance") 46 ) 47 flag.Parse() 48 if *flagSourceDir == "" { 49 tool.Failf("missing mandatory flag -sourcedir") 50 } 51 repo, err := vcs.NewRepo(targets.Linux, "", *flagSourceDir, vcs.OptPrecious) 52 if err != nil { 53 tool.Failf("failed to create repo: %v", err) 54 } 55 instances, unusedFeatures, err := parseMainSpec(*flagConfig) 56 if err != nil { 57 tool.Fail(err) 58 } 59 if err := checkConfigs(instances, unusedFeatures); err != nil { 60 tool.Fail(err) 61 } 62 // In order to speed up the process we generate instances that use the same kernel revision in parallel. 63 failed := false 64 generated := make(map[string]bool) 65 for _, inst := range instances { 66 // Find the first instance that we did not generate yet. 67 if *flagInstance != "" && *flagInstance != inst.Name || generated[inst.Name] { 68 continue 69 } 70 fmt.Printf("git checkout %v %v\n", inst.Kernel.Repo, inst.Kernel.Tag) 71 if _, err := repo.SwitchCommit(inst.Kernel.Tag); err != nil { 72 if _, err := repo.CheckoutCommit(inst.Kernel.Repo, inst.Kernel.Tag); err != nil { 73 tool.Failf("failed to checkout %v/%v: %v", inst.Kernel.Repo, inst.Kernel.Tag, err) 74 } 75 } 76 releaseTag, err := releaseTag(*flagSourceDir) 77 if err != nil { 78 tool.Fail(err) 79 } 80 fmt.Printf("kernel release %v\n", releaseTag) 81 // Now generate all instances that use this kernel revision in parallel (each will use own build dir). 82 batch := 0 83 results := make(chan error) 84 for _, inst1 := range instances { 85 if *flagInstance != "" && *flagInstance != inst1.Name || generated[inst1.Name] || inst1.Kernel != inst.Kernel { 86 continue 87 } 88 fmt.Printf("generating %v...\n", inst1.Name) 89 generated[inst1.Name] = true 90 batch++ 91 ctx := &Context{ 92 Inst: inst1, 93 ConfigDir: filepath.Dir(*flagConfig), 94 SourceDir: *flagSourceDir, 95 ReleaseTag: releaseTag, 96 } 97 go func() { 98 if err := ctx.generate(); err != nil { 99 results <- fmt.Errorf("%v failed:\n%w", ctx.Inst.Name, err) 100 } 101 results <- nil 102 }() 103 } 104 for i := 0; i < batch; i++ { 105 if err := <-results; err != nil { 106 fmt.Printf("%v\n", err) 107 failed = true 108 } 109 } 110 } 111 if failed { 112 tool.Failf("some configs failed") 113 } 114 if len(generated) == 0 { 115 tool.Failf("unknown instance name") 116 } 117 } 118 119 func checkConfigs(instances []*Instance, unusedFeatures []string) error { 120 allFeatures := make(Features) 121 for _, feat := range unusedFeatures { 122 allFeatures[feat] = true 123 } 124 for _, inst := range instances { 125 for feat := range inst.Features { 126 allFeatures[feat] = true 127 } 128 } 129 dedup := make(map[string]bool) 130 errorString := "" 131 for _, inst := range instances { 132 for _, cfg := range inst.Configs { 133 if strings.HasPrefix(cfg.Name, "CONFIG_") { 134 msg := fmt.Sprintf("Warning: excessive CONFIG_ in %v at %v:%v ?", cfg.Name, cfg.File, cfg.Line) 135 errorString += "\n" + msg 136 } 137 for _, feat := range cfg.Constraints { 138 if feat[0] == '-' { 139 feat = feat[1:] 140 } 141 if allFeatures[feat] || releaseRe.MatchString(feat) { 142 continue 143 } 144 msg := fmt.Sprintf("%v:%v: unknown feature %v", cfg.File, cfg.Line, feat) 145 if dedup[msg] { 146 continue 147 } 148 dedup[msg] = true 149 errorString += "\n" + msg 150 } 151 } 152 } 153 if errorString != "" { 154 return errors.New(errorString[1:]) 155 } 156 return nil 157 } 158 159 // Generation context for a single instance. 160 type Context struct { 161 Inst *Instance 162 Target *targets.Target 163 Kconf *kconfig.KConfig 164 ConfigDir string 165 BuildDir string 166 SourceDir string 167 ReleaseTag string 168 } 169 170 func (ctx *Context) generate() error { 171 var err error 172 if ctx.BuildDir, err = os.MkdirTemp("", "syz-kconf"); err != nil { 173 return err 174 } 175 defer os.RemoveAll(ctx.BuildDir) 176 if err := ctx.setTarget(); err != nil { 177 return err 178 } 179 if ctx.Kconf, err = kconfig.Parse(ctx.Target, filepath.Join(ctx.SourceDir, "Kconfig")); err != nil { 180 return err 181 } 182 if err := ctx.setReleaseFeatures(); err != nil { 183 return err 184 } 185 if err := ctx.mrProper(); err != nil { 186 return err 187 } 188 if err := ctx.executeShell(); err != nil { 189 return err 190 } 191 configFile := filepath.Join(ctx.BuildDir, ".config") 192 cf, err := kconfig.ParseConfig(configFile) 193 if err != nil { 194 return err 195 } 196 if !ctx.Inst.Features[featBaseline] { 197 if err := ctx.addUSBConfigs(cf); err != nil { 198 return err 199 } 200 } 201 ctx.applyConfigs(cf) 202 if !ctx.Inst.Features[featModules] { 203 cf.ModToYes() 204 } 205 // Set all configs that are not present (actually not present, rather than "is not set") to "is not set". 206 // This avoids olddefconfig turning on random things we did not ask for. 207 for _, cfg := range ctx.Kconf.Configs { 208 if (cfg.Type == kconfig.TypeTristate || cfg.Type == kconfig.TypeBool) && cf.Map[cfg.Name] == nil { 209 cf.Set(cfg.Name, kconfig.No) 210 } 211 } 212 original := cf.Serialize() 213 if err := osutil.WriteFile(configFile, original); err != nil { 214 return fmt.Errorf("failed to write .config file: %w", err) 215 } 216 // Save what we've got before olddefconfig for debugging purposes, it allows to see if we did not set a config, 217 // or olddefconfig removed it. Save as .tmp so that it's not checked-in accidentially. 218 outputFile := filepath.Join(ctx.ConfigDir, ctx.Inst.Name+".config") 219 outputFileTmp := outputFile + ".tmp" 220 if err := osutil.WriteFile(outputFileTmp, original); err != nil { 221 return fmt.Errorf("failed to write tmp config file: %w", err) 222 } 223 if err := ctx.Make("olddefconfig"); err != nil { 224 return err 225 } 226 cf, err = kconfig.ParseConfig(configFile) 227 if err != nil { 228 return err 229 } 230 if err := ctx.verifyConfigs(cf); err != nil { 231 return fmt.Errorf("%w: saved config before olddefconfig to %v", err, outputFileTmp) 232 } 233 if !ctx.Inst.Features[featModules] { 234 cf.ModToNo() 235 } 236 config := []byte(fmt.Sprintf(`# Automatically generated by syz-kconf; DO NOT EDIT. 237 # Kernel: %v %v 238 239 %s 240 %s 241 `, 242 ctx.Inst.Kernel.Repo, ctx.Inst.Kernel.Tag, cf.Serialize(), ctx.Inst.Verbatim)) 243 return osutil.WriteFile(outputFile, config) 244 } 245 246 func (ctx *Context) executeShell() error { 247 envRe := regexp.MustCompile("^[A-Z0-9_]+=") 248 for _, shell := range ctx.Inst.Shell { 249 if !ctx.Inst.Features.Match(shell.Constraints) { 250 continue 251 } 252 args := strings.Split(shell.Cmd, " ") 253 for i := 1; i < len(args); i++ { 254 args[i] = ctx.replaceVars(args[i]) 255 } 256 if args[0] == "make" { 257 if err := ctx.Make(args[1:]...); err != nil { 258 return err 259 } 260 continue 261 } 262 env := os.Environ() 263 for len(args) > 1 { 264 if !envRe.MatchString(args[0]) { 265 break 266 } 267 env = append(env, args[0]) 268 args = args[1:] 269 } 270 cmd := osutil.Command(args[0], args[1:]...) 271 cmd.Dir = ctx.SourceDir 272 cmd.Env = env 273 if _, err := osutil.Run(10*time.Minute, cmd); err != nil { 274 return err 275 } 276 } 277 return nil 278 } 279 280 func (ctx *Context) applyConfigs(cf *kconfig.ConfigFile) { 281 for _, cfg := range ctx.Inst.Configs { 282 if !ctx.Inst.Features.Match(cfg.Constraints) { 283 continue 284 } 285 if cfg.Value != kconfig.No { 286 // If this is a choice, first unset all other options. 287 // If we leave 2 choice options enabled, the last one will win. 288 // It can make sense to move this code to kconfig in some form, 289 // it's needed everywhere configs are changed. 290 if m := ctx.Kconf.Configs[cfg.Name]; m != nil && m.Parent.Kind == kconfig.MenuChoice { 291 for _, choice := range m.Parent.Elems { 292 cf.Unset(choice.Name) 293 } 294 } 295 } 296 cf.Set(cfg.Name, cfg.Value) 297 } 298 } 299 300 func (ctx *Context) verifyConfigs(cf *kconfig.ConfigFile) error { 301 errs := new(Errors) 302 for _, cfg := range ctx.Inst.Configs { 303 act := cf.Value(cfg.Name) 304 if act == cfg.Value || cfg.Optional || !ctx.Inst.Features.Match(cfg.Constraints) { 305 continue 306 } 307 if act == kconfig.No { 308 errs.push("%v:%v: %v is not present in the final config", cfg.File, cfg.Line, cfg.Name) 309 } else if cfg.Value == kconfig.No { 310 errs.push("%v:%v: %v is present in the final config", cfg.File, cfg.Line, cfg.Name) 311 } else { 312 errs.push("%v:%v: %v does not match final config %v vs %v", 313 cfg.File, cfg.Line, cfg.Name, cfg.Value, act) 314 } 315 } 316 return errs.err() 317 } 318 319 func (ctx *Context) addUSBConfigs(cf *kconfig.ConfigFile) error { 320 prefix := "" 321 switch { 322 case ctx.Inst.Features[featAndroid]: 323 prefix = "android" 324 case ctx.Inst.Features[featChromeos]: 325 prefix = "chromeos" 326 } 327 distroConfig := filepath.Join(ctx.ConfigDir, "distros", prefix+"*") 328 // Some USB drivers don't depend on any USB related symbols, but rather on a generic symbol 329 // for some input subsystem (e.g. HID), so include it as well. 330 return ctx.addDependentConfigs(cf, []string{"USB_SUPPORT", "HID"}, distroConfig) 331 } 332 333 func (ctx *Context) addDependentConfigs(dst *kconfig.ConfigFile, include []string, configGlob string) error { 334 configFiles, err := filepath.Glob(configGlob) 335 if err != nil { 336 return err 337 } 338 includes := func(a []string, b map[string]bool) bool { 339 for _, x := range a { 340 if b[x] { 341 return true 342 } 343 } 344 return false 345 } 346 selected := make(map[string]bool) 347 for _, cfg := range ctx.Kconf.Configs { 348 deps := cfg.DependsOn() 349 if !includes(include, deps) { 350 continue 351 } 352 selected[cfg.Name] = true 353 for dep := range deps { 354 selected[dep] = true 355 } 356 } 357 dedup := make(map[string]bool) 358 for _, file := range configFiles { 359 cf, err := kconfig.ParseConfig(file) 360 if err != nil { 361 return err 362 } 363 for _, cfg := range cf.Configs { 364 if cfg.Value == kconfig.No || dedup[cfg.Name] || !selected[cfg.Name] { 365 continue 366 } 367 dedup[cfg.Name] = true 368 dst.Set(cfg.Name, cfg.Value) 369 } 370 } 371 return nil 372 } 373 374 func (ctx *Context) setTarget() error { 375 for _, target := range targets.List[targets.Linux] { 376 if ctx.Inst.Features[target.KernelArch] { 377 if ctx.Target != nil { 378 return fmt.Errorf("arch is set twice") 379 } 380 ctx.Target = targets.GetEx(targets.Linux, target.Arch, ctx.Inst.Features[featClang]) 381 } 382 } 383 if ctx.Target == nil { 384 return fmt.Errorf("no arch feature") 385 } 386 return nil 387 } 388 389 func (ctx *Context) setReleaseFeatures() error { 390 tag := ctx.ReleaseTag 391 match := releaseRe.FindStringSubmatch(tag) 392 if match == nil { 393 return fmt.Errorf("bad release tag %q", tag) 394 } 395 major, err := strconv.ParseInt(match[1], 10, 32) 396 if err != nil { 397 return fmt.Errorf("bad release tag %q: %w", tag, err) 398 } 399 minor, err := strconv.ParseInt(match[2], 10, 32) 400 if err != nil { 401 return fmt.Errorf("bad release tag %q: %w", tag, err) 402 } 403 for ; major >= 2; major-- { 404 for ; minor >= 0; minor-- { 405 ctx.Inst.Features[fmt.Sprintf("v%v.%v", major, minor)] = true 406 } 407 minor = 99 408 } 409 return nil 410 } 411 412 var releaseRe = regexp.MustCompile(`^v([0-9]+)\.([0-9]+)(?:-rc([0-9]+))?(?:\.([0-9]+))?$`) 413 414 func (ctx *Context) mrProper() error { 415 // Run 'make mrproper', otherwise out-of-tree build fails. 416 // However, it takes unreasonable amount of time, 417 // so first check few files and if they are missing hope for best. 418 files := []string{ 419 ".config", 420 "init/main.o", 421 "include/config", 422 "include/generated/compile.h", 423 "arch/" + ctx.Target.KernelArch + "/include/generated", 424 } 425 for _, file := range files { 426 if osutil.IsExist(filepath.Join(ctx.SourceDir, filepath.FromSlash(file))) { 427 goto clean 428 } 429 } 430 return nil 431 clean: 432 buildDir := ctx.BuildDir 433 ctx.BuildDir = ctx.SourceDir 434 err := ctx.Make("mrproper") 435 ctx.BuildDir = buildDir 436 return err 437 } 438 439 func (ctx *Context) Make(args ...string) error { 440 args = append(args, 441 "O="+ctx.BuildDir, 442 "ARCH="+ctx.Target.KernelArch, 443 "-j", fmt.Sprint(runtime.NumCPU()), 444 ) 445 if ctx.Target.Triple != "" { 446 args = append(args, "CROSS_COMPILE="+ctx.Target.Triple+"-") 447 } 448 if ctx.Inst.Compiler != "" { 449 args = append(args, "CC="+ctx.replaceVars(ctx.Inst.Compiler)) 450 } else if ctx.Target.KernelCompiler != "" { 451 args = append(args, "CC="+ctx.Target.KernelCompiler) 452 } 453 if ctx.Inst.Linker != "" { 454 args = append(args, "LD="+ctx.replaceVars(ctx.Inst.Linker)) 455 } 456 _, err := osutil.RunCmd(10*time.Minute, ctx.SourceDir, "make", args...) 457 return err 458 } 459 460 func (ctx *Context) replaceVars(str string) string { 461 str = strings.ReplaceAll(str, "${SOURCEDIR}", ctx.SourceDir) 462 str = strings.ReplaceAll(str, "${BUILDDIR}", ctx.BuildDir) 463 str = strings.ReplaceAll(str, "${ARCH}", ctx.Target.KernelArch) 464 return str 465 } 466 467 func releaseTag(dir string) (string, error) { 468 data, err := os.ReadFile(filepath.Join(dir, "Makefile")) 469 if err != nil { 470 return "", err 471 } 472 return releaseTagImpl(data) 473 } 474 475 func releaseTagImpl(data []byte) (string, error) { 476 match := makefileReleaseRe.FindSubmatch(data) 477 if match == nil { 478 return "", fmt.Errorf("did not find VERSION/PATCHLEVEL in the kernel Makefile") 479 } 480 return fmt.Sprintf("v%s.%s", match[1], match[2]), nil 481 } 482 483 var makefileReleaseRe = regexp.MustCompile(`\nVERSION = ([0-9]+)(?:\n.*)*\nPATCHLEVEL = ([0-9]+)\n`)