github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/csource/options.go (about)

     1  // Copyright 2017 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 csource
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/json"
     9  	"errors"
    10  	"fmt"
    11  	"sort"
    12  	"strings"
    13  
    14  	"github.com/google/syzkaller/pkg/flatrpc"
    15  	"github.com/google/syzkaller/pkg/mgrconfig"
    16  	"github.com/google/syzkaller/sys/targets"
    17  )
    18  
    19  // Options control various aspects of source generation.
    20  // Dashboard also provides serialized Options along with syzkaller reproducers.
    21  type Options struct {
    22  	Threaded    bool   `json:"threaded,omitempty"`
    23  	Repeat      bool   `json:"repeat,omitempty"`
    24  	RepeatTimes int    `json:"repeat_times,omitempty"` // if non-0, repeat that many times
    25  	Procs       int    `json:"procs"`
    26  	Slowdown    int    `json:"slowdown"`
    27  	Sandbox     string `json:"sandbox"`
    28  	SandboxArg  int    `json:"sandbox_arg"`
    29  
    30  	Leak bool `json:"leak,omitempty"` // do leak checking
    31  
    32  	// These options allow for a more fine-tuned control over the generated C code.
    33  	NetInjection  bool `json:"tun,omitempty"`
    34  	NetDevices    bool `json:"netdev,omitempty"`
    35  	NetReset      bool `json:"resetnet,omitempty"`
    36  	Cgroups       bool `json:"cgroups,omitempty"`
    37  	BinfmtMisc    bool `json:"binfmt_misc,omitempty"`
    38  	CloseFDs      bool `json:"close_fds"`
    39  	KCSAN         bool `json:"kcsan,omitempty"`
    40  	DevlinkPCI    bool `json:"devlinkpci,omitempty"`
    41  	NicVF         bool `json:"nicvf,omitempty"`
    42  	USB           bool `json:"usb,omitempty"`
    43  	VhciInjection bool `json:"vhci,omitempty"`
    44  	Wifi          bool `json:"wifi,omitempty"`
    45  	IEEE802154    bool `json:"ieee802154,omitempty"`
    46  	Sysctl        bool `json:"sysctl,omitempty"`
    47  	Swap          bool `json:"swap,omitempty"`
    48  
    49  	UseTmpDir  bool `json:"tmpdir,omitempty"`
    50  	HandleSegv bool `json:"segv,omitempty"`
    51  
    52  	Trace bool `json:"trace,omitempty"`
    53  
    54  	CallComments bool `json:"callcomments,omitempty"`
    55  
    56  	LegacyOptions
    57  }
    58  
    59  // These are legacy options, they remain only for the sake of backward compatibility.
    60  type LegacyOptions struct {
    61  	Collide   bool `json:"collide,omitempty"`
    62  	Fault     bool `json:"fault,omitempty"`
    63  	FaultCall int  `json:"fault_call,omitempty"`
    64  	FaultNth  int  `json:"fault_nth,omitempty"`
    65  }
    66  
    67  // Check checks if the opts combination is valid or not.
    68  // For example, Collide without Threaded is not valid.
    69  // Invalid combinations must not be passed to Write.
    70  func (opts Options) Check(OS string) error {
    71  	switch opts.Sandbox {
    72  	case "", sandboxNone, sandboxNamespace, sandboxSetuid, sandboxAndroid:
    73  	default:
    74  		return fmt.Errorf("unknown sandbox %v", opts.Sandbox)
    75  	}
    76  	if !opts.Threaded && opts.Collide {
    77  		// Collide requires threaded.
    78  		return errors.New("option Collide without Threaded")
    79  	}
    80  	if !opts.Repeat {
    81  		if opts.Procs > 1 {
    82  			// This does not affect generated code.
    83  			return errors.New("option Procs>1 without Repeat")
    84  		}
    85  		if opts.NetReset {
    86  			return errors.New("option NetReset without Repeat")
    87  		}
    88  		if opts.RepeatTimes > 1 {
    89  			return errors.New("option RepeatTimes without Repeat")
    90  		}
    91  	}
    92  	if opts.Sandbox == "" {
    93  		if opts.NetInjection {
    94  			return errors.New("option NetInjection without sandbox")
    95  		}
    96  		if opts.NetDevices {
    97  			return errors.New("option NetDevices without sandbox")
    98  		}
    99  		if opts.Cgroups {
   100  			return errors.New("option Cgroups without sandbox")
   101  		}
   102  		if opts.BinfmtMisc {
   103  			return errors.New("option BinfmtMisc without sandbox")
   104  		}
   105  		if opts.VhciInjection {
   106  			return errors.New("option VhciInjection without sandbox")
   107  		}
   108  		if opts.Wifi {
   109  			return errors.New("option Wifi without sandbox")
   110  		}
   111  	}
   112  	if opts.Sandbox == sandboxNamespace && !opts.UseTmpDir {
   113  		// This is borken and never worked.
   114  		// This tries to create syz-tmp dir in cwd,
   115  		// which will fail if procs>1 and on second run of the program.
   116  		return errors.New("option Sandbox=namespace without UseTmpDir")
   117  	}
   118  	if opts.NetReset && (opts.Sandbox == "" || opts.Sandbox == sandboxSetuid) {
   119  		return errors.New("option NetReset without sandbox")
   120  	}
   121  	if opts.Cgroups && !opts.UseTmpDir {
   122  		return errors.New("option Cgroups without UseTmpDir")
   123  	}
   124  	return opts.checkLinuxOnly(OS)
   125  }
   126  
   127  func (opts Options) checkLinuxOnly(OS string) error {
   128  	if OS == targets.Linux {
   129  		return nil
   130  	}
   131  	if opts.NetInjection && OS != targets.OpenBSD && OS != targets.FreeBSD && OS != targets.NetBSD {
   132  		return fmt.Errorf("option NetInjection is not supported on %v", OS)
   133  	}
   134  	if opts.Sandbox == sandboxNamespace ||
   135  		(opts.Sandbox == sandboxSetuid && OS != targets.OpenBSD && OS != targets.FreeBSD && OS != targets.NetBSD) ||
   136  		opts.Sandbox == sandboxAndroid {
   137  		return fmt.Errorf("option Sandbox=%v is not supported on %v", opts.Sandbox, OS)
   138  	}
   139  	for name, opt := range map[string]*bool{
   140  		"NetDevices":    &opts.NetDevices,
   141  		"NetReset":      &opts.NetReset,
   142  		"Cgroups":       &opts.Cgroups,
   143  		"BinfmtMisc":    &opts.BinfmtMisc,
   144  		"CloseFDs":      &opts.CloseFDs,
   145  		"KCSAN":         &opts.KCSAN,
   146  		"DevlinkPCI":    &opts.DevlinkPCI,
   147  		"NicVF":         &opts.NicVF,
   148  		"USB":           &opts.USB,
   149  		"VhciInjection": &opts.VhciInjection,
   150  		"Wifi":          &opts.Wifi,
   151  		"ieee802154":    &opts.IEEE802154,
   152  		"Fault":         &opts.Fault,
   153  		"Leak":          &opts.Leak,
   154  		"Sysctl":        &opts.Sysctl,
   155  		"Swap":          &opts.Swap,
   156  	} {
   157  		if *opt {
   158  			return fmt.Errorf("option %v is not supported on %v", name, OS)
   159  		}
   160  	}
   161  	return nil
   162  }
   163  
   164  func DefaultOpts(cfg *mgrconfig.Config) Options {
   165  	opts := Options{
   166  		Threaded:     true,
   167  		Repeat:       true,
   168  		Procs:        cfg.Procs,
   169  		Slowdown:     cfg.Timeouts.Slowdown,
   170  		Sandbox:      cfg.Sandbox,
   171  		UseTmpDir:    true,
   172  		HandleSegv:   true,
   173  		CallComments: true,
   174  	}
   175  	if cfg.TargetOS == targets.Linux {
   176  		opts.NetInjection = true
   177  		opts.NetDevices = true
   178  		opts.NetReset = true
   179  		opts.Cgroups = true
   180  		opts.BinfmtMisc = true
   181  		opts.CloseFDs = true
   182  		opts.DevlinkPCI = true
   183  		opts.NicVF = true
   184  		opts.USB = true
   185  		opts.VhciInjection = true
   186  		opts.Wifi = true
   187  		opts.IEEE802154 = true
   188  		opts.Sysctl = true
   189  		opts.Swap = true
   190  	}
   191  	if cfg.Sandbox == "" || cfg.Sandbox == "setuid" {
   192  		opts.NetReset = false
   193  	}
   194  	if err := opts.Check(cfg.TargetOS); err != nil {
   195  		panic(fmt.Sprintf("DefaultOpts created bad opts: %v", err))
   196  	}
   197  	return opts
   198  }
   199  
   200  func (opts Options) Serialize() []byte {
   201  	data, err := json.Marshal(opts)
   202  	if err != nil {
   203  		panic(err)
   204  	}
   205  	return data
   206  }
   207  
   208  func deserializeLegacyOptions(data string, opts *Options) (int, error) {
   209  	ignoreBool := true
   210  	keyToTarget := map[string]any{
   211  		"Threaded":      &opts.Threaded,
   212  		"Collide":       &opts.Collide,
   213  		"Repeat":        &opts.Repeat,
   214  		"Procs":         &opts.Procs,
   215  		"Sandbox":       &opts.Sandbox,
   216  		"SandboxArg":    &opts.SandboxArg,
   217  		"Fault":         &opts.Fault,
   218  		"FaultCall":     &opts.FaultCall,
   219  		"FaultNth":      &opts.FaultNth,
   220  		"EnableTun":     &opts.NetInjection,
   221  		"UseTmpDir":     &opts.UseTmpDir,
   222  		"EnableCgroups": &opts.Cgroups,
   223  		"HandleSegv":    &opts.HandleSegv,
   224  		"WaitRepeat":    &ignoreBool,
   225  		"Debug":         &ignoreBool,
   226  		"Repro":         &ignoreBool,
   227  	}
   228  
   229  	data = strings.TrimSpace(data)
   230  	data = strings.TrimPrefix(data, "{")
   231  	data = strings.TrimSuffix(data, "}")
   232  	totalRead := 0
   233  	for _, token := range strings.Fields(data) {
   234  		key, value, keyValueFound := strings.Cut(token, ":")
   235  		if !keyValueFound {
   236  			return totalRead, fmt.Errorf("error splitting options token %v", token)
   237  		}
   238  		if _, ok := keyToTarget[key]; !ok {
   239  			return totalRead, fmt.Errorf("error, unexpected option key %v", key)
   240  		}
   241  		dest := keyToTarget[key]
   242  		n, err := fmt.Sscanf(value, "%v", dest)
   243  		if err != nil {
   244  			return totalRead, fmt.Errorf("failed to read %v", value)
   245  		}
   246  		totalRead += n
   247  		delete(keyToTarget, key)
   248  	}
   249  
   250  	return totalRead, nil
   251  }
   252  
   253  // Support for legacy formats.
   254  func deserializeLegacyFormats(data []byte, opts *Options) error {
   255  	data = bytes.ReplaceAll(data, []byte("Sandbox: "), []byte("Sandbox:empty "))
   256  	strData := string(data)
   257  
   258  	// We can distinguish between legacy formats by the number
   259  	// of fields. The formats we support have 14, 15 and 16 fields.
   260  	fieldsFound, err := deserializeLegacyOptions(strData, opts)
   261  	if err != nil {
   262  		return fmt.Errorf("failed to parse '%v': %w", strData, err)
   263  	}
   264  	if fieldsFound < 14 || fieldsFound > 16 {
   265  		return fmt.Errorf("%v params found, expected 14 <= x <= 16", fieldsFound)
   266  	}
   267  
   268  	if opts.Sandbox == "empty" {
   269  		opts.Sandbox = ""
   270  	}
   271  	return err
   272  }
   273  
   274  func DeserializeOptions(data []byte) (Options, error) {
   275  	opts := Options{
   276  		Slowdown: 1,
   277  		// Before CloseFDs was added, close_fds() was always called, so default to true.
   278  		CloseFDs: true,
   279  	}
   280  	if err := json.Unmarshal(data, &opts); err == nil {
   281  		return opts, nil
   282  	}
   283  	err := deserializeLegacyFormats(data, &opts)
   284  	return opts, err
   285  }
   286  
   287  type Feature struct {
   288  	Description string
   289  	Enabled     bool
   290  }
   291  
   292  type Features map[string]Feature
   293  
   294  func defaultFeatures(value bool) Features {
   295  	return map[string]Feature{
   296  		"tun":         {"setup and use /dev/tun for packet injection", value},
   297  		"net_dev":     {"setup more network devices for testing", value},
   298  		"net_reset":   {"reset network namespace between programs", value},
   299  		"cgroups":     {"setup cgroups for testing", value},
   300  		"binfmt_misc": {"setup binfmt_misc for testing", value},
   301  		"close_fds":   {"close fds after each program", value},
   302  		"devlink_pci": {"setup devlink PCI device", value},
   303  		"nic_vf":      {"setup NIC VF device", value},
   304  		"usb":         {"setup and use /dev/raw-gadget for USB emulation", value},
   305  		"vhci":        {"setup and use /dev/vhci for hci packet injection", value},
   306  		"wifi":        {"setup and use mac80211_hwsim for wifi emulation", value},
   307  		"ieee802154":  {"setup and use mac802154_hwsim for emulation", value},
   308  		"sysctl":      {"setup sysctl's for fuzzing", value},
   309  		"swap":        {"setup and use a swap file", value},
   310  	}
   311  }
   312  
   313  func ParseFeaturesFlags(enable, disable string, defaultValue bool) (Features, error) {
   314  	const (
   315  		none = "none"
   316  		all  = "all"
   317  	)
   318  	if enable == none && disable == none {
   319  		return defaultFeatures(defaultValue), nil
   320  	}
   321  	if enable != none && disable != none {
   322  		return nil, fmt.Errorf("can't use -enable and -disable flags at the same time")
   323  	}
   324  	if enable == all || disable == "" {
   325  		return defaultFeatures(true), nil
   326  	}
   327  	if disable == all || enable == "" {
   328  		return defaultFeatures(false), nil
   329  	}
   330  	var items []string
   331  	var features Features
   332  	if enable != none {
   333  		items = strings.Split(enable, ",")
   334  		features = defaultFeatures(false)
   335  	} else {
   336  		items = strings.Split(disable, ",")
   337  		features = defaultFeatures(true)
   338  	}
   339  	for _, item := range items {
   340  		if _, ok := features[item]; !ok {
   341  			return nil, fmt.Errorf("unknown feature specified: %s", item)
   342  		}
   343  		feature := features[item]
   344  		feature.Enabled = enable != none
   345  		features[item] = feature
   346  	}
   347  	return features, nil
   348  }
   349  
   350  func PrintAvailableFeaturesFlags() {
   351  	fmt.Printf("available features for -enable and -disable:\n")
   352  	features := defaultFeatures(false)
   353  	var names []string
   354  	for name := range features {
   355  		names = append(names, name)
   356  	}
   357  	sort.Strings(names)
   358  	for _, name := range names {
   359  		fmt.Printf("  %s - %s\n", name, features[name].Description)
   360  	}
   361  }
   362  
   363  // This is the main configuration used by executor, only for testing.
   364  var ExecutorOpts = Options{
   365  	Threaded:  true,
   366  	Repeat:    true,
   367  	Procs:     2,
   368  	Slowdown:  1,
   369  	Sandbox:   "none",
   370  	UseTmpDir: true,
   371  }
   372  
   373  func FeaturesToFlags(features flatrpc.Feature, manual Features) flatrpc.ExecEnv {
   374  	for feat := range flatrpc.EnumNamesFeature {
   375  		opt := FlatRPCFeaturesToCSource[feat]
   376  		if opt != "" && manual != nil && !manual[opt].Enabled {
   377  			features &= ^feat
   378  		}
   379  	}
   380  	var flags flatrpc.ExecEnv
   381  	if manual == nil || manual["net_reset"].Enabled {
   382  		flags |= flatrpc.ExecEnvEnableNetReset
   383  	}
   384  	if manual == nil || manual["cgroups"].Enabled {
   385  		flags |= flatrpc.ExecEnvEnableCgroups
   386  	}
   387  	if manual == nil || manual["close_fds"].Enabled {
   388  		flags |= flatrpc.ExecEnvEnableCloseFds
   389  	}
   390  	if features&flatrpc.FeatureExtraCoverage != 0 {
   391  		flags |= flatrpc.ExecEnvExtraCover
   392  	}
   393  	if features&flatrpc.FeatureDelayKcovMmap != 0 {
   394  		flags |= flatrpc.ExecEnvDelayKcovMmap
   395  	}
   396  	if features&flatrpc.FeatureNetInjection != 0 {
   397  		flags |= flatrpc.ExecEnvEnableTun
   398  	}
   399  	if features&flatrpc.FeatureNetDevices != 0 {
   400  		flags |= flatrpc.ExecEnvEnableNetDev
   401  	}
   402  	if features&flatrpc.FeatureDevlinkPCI != 0 {
   403  		flags |= flatrpc.ExecEnvEnableDevlinkPCI
   404  	}
   405  	if features&flatrpc.FeatureNicVF != 0 {
   406  		flags |= flatrpc.ExecEnvEnableNicVF
   407  	}
   408  	if features&flatrpc.FeatureVhciInjection != 0 {
   409  		flags |= flatrpc.ExecEnvEnableVhciInjection
   410  	}
   411  	if features&flatrpc.FeatureWifiEmulation != 0 {
   412  		flags |= flatrpc.ExecEnvEnableWifi
   413  	}
   414  	return flags
   415  }
   416  
   417  var FlatRPCFeaturesToCSource = map[flatrpc.Feature]string{
   418  	flatrpc.FeatureNetInjection:    "tun",
   419  	flatrpc.FeatureNetDevices:      "net_dev",
   420  	flatrpc.FeatureDevlinkPCI:      "devlink_pci",
   421  	flatrpc.FeatureNicVF:           "nic_vf",
   422  	flatrpc.FeatureVhciInjection:   "vhci",
   423  	flatrpc.FeatureWifiEmulation:   "wifi",
   424  	flatrpc.FeatureUSBEmulation:    "usb",
   425  	flatrpc.FeatureBinFmtMisc:      "binfmt_misc",
   426  	flatrpc.FeatureLRWPANEmulation: "ieee802154",
   427  	flatrpc.FeatureSwap:            "swap",
   428  }