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