2015-10-12 08:16:57 +00:00
|
|
|
// Copyright 2015 syzkaller project authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package vm
|
|
|
|
|
|
|
|
import (
|
2016-09-01 17:54:55 +00:00
|
|
|
"bytes"
|
2015-12-23 18:11:29 +00:00
|
|
|
"errors"
|
2015-10-12 08:16:57 +00:00
|
|
|
"fmt"
|
2016-08-28 17:21:57 +00:00
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"syscall"
|
2015-12-23 18:11:29 +00:00
|
|
|
"time"
|
2016-09-01 17:54:55 +00:00
|
|
|
|
|
|
|
"github.com/google/syzkaller/report"
|
2015-10-12 08:16:57 +00:00
|
|
|
)
|
|
|
|
|
2015-12-23 18:11:29 +00:00
|
|
|
// Instance represents a Linux VM or a remote physical machine.
|
2015-10-12 08:16:57 +00:00
|
|
|
type Instance interface {
|
2016-01-11 16:33:44 +00:00
|
|
|
// Copy copies a hostSrc file into vm and returns file name in vm.
|
|
|
|
Copy(hostSrc string) (string, error)
|
|
|
|
|
|
|
|
// Forward setups forwarding from within VM to host port port
|
|
|
|
// and returns address to use in VM.
|
|
|
|
Forward(port int) (string, error)
|
|
|
|
|
2015-12-23 18:11:29 +00:00
|
|
|
// Run runs cmd inside of the VM (think of ssh cmd).
|
|
|
|
// outc receives combined cmd and kernel console output.
|
|
|
|
// errc receives either command Wait return error or vm.TimeoutErr.
|
2016-11-19 10:14:11 +00:00
|
|
|
// Command is terminated after timeout. Send on the stop chan can be used to terminate it earlier.
|
|
|
|
Run(timeout time.Duration, stop <-chan bool, command string) (outc <-chan []byte, errc <-chan error, err error)
|
2016-01-11 16:33:44 +00:00
|
|
|
|
2015-12-23 18:11:29 +00:00
|
|
|
// Close stops and destroys the VM.
|
|
|
|
Close()
|
2015-10-12 08:16:57 +00:00
|
|
|
}
|
|
|
|
|
2015-10-19 10:47:37 +00:00
|
|
|
type Config struct {
|
2016-10-06 14:15:10 +00:00
|
|
|
Name string
|
|
|
|
Index int
|
|
|
|
Workdir string
|
|
|
|
Bin string
|
2016-11-22 15:59:04 +00:00
|
|
|
BinArgs string
|
2016-10-06 14:15:10 +00:00
|
|
|
Initrd string
|
|
|
|
Kernel string
|
|
|
|
Cmdline string
|
|
|
|
Image string
|
|
|
|
Sshkey string
|
|
|
|
Executor string
|
|
|
|
Device string
|
|
|
|
MachineType string
|
|
|
|
Cpu int
|
|
|
|
Mem int
|
|
|
|
Debug bool
|
2015-10-19 10:47:37 +00:00
|
|
|
}
|
|
|
|
|
2015-12-23 18:11:29 +00:00
|
|
|
type ctorFunc func(cfg *Config) (Instance, error)
|
2015-10-12 08:16:57 +00:00
|
|
|
|
|
|
|
var ctors = make(map[string]ctorFunc)
|
|
|
|
|
|
|
|
func Register(typ string, ctor ctorFunc) {
|
|
|
|
ctors[typ] = ctor
|
|
|
|
}
|
|
|
|
|
2016-09-29 13:04:48 +00:00
|
|
|
// Close to interrupt all pending operations.
|
|
|
|
var Shutdown = make(chan struct{})
|
|
|
|
|
2015-12-23 18:11:29 +00:00
|
|
|
// Create creates and boots a new VM instance.
|
|
|
|
func Create(typ string, cfg *Config) (Instance, error) {
|
2015-10-12 08:16:57 +00:00
|
|
|
ctor := ctors[typ]
|
|
|
|
if ctor == nil {
|
|
|
|
return nil, fmt.Errorf("unknown instance type '%v'", typ)
|
|
|
|
}
|
2015-12-23 18:11:29 +00:00
|
|
|
return ctor(cfg)
|
2015-10-12 08:16:57 +00:00
|
|
|
}
|
2015-12-23 18:11:29 +00:00
|
|
|
|
2016-08-28 17:21:57 +00:00
|
|
|
func LongPipe() (io.ReadCloser, io.WriteCloser, error) {
|
|
|
|
r, w, err := os.Pipe()
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, fmt.Errorf("failed to create pipe: %v", err)
|
|
|
|
}
|
|
|
|
for sz := 128 << 10; sz <= 2<<20; sz *= 2 {
|
|
|
|
syscall.Syscall(syscall.SYS_FCNTL, w.Fd(), syscall.F_SETPIPE_SZ, uintptr(sz))
|
|
|
|
}
|
|
|
|
return r, w, err
|
|
|
|
}
|
|
|
|
|
2016-08-30 13:19:28 +00:00
|
|
|
var TimeoutErr = errors.New("timeout")
|
2016-09-01 17:54:55 +00:00
|
|
|
|
|
|
|
func MonitorExecution(outc <-chan []byte, errc <-chan error, local, needOutput bool) (desc string, text, output []byte, crashed, timedout bool) {
|
|
|
|
waitForOutput := func() {
|
|
|
|
dur := time.Second
|
|
|
|
if needOutput {
|
|
|
|
dur = 10 * time.Second
|
|
|
|
}
|
|
|
|
timer := time.NewTimer(dur).C
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case out, ok := <-outc:
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
output = append(output, out...)
|
|
|
|
case <-timer:
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
matchPos := 0
|
|
|
|
const (
|
|
|
|
beforeContext = 256 << 10
|
|
|
|
afterContext = 128 << 10
|
|
|
|
)
|
2016-09-28 16:05:28 +00:00
|
|
|
extractError := func(defaultError string) (string, []byte, []byte, bool, bool) {
|
|
|
|
// Give it some time to finish writing the error message.
|
|
|
|
waitForOutput()
|
|
|
|
if !report.ContainsCrash(output[matchPos:]) {
|
|
|
|
return defaultError, nil, output, true, false
|
|
|
|
}
|
|
|
|
desc, text, start, end := report.Parse(output[matchPos:])
|
|
|
|
start = start + matchPos - beforeContext
|
|
|
|
if start < 0 {
|
|
|
|
start = 0
|
|
|
|
}
|
|
|
|
end = end + matchPos + afterContext
|
|
|
|
if end > len(output) {
|
|
|
|
end = len(output)
|
|
|
|
}
|
|
|
|
return desc, text, output[start:end], true, false
|
|
|
|
}
|
|
|
|
|
2016-09-01 17:54:55 +00:00
|
|
|
lastExecuteTime := time.Now()
|
|
|
|
ticker := time.NewTimer(3 * time.Minute)
|
2016-09-09 11:22:48 +00:00
|
|
|
tickerFired := false
|
2016-09-01 17:54:55 +00:00
|
|
|
for {
|
2016-09-09 11:22:48 +00:00
|
|
|
if !tickerFired && !ticker.Stop() {
|
2016-09-01 17:54:55 +00:00
|
|
|
<-ticker.C
|
|
|
|
}
|
2016-09-09 11:22:48 +00:00
|
|
|
tickerFired = false
|
2016-09-01 17:54:55 +00:00
|
|
|
ticker.Reset(3 * time.Minute)
|
|
|
|
select {
|
|
|
|
case err := <-errc:
|
|
|
|
switch err {
|
|
|
|
case nil:
|
|
|
|
waitForOutput()
|
|
|
|
return "", nil, output, false, false
|
|
|
|
case TimeoutErr:
|
|
|
|
return err.Error(), nil, nil, false, true
|
|
|
|
default:
|
2016-09-28 16:05:28 +00:00
|
|
|
// Note: connection lost can race with a kernel oops message.
|
|
|
|
// In such case we want to return the kernel oops.
|
|
|
|
return extractError("lost connection to test machine")
|
2016-09-01 17:54:55 +00:00
|
|
|
}
|
|
|
|
case out := <-outc:
|
|
|
|
output = append(output, out...)
|
2016-09-03 10:35:16 +00:00
|
|
|
if bytes.Index(output[matchPos:], []byte("executing program")) != -1 { // syz-fuzzer output
|
|
|
|
lastExecuteTime = time.Now()
|
|
|
|
}
|
|
|
|
if bytes.Index(output[matchPos:], []byte("executed programs:")) != -1 { // syz-execprog output
|
2016-09-01 17:54:55 +00:00
|
|
|
lastExecuteTime = time.Now()
|
|
|
|
}
|
|
|
|
if report.ContainsCrash(output[matchPos:]) {
|
2016-09-28 16:05:28 +00:00
|
|
|
return extractError("")
|
2016-09-01 17:54:55 +00:00
|
|
|
}
|
|
|
|
if len(output) > 2*beforeContext {
|
|
|
|
copy(output, output[len(output)-beforeContext:])
|
|
|
|
output = output[:beforeContext]
|
|
|
|
}
|
|
|
|
matchPos = len(output) - 128
|
|
|
|
if matchPos < 0 {
|
|
|
|
matchPos = 0
|
|
|
|
}
|
|
|
|
// In some cases kernel constantly prints something to console,
|
|
|
|
// but fuzzer is not actually executing programs.
|
|
|
|
if !local && time.Since(lastExecuteTime) > 3*time.Minute {
|
|
|
|
return "test machine is not executing programs", nil, output, true, false
|
|
|
|
}
|
|
|
|
case <-ticker.C:
|
2016-09-09 11:22:48 +00:00
|
|
|
tickerFired = true
|
2016-09-01 17:54:55 +00:00
|
|
|
if !local {
|
|
|
|
return "no output from test machine", nil, output, true, false
|
|
|
|
}
|
2016-09-29 13:04:48 +00:00
|
|
|
case <-Shutdown:
|
|
|
|
return "", nil, nil, false, false
|
2016-09-01 17:54:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-09-29 13:04:48 +00:00
|
|
|
|
|
|
|
// Sleep for d.
|
|
|
|
// If shutdown is in progress, return false prematurely.
|
|
|
|
func SleepInterruptible(d time.Duration) bool {
|
|
|
|
select {
|
|
|
|
case <-time.After(d):
|
|
|
|
return true
|
|
|
|
case <-Shutdown:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|