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  }