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 }