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`)