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