1// Copyright 2018 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 instance provides helper functions for creation of temporal instances 5// used for testing of images, patches and bisection. 6package instance 7 8import ( 9 "bytes" 10 "encoding/json" 11 "fmt" 12 "net" 13 "os" 14 "path/filepath" 15 "strings" 16 "time" 17 18 "github.com/google/syzkaller/pkg/build" 19 "github.com/google/syzkaller/pkg/csource" 20 "github.com/google/syzkaller/pkg/log" 21 "github.com/google/syzkaller/pkg/mgrconfig" 22 "github.com/google/syzkaller/pkg/osutil" 23 "github.com/google/syzkaller/pkg/report" 24 "github.com/google/syzkaller/pkg/vcs" 25 "github.com/google/syzkaller/prog" 26 "github.com/google/syzkaller/vm" 27) 28 29type Env struct { 30 cfg *mgrconfig.Config 31} 32 33func NewEnv(cfg *mgrconfig.Config) (*Env, error) { 34 switch cfg.Type { 35 case "gce", "qemu", "gvisor": 36 default: 37 return nil, fmt.Errorf("test instances can only work with qemu/gce") 38 } 39 if cfg.Workdir == "" { 40 return nil, fmt.Errorf("workdir path is empty") 41 } 42 if cfg.KernelSrc == "" { 43 return nil, fmt.Errorf("kernel src path is empty") 44 } 45 if cfg.Syzkaller == "" { 46 return nil, fmt.Errorf("syzkaller path is empty") 47 } 48 if err := osutil.MkdirAll(cfg.Workdir); err != nil { 49 return nil, fmt.Errorf("failed to create tmp dir: %v", err) 50 } 51 env := &Env{ 52 cfg: cfg, 53 } 54 return env, nil 55} 56 57func (env *Env) BuildSyzkaller(repo, commit string) error { 58 cfg := env.cfg 59 srcIndex := strings.LastIndex(cfg.Syzkaller, "/src/") 60 if srcIndex == -1 { 61 return fmt.Errorf("syzkaller path %q is not in GOPATH", cfg.Syzkaller) 62 } 63 if _, err := vcs.NewSyzkallerRepo(cfg.Syzkaller).CheckoutCommit(repo, commit); err != nil { 64 return fmt.Errorf("failed to checkout syzkaller repo: %v", err) 65 } 66 cmd := osutil.Command("make", "target") 67 cmd.Dir = cfg.Syzkaller 68 cmd.Env = append([]string{}, os.Environ()...) 69 cmd.Env = append(cmd.Env, 70 "GOPATH="+cfg.Syzkaller[:srcIndex], 71 "TARGETOS="+cfg.TargetOS, 72 "TARGETVMARCH="+cfg.TargetVMArch, 73 "TARGETARCH="+cfg.TargetArch, 74 ) 75 if _, err := osutil.Run(time.Hour, cmd); err != nil { 76 return fmt.Errorf("syzkaller build failed: %v", err) 77 } 78 return nil 79} 80 81func (env *Env) BuildKernel(compilerBin, userspaceDir, cmdlineFile, sysctlFile string, kernelConfig []byte) error { 82 cfg := env.cfg 83 imageDir := filepath.Join(cfg.Workdir, "image") 84 if err := build.Image(cfg.TargetOS, cfg.TargetVMArch, cfg.Type, 85 cfg.KernelSrc, imageDir, compilerBin, userspaceDir, 86 cmdlineFile, sysctlFile, kernelConfig); err != nil { 87 return err 88 } 89 return SetConfigImage(cfg, imageDir) 90} 91 92func SetConfigImage(cfg *mgrconfig.Config, imageDir string) error { 93 cfg.KernelObj = filepath.Join(imageDir, "obj") 94 cfg.Image = filepath.Join(imageDir, "image") 95 if keyFile := filepath.Join(imageDir, "key"); osutil.IsExist(keyFile) { 96 cfg.SSHKey = keyFile 97 } 98 if cfg.Type == "qemu" { 99 kernel := filepath.Join(imageDir, "kernel") 100 if !osutil.IsExist(kernel) { 101 kernel = "" 102 } 103 initrd := filepath.Join(imageDir, "initrd") 104 if !osutil.IsExist(initrd) { 105 initrd = "" 106 } 107 if kernel != "" || initrd != "" { 108 qemu := make(map[string]interface{}) 109 if err := json.Unmarshal(cfg.VM, &qemu); err != nil { 110 return fmt.Errorf("failed to parse qemu config: %v", err) 111 } 112 if kernel != "" { 113 qemu["kernel"] = kernel 114 } 115 if initrd != "" { 116 qemu["initrd"] = initrd 117 } 118 vmCfg, err := json.Marshal(qemu) 119 if err != nil { 120 return fmt.Errorf("failed to serialize qemu config: %v", err) 121 } 122 cfg.VM = vmCfg 123 } 124 } 125 return nil 126} 127 128type TestError struct { 129 Boot bool // says if the error happened during booting or during instance testing 130 Title string 131 Output []byte 132 Report *report.Report 133} 134 135func (err *TestError) Error() string { 136 return err.Title 137} 138 139type CrashError struct { 140 Report *report.Report 141} 142 143func (err *CrashError) Error() string { 144 return err.Report.Title 145} 146 147// Test boots numVMs VMs, tests basic kernel operation, and optionally tests the provided reproducer. 148// TestError is returned if there is a problem with kernel/image (crash, reboot loop, etc). 149// CrashError is returned if the reproducer crashes kernel. 150func (env *Env) Test(numVMs int, reproSyz, reproOpts, reproC []byte) ([]error, error) { 151 if err := mgrconfig.Complete(env.cfg); err != nil { 152 return nil, err 153 } 154 reporter, err := report.NewReporter(env.cfg) 155 if err != nil { 156 return nil, err 157 } 158 vmPool, err := vm.Create(env.cfg, false) 159 if err != nil { 160 return nil, fmt.Errorf("failed to create VM pool: %v", err) 161 } 162 if n := vmPool.Count(); numVMs > n { 163 numVMs = n 164 } 165 res := make(chan error, numVMs) 166 for i := 0; i < numVMs; i++ { 167 inst := &inst{ 168 cfg: env.cfg, 169 reporter: reporter, 170 vmPool: vmPool, 171 vmIndex: i, 172 reproSyz: reproSyz, 173 reproOpts: reproOpts, 174 reproC: reproC, 175 } 176 go func() { res <- inst.test() }() 177 } 178 var errors []error 179 for i := 0; i < numVMs; i++ { 180 errors = append(errors, <-res) 181 } 182 return errors, nil 183} 184 185type inst struct { 186 cfg *mgrconfig.Config 187 reporter report.Reporter 188 vmPool *vm.Pool 189 vm *vm.Instance 190 vmIndex int 191 reproSyz []byte 192 reproOpts []byte 193 reproC []byte 194} 195 196func (inst *inst) test() error { 197 vmInst, err := inst.vmPool.Create(inst.vmIndex) 198 if err != nil { 199 testErr := &TestError{ 200 Boot: true, 201 Title: err.Error(), 202 } 203 if bootErr, ok := err.(vm.BootErrorer); ok { 204 testErr.Title, testErr.Output = bootErr.BootError() 205 // This linux-ism avoids detecting any crash during boot as "unexpected kernel reboot". 206 output := testErr.Output 207 if pos := bytes.Index(output, []byte("Booting the kernel.")); pos != -1 { 208 output = output[pos+1:] 209 } 210 testErr.Report = inst.reporter.Parse(output) 211 if testErr.Report != nil { 212 testErr.Title = testErr.Report.Title 213 } else { 214 testErr.Report = &report.Report{ 215 Title: testErr.Title, 216 Output: testErr.Output, 217 } 218 } 219 if err := inst.reporter.Symbolize(testErr.Report); err != nil { 220 // TODO(dvyukov): send such errors to dashboard. 221 log.Logf(0, "failed to symbolize report: %v", err) 222 } 223 } 224 return testErr 225 } 226 defer vmInst.Close() 227 inst.vm = vmInst 228 if err := inst.testInstance(); err != nil { 229 return err 230 } 231 if len(inst.reproSyz) != 0 { 232 if err := inst.testRepro(); err != nil { 233 return err 234 } 235 } 236 return nil 237} 238 239// testInstance tests basic operation of the provided VM 240// (that we can copy binaries, run binaries, they can connect to host, run syzkaller programs, etc). 241// TestError is returned if there is a problem with the kernel (e.g. crash). 242func (inst *inst) testInstance() error { 243 ln, err := net.Listen("tcp", ":") 244 if err != nil { 245 return fmt.Errorf("failed to open listening socket: %v", err) 246 } 247 defer ln.Close() 248 acceptErr := make(chan error, 1) 249 go func() { 250 conn, err := ln.Accept() 251 if err == nil { 252 conn.Close() 253 } 254 acceptErr <- err 255 }() 256 fwdAddr, err := inst.vm.Forward(ln.Addr().(*net.TCPAddr).Port) 257 if err != nil { 258 return fmt.Errorf("failed to setup port forwarding: %v", err) 259 } 260 fuzzerBin, err := inst.vm.Copy(inst.cfg.SyzFuzzerBin) 261 if err != nil { 262 return &TestError{Title: fmt.Sprintf("failed to copy test binary to VM: %v", err)} 263 } 264 executorBin, err := inst.vm.Copy(inst.cfg.SyzExecutorBin) 265 if err != nil { 266 return &TestError{Title: fmt.Sprintf("failed to copy test binary to VM: %v", err)} 267 } 268 269 cmd := FuzzerCmd(fuzzerBin, executorBin, "test", inst.cfg.TargetOS, inst.cfg.TargetArch, fwdAddr, 270 inst.cfg.Sandbox, 0, 0, false, false, true, false) 271 outc, errc, err := inst.vm.Run(5*time.Minute, nil, cmd) 272 if err != nil { 273 return fmt.Errorf("failed to run binary in VM: %v", err) 274 } 275 rep := inst.vm.MonitorExecution(outc, errc, inst.reporter, true) 276 if rep != nil { 277 if err := inst.reporter.Symbolize(rep); err != nil { 278 // TODO(dvyukov): send such errors to dashboard. 279 log.Logf(0, "failed to symbolize report: %v", err) 280 } 281 return &TestError{ 282 Title: rep.Title, 283 Report: rep, 284 } 285 } 286 select { 287 case err := <-acceptErr: 288 return err 289 case <-time.After(10 * time.Second): 290 return fmt.Errorf("test machine failed to connect to host") 291 } 292} 293 294func (inst *inst) testRepro() error { 295 cfg := inst.cfg 296 execprogBin, err := inst.vm.Copy(cfg.SyzExecprogBin) 297 if err != nil { 298 return &TestError{Title: fmt.Sprintf("failed to copy test binary to VM: %v", err)} 299 } 300 executorBin, err := inst.vm.Copy(cfg.SyzExecutorBin) 301 if err != nil { 302 return &TestError{Title: fmt.Sprintf("failed to copy test binary to VM: %v", err)} 303 } 304 progFile := filepath.Join(cfg.Workdir, "repro.prog") 305 if err := osutil.WriteFile(progFile, inst.reproSyz); err != nil { 306 return fmt.Errorf("failed to write temp file: %v", err) 307 } 308 vmProgFile, err := inst.vm.Copy(progFile) 309 if err != nil { 310 return &TestError{Title: fmt.Sprintf("failed to copy test binary to VM: %v", err)} 311 } 312 opts, err := csource.DeserializeOptions(inst.reproOpts) 313 if err != nil { 314 return err 315 } 316 // Combine repro options and default options in a way that increases chances to reproduce the crash. 317 // First, we always enable threaded/collide as it should be [almost] strictly better. 318 // Executor does not support empty sandbox, so we use none instead. 319 // Finally, always use repeat and multiple procs. 320 if opts.Sandbox == "" { 321 opts.Sandbox = "none" 322 } 323 if !opts.Fault { 324 opts.FaultCall = -1 325 } 326 cmdSyz := ExecprogCmd(execprogBin, executorBin, cfg.TargetOS, cfg.TargetArch, opts.Sandbox, 327 true, true, true, cfg.Procs, opts.FaultCall, opts.FaultNth, vmProgFile) 328 if err := inst.testProgram(cmdSyz, 7*time.Minute); err != nil { 329 return err 330 } 331 if len(inst.reproC) == 0 { 332 return nil 333 } 334 target, err := prog.GetTarget(cfg.TargetOS, cfg.TargetArch) 335 if err != nil { 336 return err 337 } 338 bin, err := csource.Build(target, inst.reproC) 339 if err != nil { 340 return err 341 } 342 vmBin, err := inst.vm.Copy(bin) 343 if err != nil { 344 return &TestError{Title: fmt.Sprintf("failed to copy test binary to VM: %v", err)} 345 } 346 // We should test for longer (e.g. 5 mins), but the problem is that 347 // reproducer does not print anything, so after 3 mins we detect "no output". 348 return inst.testProgram(vmBin, time.Minute) 349} 350 351func (inst *inst) testProgram(command string, testTime time.Duration) error { 352 outc, errc, err := inst.vm.Run(testTime, nil, command) 353 if err != nil { 354 return fmt.Errorf("failed to run binary in VM: %v", err) 355 } 356 rep := inst.vm.MonitorExecution(outc, errc, inst.reporter, true) 357 if rep == nil { 358 return nil 359 } 360 if err := inst.reporter.Symbolize(rep); err != nil { 361 log.Logf(0, "failed to symbolize report: %v", err) 362 } 363 return &CrashError{Report: rep} 364} 365 366func FuzzerCmd(fuzzer, executor, name, OS, arch, fwdAddr, sandbox string, procs, verbosity int, 367 cover, debug, test, runtest bool) string { 368 osArg := "" 369 if OS == "akaros" { 370 // Only akaros needs OS, because the rest assume host OS. 371 // But speciying OS for all OSes breaks patch testing on syzbot 372 // because old execprog does not have os flag. 373 osArg = " -os=" + OS 374 } 375 return fmt.Sprintf("%v -executor=%v -name=%v -arch=%v%v -manager=%v -sandbox=%v"+ 376 " -procs=%v -v=%d -cover=%v -debug=%v -test=%v -runtest=%v", 377 fuzzer, executor, name, arch, osArg, fwdAddr, sandbox, 378 procs, verbosity, cover, debug, test, runtest) 379} 380 381func ExecprogCmd(execprog, executor, OS, arch, sandbox string, repeat, threaded, collide bool, 382 procs, faultCall, faultNth int, progFile string) string { 383 repeatCount := 1 384 if repeat { 385 repeatCount = 0 386 } 387 osArg := "" 388 if OS == "akaros" { 389 osArg = " -os=" + OS 390 } 391 return fmt.Sprintf("%v -executor=%v -arch=%v%v -sandbox=%v"+ 392 " -procs=%v -repeat=%v -threaded=%v -collide=%v -cover=0"+ 393 " -fault_call=%v -fault_nth=%v %v", 394 execprog, executor, arch, osArg, sandbox, 395 procs, repeatCount, threaded, collide, 396 faultCall, faultNth, progFile) 397} 398