mirror of
https://github.com/reactos/syzkaller.git
synced 2024-12-12 13:56:12 +00:00
416 lines
9.2 KiB
Go
416 lines
9.2 KiB
Go
// 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 kvm
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"regexp"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/google/syzkaller/vm"
|
|
)
|
|
|
|
const hostAddr = "192.168.33.1"
|
|
const logOutput = true
|
|
|
|
func init() {
|
|
vm.Register("kvm", ctor)
|
|
}
|
|
|
|
type kvm struct {
|
|
params
|
|
workdir string
|
|
crashdir string
|
|
callsFlag string
|
|
id int
|
|
mgrPort int
|
|
}
|
|
|
|
type params struct {
|
|
Lkvm string
|
|
Kernel string
|
|
Cmdline string
|
|
Fuzzer string
|
|
Executor string
|
|
Cpu int
|
|
Mem int
|
|
}
|
|
|
|
func ctor(cfg *vm.Config, index int) (vm.Instance, error) {
|
|
p := new(params)
|
|
if err := json.Unmarshal(cfg.Params, p); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal kvm params: %v", err)
|
|
}
|
|
if _, err := os.Stat(p.Kernel); err != nil {
|
|
return nil, fmt.Errorf("kernel '%v' does not exist: %v", p.Kernel, err)
|
|
}
|
|
if _, err := os.Stat(p.Fuzzer); err != nil {
|
|
return nil, fmt.Errorf("fuzzer binary '%v' does not exist: %v", p.Fuzzer, err)
|
|
}
|
|
if _, err := os.Stat(p.Executor); err != nil {
|
|
return nil, fmt.Errorf("executor binary '%v' does not exist: %v", p.Executor, err)
|
|
}
|
|
if p.Lkvm == "" {
|
|
p.Lkvm = "lkvm"
|
|
}
|
|
if p.Cpu <= 0 || p.Cpu > 1024 {
|
|
return nil, fmt.Errorf("bad kvm cpu: %v, want [1-1024]", p.Cpu)
|
|
}
|
|
if p.Mem < 128 || p.Mem > 1048576 {
|
|
return nil, fmt.Errorf("bad kvm mem: %v, want [128-1048576]", p.Mem)
|
|
}
|
|
|
|
crashdir := filepath.Join(cfg.Workdir, "crashes")
|
|
os.MkdirAll(crashdir, 0770)
|
|
|
|
workdir := filepath.Join(cfg.Workdir, "kvm")
|
|
os.MkdirAll(workdir, 0770)
|
|
|
|
vm := &kvm{
|
|
params: *p,
|
|
workdir: workdir,
|
|
crashdir: crashdir,
|
|
id: index,
|
|
mgrPort: cfg.ManagerPort,
|
|
}
|
|
|
|
if len(cfg.Syscalls) != 0 {
|
|
buf := new(bytes.Buffer)
|
|
for c := range cfg.Syscalls {
|
|
fmt.Fprintf(buf, ",%v", c)
|
|
}
|
|
vm.callsFlag = "-calls=" + buf.String()[1:]
|
|
}
|
|
|
|
return vm, nil
|
|
}
|
|
|
|
func (vm *kvm) Run() {
|
|
log.Printf("kvm/%v: started\n", vm.id)
|
|
sandbox := fmt.Sprintf("syz-%v", vm.id)
|
|
sandboxPath := filepath.Join(os.Getenv("HOME"), ".lkvm", sandbox)
|
|
for run := 0; ; run++ {
|
|
logname := filepath.Join(vm.workdir, fmt.Sprintf("log%v-%v-%v", vm.id, run, time.Now().Unix()))
|
|
var logf *os.File
|
|
if logOutput {
|
|
var err error
|
|
logf, err = os.Create(logname)
|
|
if err != nil {
|
|
log.Printf("failed to create log file: %v", err)
|
|
time.Sleep(10 * time.Second)
|
|
continue
|
|
}
|
|
}
|
|
rpipe, wpipe, err := os.Pipe()
|
|
if err != nil {
|
|
log.Printf("failed to create pipe: %v", err)
|
|
if logf != nil {
|
|
logf.Close()
|
|
}
|
|
time.Sleep(10 * time.Second)
|
|
continue
|
|
}
|
|
os.RemoveAll(sandboxPath)
|
|
out, err := exec.Command(vm.Lkvm, "setup", sandbox).CombinedOutput()
|
|
if err != nil {
|
|
log.Printf("failed to lkvm setup: %v\n%s", err, out)
|
|
if logf != nil {
|
|
logf.Close()
|
|
}
|
|
rpipe.Close()
|
|
wpipe.Close()
|
|
time.Sleep(10 * time.Second)
|
|
continue
|
|
}
|
|
if err := copyFile(vm.Fuzzer, filepath.Join(sandboxPath, "/syzkaller_fuzzer")); err != nil {
|
|
log.Printf("failed to copy file into sandbox: %v", err)
|
|
os.RemoveAll(sandboxPath)
|
|
if logf != nil {
|
|
logf.Close()
|
|
}
|
|
rpipe.Close()
|
|
wpipe.Close()
|
|
time.Sleep(10 * time.Second)
|
|
continue
|
|
}
|
|
if err := copyFile(vm.Executor, filepath.Join(sandboxPath, "/syzkaller_executor")); err != nil {
|
|
log.Printf("failed to copy file into sandbox: %v", err)
|
|
os.RemoveAll(sandboxPath)
|
|
if logf != nil {
|
|
logf.Close()
|
|
}
|
|
rpipe.Close()
|
|
wpipe.Close()
|
|
time.Sleep(10 * time.Second)
|
|
continue
|
|
}
|
|
inst := &Instance{
|
|
id: vm.id,
|
|
crashdir: vm.crashdir,
|
|
params: vm.params,
|
|
name: fmt.Sprintf("kvm/%v-%v", vm.id, run),
|
|
sandbox: sandbox,
|
|
sandboxPath: sandboxPath,
|
|
callsFlag: vm.callsFlag,
|
|
log: logf,
|
|
rpipe: rpipe,
|
|
wpipe: wpipe,
|
|
mgrPort: vm.mgrPort,
|
|
cmds: make(map[*Command]bool),
|
|
}
|
|
inst.Run()
|
|
inst.Shutdown()
|
|
time.Sleep(10 * time.Second)
|
|
}
|
|
}
|
|
|
|
type Instance struct {
|
|
params
|
|
sync.Mutex
|
|
id int
|
|
crashdir string
|
|
name string
|
|
sandbox string
|
|
sandboxPath string
|
|
callsFlag string
|
|
log *os.File
|
|
rpipe *os.File
|
|
wpipe *os.File
|
|
mgrPort int
|
|
cmds map[*Command]bool
|
|
kvm *Command
|
|
}
|
|
|
|
type Command struct {
|
|
sync.Mutex
|
|
cmd *exec.Cmd
|
|
done chan struct{}
|
|
failed bool
|
|
out []byte
|
|
outpos int
|
|
}
|
|
|
|
func (inst *Instance) Run() {
|
|
var outputMu sync.Mutex
|
|
var output []byte
|
|
go func() {
|
|
var buf [64 << 10]byte
|
|
for {
|
|
n, err := inst.rpipe.Read(buf[:])
|
|
if n != 0 {
|
|
outputMu.Lock()
|
|
output = append(output, buf[:n]...)
|
|
outputMu.Unlock()
|
|
if inst.log != nil {
|
|
inst.log.Write(buf[:n])
|
|
}
|
|
}
|
|
if err != nil {
|
|
break
|
|
}
|
|
}
|
|
}()
|
|
|
|
// Start the instance.
|
|
inst.kvm = inst.CreateCommand(
|
|
inst.Lkvm, "sandbox",
|
|
"--disk", inst.sandbox,
|
|
fmt.Sprintf("--mem=%v", inst.Mem),
|
|
fmt.Sprintf("--cpus=%v", inst.Cpu),
|
|
"--kernel", inst.Kernel,
|
|
"--network", "mode=user",
|
|
"--", "/syzkaller_fuzzer",
|
|
"-name", inst.name,
|
|
"-executor", "/syzkaller_executor",
|
|
"-manager", fmt.Sprintf("%v:%v", hostAddr, inst.mgrPort),
|
|
inst.callsFlag,
|
|
)
|
|
|
|
start := time.Now()
|
|
deadline := start.Add(time.Hour)
|
|
lastOutput := time.Now()
|
|
lastOutputLen := 0
|
|
matchPos := 0
|
|
crashRe := regexp.MustCompile("\\[ cut here \\]|Kernel panic| BUG: | WARNING: | INFO: |unable to handle kernel NULL pointer dereference|general protection fault")
|
|
const contextSize = 64 << 10
|
|
for range time.NewTicker(5 * time.Second).C {
|
|
outputMu.Lock()
|
|
if lastOutputLen != len(output) {
|
|
lastOutput = time.Now()
|
|
}
|
|
if loc := crashRe.FindAllIndex(output[matchPos:], -1); len(loc) != 0 {
|
|
// Give it some time to finish writing the error message.
|
|
outputMu.Unlock()
|
|
time.Sleep(5 * time.Second)
|
|
outputMu.Lock()
|
|
loc = crashRe.FindAllIndex(output[matchPos:], -1)
|
|
start := loc[0][0] - contextSize
|
|
if start < 0 {
|
|
start = 0
|
|
}
|
|
end := loc[len(loc)-1][1] + contextSize
|
|
if end > len(output) {
|
|
end = len(output)
|
|
}
|
|
inst.SaveCrasher(output[start:end])
|
|
}
|
|
if len(output) > 2*contextSize {
|
|
copy(output, output[len(output)-contextSize:])
|
|
output = output[:contextSize]
|
|
}
|
|
matchPos = len(output) - 128
|
|
if matchPos < 0 {
|
|
matchPos = 0
|
|
}
|
|
lastOutputLen = len(output)
|
|
outputMu.Unlock()
|
|
|
|
if time.Since(lastOutput) > 3*time.Minute {
|
|
time.Sleep(time.Second)
|
|
outputMu.Lock()
|
|
output = append(output, "\nno output from fuzzer, restarting\n"...)
|
|
inst.SaveCrasher(output)
|
|
outputMu.Unlock()
|
|
inst.Logf("no output from fuzzer, restarting")
|
|
inst.kvm.cmd.Process.Kill()
|
|
inst.kvm.cmd.Process.Kill()
|
|
return
|
|
}
|
|
if inst.kvm.Exited() {
|
|
time.Sleep(time.Second)
|
|
outputMu.Lock()
|
|
output = append(output, "\nfuzzer binary stopped or lost connection\n"...)
|
|
inst.SaveCrasher(output)
|
|
outputMu.Unlock()
|
|
inst.Logf("fuzzer binary stopped or lost connection")
|
|
return
|
|
}
|
|
if time.Now().After(deadline) {
|
|
inst.Logf("running for long enough, restarting")
|
|
inst.kvm.cmd.Process.Kill()
|
|
inst.kvm.cmd.Process.Kill()
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (inst *Instance) SaveCrasher(output []byte) {
|
|
ioutil.WriteFile(filepath.Join(inst.crashdir, fmt.Sprintf("crash%v-%v", inst.id, time.Now().UnixNano())), output, 0660)
|
|
}
|
|
|
|
func (inst *Instance) Shutdown() {
|
|
defer func() {
|
|
os.RemoveAll(inst.sandboxPath)
|
|
inst.rpipe.Close()
|
|
inst.wpipe.Close()
|
|
if inst.log != nil {
|
|
inst.log.Close()
|
|
}
|
|
}()
|
|
if inst.kvm.cmd == nil {
|
|
// CreateCommand should have been failed very early.
|
|
return
|
|
}
|
|
for try := 0; try < 10; try++ {
|
|
inst.kvm.cmd.Process.Kill()
|
|
time.Sleep(time.Second)
|
|
inst.Lock()
|
|
n := len(inst.cmds)
|
|
inst.Unlock()
|
|
if n == 0 {
|
|
return
|
|
}
|
|
}
|
|
inst.Logf("hanged processes after kill")
|
|
inst.Lock()
|
|
for cmd := range inst.cmds {
|
|
cmd.cmd.Process.Kill()
|
|
cmd.cmd.Process.Kill()
|
|
}
|
|
inst.Unlock()
|
|
time.Sleep(3 * time.Second)
|
|
}
|
|
|
|
func (inst *Instance) CreateCommand(args ...string) *Command {
|
|
if inst.log != nil {
|
|
fmt.Fprintf(inst.log, "executing command: %v\n", args)
|
|
}
|
|
cmd := &Command{}
|
|
cmd.done = make(chan struct{})
|
|
cmd.cmd = exec.Command(args[0], args[1:]...)
|
|
cmd.cmd.Stdout = inst.wpipe
|
|
cmd.cmd.Stderr = inst.wpipe
|
|
if err := cmd.cmd.Start(); err != nil {
|
|
inst.Logf("failed to start command '%v': %v\n", args, err)
|
|
cmd.failed = true
|
|
close(cmd.done)
|
|
return cmd
|
|
}
|
|
inst.Lock()
|
|
inst.cmds[cmd] = true
|
|
inst.Unlock()
|
|
go func() {
|
|
err := cmd.cmd.Wait()
|
|
inst.Lock()
|
|
delete(inst.cmds, cmd)
|
|
inst.Unlock()
|
|
if inst.log != nil {
|
|
fmt.Fprintf(inst.log, "command '%v' exited: %v\n", args, err)
|
|
}
|
|
cmd.failed = err != nil
|
|
close(cmd.done)
|
|
}()
|
|
return cmd
|
|
}
|
|
|
|
func (inst *Instance) Logf(str string, args ...interface{}) {
|
|
fmt.Fprintf(inst.wpipe, str+"\n", args...)
|
|
log.Printf("%v: "+str, append([]interface{}{inst.name}, args...)...)
|
|
}
|
|
|
|
func (cmd *Command) Wait(max time.Duration) bool {
|
|
select {
|
|
case <-cmd.done:
|
|
return !cmd.failed
|
|
case <-time.After(max):
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (cmd *Command) Exited() bool {
|
|
select {
|
|
case <-cmd.done:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func copyFile(oldfn, newfn string) error {
|
|
oldf, err := os.Open(oldfn)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer oldf.Close()
|
|
newf, err := os.Create(newfn)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer newf.Close()
|
|
_, err = io.Copy(newf, oldf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|