diff options
| author | Dmitry Vyukov <dvyukov@google.com> | 2016-08-30 14:33:39 +0200 |
|---|---|---|
| committer | Dmitry Vyukov <dvyukov@google.com> | 2016-08-30 14:33:39 +0200 |
| commit | bc9b349bd77c6ccc224a8e7cf72e76595bfc0342 (patch) | |
| tree | fd5e3f485dbd6ee40caa52038a117aa9e26a2359 | |
| parent | 26a5cf9efa8d0fc4ffbf2f9810241d4fdead8e16 (diff) | |
vm/adb: support multiple adb devices
Device IDs are specified in "devices" config param.
| -rw-r--r-- | config/config.go | 71 | ||||
| -rw-r--r-- | syz-manager/manager.go | 6 | ||||
| -rw-r--r-- | tools/syz-repro/repro.go | 15 | ||||
| -rw-r--r-- | vm/adb/adb.go | 57 | ||||
| -rw-r--r-- | vm/vm.go | 28 |
5 files changed, 119 insertions, 58 deletions
diff --git a/config/config.go b/config/config.go index b98b481db..83e92a25b 100644 --- a/config/config.go +++ b/config/config.go @@ -34,10 +34,11 @@ type Config struct { Debug bool // dump all VM output to console Output string // one of stdout/dmesg/file (useful only for local VM) - Syzkaller string // path to syzkaller checkout (syz-manager will look for binaries in bin subdir) - Type string // VM type (qemu, kvm, local) - Count int // number of VMs - Procs int // number of parallel processes inside of every VM + Syzkaller string // path to syzkaller checkout (syz-manager will look for binaries in bin subdir) + Type string // VM type (qemu, kvm, local) + Count int // number of VMs (don't secify for adb, instead specify devices) + Devices []string // device IDs for adb + Procs int // number of parallel processes inside of every VM Sandbox string // type of sandbox to use during fuzzing: // "none": don't do anything special (has false positives, e.g. due to killing init) @@ -48,8 +49,6 @@ type Config struct { Cover bool // use kcov coverage (default: true) Leak bool // do memory leak checking - ConsoleDev string // console device for adb vm - Enable_Syscalls []string Disable_Syscalls []string Suppressions []string @@ -98,21 +97,36 @@ func parse(data []byte) (*Config, map[int]bool, []*regexp.Regexp, error) { if cfg.Type == "" { return nil, nil, nil, fmt.Errorf("config param type is empty") } - if cfg.Type == "none" { + switch cfg.Type { + case "none": if cfg.Count != 0 { return nil, nil, nil, fmt.Errorf("invalid config param count: %v, type \"none\" does not support param count", cfg.Count) } if cfg.Rpc == "" { return nil, nil, nil, fmt.Errorf("config param rpc is empty (required for type \"none\")") } - } else { + if len(cfg.Devices) != 0 { + return nil, nil, nil, fmt.Errorf("type %v does not support devices param", cfg.Type) + } + case "adb": + if cfg.Count != 0 { + return nil, nil, nil, fmt.Errorf("don't specify count for adb, instead specify devices") + } + if len(cfg.Devices) == 0 { + return nil, nil, nil, fmt.Errorf("specify at least 1 adb device") + } + cfg.Count = len(cfg.Devices) + default: if cfg.Count <= 0 || cfg.Count > 1000 { return nil, nil, nil, fmt.Errorf("invalid config param count: %v, want (1, 1000]", cfg.Count) } - if cfg.Rpc == "" { - cfg.Rpc = "localhost:0" + if len(cfg.Devices) != 0 { + return nil, nil, nil, fmt.Errorf("type %v does not support devices param", cfg.Type) } } + if cfg.Rpc == "" { + cfg.Rpc = "localhost:0" + } if cfg.Procs <= 0 { cfg.Procs = 1 } @@ -219,26 +233,31 @@ func parseSuppressions(cfg *Config) ([]*regexp.Regexp, error) { return suppressions, nil } -func CreateVMConfig(cfg *Config) (*vm.Config, error) { +func CreateVMConfig(cfg *Config, index int) (*vm.Config, error) { + if index < 0 || index >= cfg.Count { + return nil, fmt.Errorf("invalid VM index %v (count %v)", index, cfg.Count) + } workdir, index, err := fileutil.ProcessTempDir(cfg.Workdir) if err != nil { return nil, fmt.Errorf("failed to create instance temp dir: %v", err) } vmCfg := &vm.Config{ - Name: fmt.Sprintf("%v-%v", cfg.Type, index), - Index: index, - Workdir: workdir, - Bin: cfg.Bin, - Kernel: cfg.Kernel, - Cmdline: cfg.Cmdline, - Image: cfg.Image, - Initrd: cfg.Initrd, - Sshkey: cfg.Sshkey, - Executor: filepath.Join(cfg.Syzkaller, "bin", "syz-executor"), - ConsoleDev: cfg.ConsoleDev, - Cpu: cfg.Cpu, - Mem: cfg.Mem, - Debug: cfg.Debug, + Name: fmt.Sprintf("%v-%v", cfg.Type, index), + Index: index, + Workdir: workdir, + Bin: cfg.Bin, + Kernel: cfg.Kernel, + Cmdline: cfg.Cmdline, + Image: cfg.Image, + Initrd: cfg.Initrd, + Sshkey: cfg.Sshkey, + Executor: filepath.Join(cfg.Syzkaller, "bin", "syz-executor"), + Cpu: cfg.Cpu, + Mem: cfg.Mem, + Debug: cfg.Debug, + } + if len(cfg.Devices) != 0 { + vmCfg.Device = cfg.Devices[index] } return vmCfg, nil } @@ -264,11 +283,11 @@ func checkUnknownFields(data []byte) (string, error) { "Syzkaller", "Type", "Count", + "Devices", "Procs", "Cover", "Sandbox", "Leak", - "ConsoleDev", "Enable_Syscalls", "Disable_Syscalls", "Suppressions", diff --git a/syz-manager/manager.go b/syz-manager/manager.go index b88956583..fa457a2c8 100644 --- a/syz-manager/manager.go +++ b/syz-manager/manager.go @@ -166,18 +166,18 @@ func RunManager(cfg *config.Config, syscalls map[int]bool, suppressions []*regex var wg sync.WaitGroup wg.Add(cfg.Count + 1) for i := 0; i < cfg.Count; i++ { - first := i == 0 + i := i go func() { defer wg.Done() for { - vmCfg, err := config.CreateVMConfig(cfg) + vmCfg, err := config.CreateVMConfig(cfg, i) if atomic.LoadUint32(&shutdown) != 0 { break } if err != nil { fatalf("failed to create VM config: %v", err) } - ok := mgr.runInstance(vmCfg, first) + ok := mgr.runInstance(vmCfg, i == 0) if atomic.LoadUint32(&shutdown) != 0 { break } diff --git a/tools/syz-repro/repro.go b/tools/syz-repro/repro.go index 63adad52c..1e616c986 100644 --- a/tools/syz-repro/repro.go +++ b/tools/syz-repro/repro.go @@ -30,12 +30,13 @@ var ( flagCount = flag.Int("count", 0, "number of VMs to use (overrides config count param)") instances chan VM - bootRequests chan bool + bootRequests chan int shutdown = make(chan struct{}) ) type VM struct { vm.Instance + index int execprogBin string executorBin string } @@ -70,12 +71,12 @@ func main() { log.Printf("target crash: '%s'", crashDesc) instances = make(chan VM, cfg.Count) - bootRequests = make(chan bool, cfg.Count) + bootRequests = make(chan int, cfg.Count) for i := 0; i < cfg.Count; i++ { - bootRequests <- true + bootRequests <- i go func() { - for range bootRequests { - vmCfg, err := config.CreateVMConfig(cfg) + for index := range bootRequests { + vmCfg, err := config.CreateVMConfig(cfg, index) if err != nil { log.Fatalf("failed to create VM config: %v", err) } @@ -91,7 +92,7 @@ func main() { if err != nil { log.Fatalf("failed to copy to VM: %v", err) } - instances <- VM{inst, execprogBin, executorBin} + instances <- VM{inst, index, execprogBin, executorBin} } }() } @@ -197,7 +198,7 @@ func repro(cfg *config.Config, entries []*prog.LogEntry, crashStart int) { func returnInstance(inst VM, res bool) { if res { // The test crashed, discard the VM and issue another boot request. - bootRequests <- true + bootRequests <- inst.index inst.Close() } else { // The test did not crash, reuse the same VM in future. diff --git a/vm/adb/adb.go b/vm/adb/adb.go index 91e59d519..e8090eddb 100644 --- a/vm/adb/adb.go +++ b/vm/adb/adb.go @@ -11,6 +11,9 @@ import ( "os" "os/exec" "path/filepath" + "regexp" + "strconv" + "sync" "time" "github.com/google/syzkaller/vm" @@ -21,8 +24,9 @@ func init() { } type instance struct { - cfg *vm.Config - closed chan bool + cfg *vm.Config + console string + closed chan bool } func ctor(cfg *vm.Config) (vm.Instance, error) { @@ -39,6 +43,9 @@ func ctor(cfg *vm.Config) (vm.Instance, error) { if err := validateConfig(cfg); err != nil { return nil, err } + if err := inst.findConsole(); err != nil { + return nil, err + } if err := inst.repair(); err != nil { return nil, err } @@ -52,9 +59,43 @@ func validateConfig(cfg *vm.Config) error { if cfg.Bin == "" { cfg.Bin = "adb" } - if _, err := os.Stat(cfg.ConsoleDev); err != nil { - return fmt.Errorf("console device '%v' is missing: %v", cfg.ConsoleDev, err) + if !regexp.MustCompile("[0-9A-F]+").MatchString(cfg.Device) { + return fmt.Errorf("invalid adb device id '%v'", cfg.Device) + } + return nil +} + +var ( + consoleCacheMu sync.Mutex + consoleCache = make(map[string]string) +) + +func (inst *instance) findConsole() error { + // Case Closed Debugging using Suzy-Q: + // https://chromium.googlesource.com/chromiumos/platform/ec/+/master/docs/case_closed_debugging.md + consoleCacheMu.Lock() + defer consoleCacheMu.Unlock() + if inst.console = consoleCache[inst.cfg.Device]; inst.console != "" { + return nil + } + out, err := exec.Command(inst.cfg.Bin, "devices", "-l").CombinedOutput() + if err != nil { + return fmt.Errorf("failed to execute 'adb devices -l': %v\n%v\n", err, string(out)) + } + re := regexp.MustCompile(fmt.Sprintf("%v +device usb:([0-9]+)-([0-9]+)\\.([0-9]+) ", inst.cfg.Device)) + match := re.FindAllStringSubmatch(string(out), 1) + if match == nil { + return fmt.Errorf("can't find adb device '%v' in 'adb devices' output:\n%v\n", inst.cfg.Device, string(out)) + } + bus, _ := strconv.ParseUint(match[0][1], 10, 64) + port, _ := strconv.ParseUint(match[0][2], 10, 64) + files, err := filepath.Glob(fmt.Sprintf("/sys/bus/usb/devices/%v-%v.2:1.1/ttyUSB*", bus, port)) + if err != nil || len(files) == 0 { + return fmt.Errorf("can't find any ttyUDB devices for adb device '%v' on bus %v-%v", inst.cfg.Device, bus, port) } + inst.console = "/dev/" + filepath.Base(files[0]) + consoleCache[inst.cfg.Device] = inst.console + log.Printf("associating adb device %v with console %v", inst.cfg.Device, inst.console) return nil } @@ -77,7 +118,7 @@ func (inst *instance) adb(args ...string) error { } defer wpipe.Close() defer rpipe.Close() - cmd := exec.Command(inst.cfg.Bin, args...) + cmd := exec.Command(inst.cfg.Bin, append([]string{"-s", inst.cfg.Device}, args...)...) cmd.Stdout = wpipe cmd.Stderr = wpipe if err := cmd.Start(); err != nil { @@ -158,13 +199,13 @@ func (inst *instance) Run(timeout time.Duration, command string) (<-chan []byte, return nil, nil, err } - cat := exec.Command("cat", inst.cfg.ConsoleDev) + cat := exec.Command("cat", inst.console) cat.Stdout = catWpipe cat.Stderr = catWpipe if err := cat.Start(); err != nil { catRpipe.Close() catWpipe.Close() - return nil, nil, fmt.Errorf("failed to start cat %v: %v", inst.cfg.ConsoleDev, err) + return nil, nil, fmt.Errorf("failed to start cat %v: %v", inst.console, err) } catWpipe.Close() @@ -186,7 +227,7 @@ func (inst *instance) Run(timeout time.Duration, command string) (<-chan []byte, if inst.cfg.Debug { log.Printf("starting: adb shell %v", command) } - adb := exec.Command(inst.cfg.Bin, "shell", "cd /data; "+command) + adb := exec.Command(inst.cfg.Bin, "-s", inst.cfg.Device, "shell", "cd /data; "+command) adb.Stdout = adbWpipe adb.Stderr = adbWpipe if err := adb.Start(); err != nil { @@ -32,20 +32,20 @@ type Instance interface { } type Config struct { - Name string - Index int - Workdir string - Bin string - Initrd string - Kernel string - Cmdline string - Image string - Sshkey string - Executor string - ConsoleDev string - Cpu int - Mem int - Debug bool + Name string + Index int + Workdir string + Bin string + Initrd string + Kernel string + Cmdline string + Image string + Sshkey string + Executor string + Device string + Cpu int + Mem int + Debug bool } type ctorFunc func(cfg *Config) (Instance, error) |
