mirror of
https://github.com/reactos/syzkaller.git
synced 2024-11-27 21:30:33 +00:00
dfc7d235f5
We've got a case when "ODEBUG:" was incorrectly detected as crash. That was caused by a flaw in matchPos logic. Fix that. See the added test for details.
369 lines
7.8 KiB
Go
369 lines
7.8 KiB
Go
// Copyright 2018 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 (
|
|
"bytes"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/syzkaller/pkg/mgrconfig"
|
|
"github.com/google/syzkaller/pkg/report"
|
|
"github.com/google/syzkaller/vm/vmimpl"
|
|
)
|
|
|
|
type testPool struct {
|
|
}
|
|
|
|
func (pool *testPool) Count() int {
|
|
return 1
|
|
}
|
|
|
|
func (pool *testPool) Create(workdir string, index int) (vmimpl.Instance, error) {
|
|
return &testInstance{
|
|
outc: make(chan []byte, 10),
|
|
errc: make(chan error, 1),
|
|
}, nil
|
|
}
|
|
|
|
type testInstance struct {
|
|
outc chan []byte
|
|
errc chan error
|
|
diagnoseBug bool
|
|
diagnoseNoWait bool
|
|
}
|
|
|
|
func (inst *testInstance) Copy(hostSrc string) (string, error) {
|
|
return "", nil
|
|
}
|
|
|
|
func (inst *testInstance) Forward(port int) (string, error) {
|
|
return "", nil
|
|
}
|
|
|
|
func (inst *testInstance) Run(timeout time.Duration, stop <-chan bool, command string) (
|
|
outc <-chan []byte, errc <-chan error, err error) {
|
|
return inst.outc, inst.errc, nil
|
|
}
|
|
|
|
func (inst *testInstance) Diagnose() ([]byte, bool) {
|
|
var diag []byte
|
|
if inst.diagnoseBug {
|
|
diag = []byte("BUG: DIAGNOSE\n")
|
|
} else {
|
|
diag = []byte("DIAGNOSE\n")
|
|
}
|
|
|
|
if inst.diagnoseNoWait {
|
|
return diag, false
|
|
}
|
|
|
|
inst.outc <- diag
|
|
return nil, true
|
|
}
|
|
|
|
func (inst *testInstance) Close() {
|
|
}
|
|
|
|
func init() {
|
|
beforeContext = maxErrorLength + 100
|
|
tickerPeriod = 1 * time.Second
|
|
NoOutputTimeout = 5 * time.Second
|
|
waitForOutputTimeout = 3 * time.Second
|
|
|
|
ctor := func(env *vmimpl.Env) (vmimpl.Pool, error) {
|
|
return &testPool{}, nil
|
|
}
|
|
vmimpl.Register("test", ctor, false)
|
|
}
|
|
|
|
type Test struct {
|
|
Name string
|
|
Exit ExitCondition
|
|
DiagnoseBug bool // Diagnose produces output that is detected as kernel crash
|
|
DiagnoseNoWait bool // Diagnose returns output directly rather than to console
|
|
Body func(outc chan []byte, errc chan error)
|
|
Report *report.Report
|
|
}
|
|
|
|
var tests = []*Test{
|
|
{
|
|
Name: "program-exits-normally",
|
|
Exit: ExitNormal,
|
|
Body: func(outc chan []byte, errc chan error) {
|
|
time.Sleep(time.Second)
|
|
errc <- nil
|
|
},
|
|
},
|
|
{
|
|
Name: "program-exits-when-it-should-not",
|
|
Body: func(outc chan []byte, errc chan error) {
|
|
time.Sleep(time.Second)
|
|
errc <- nil
|
|
},
|
|
Report: &report.Report{
|
|
Title: lostConnectionCrash,
|
|
},
|
|
},
|
|
{
|
|
Name: "#875-diagnose-bugs",
|
|
Exit: ExitNormal,
|
|
DiagnoseBug: true,
|
|
Body: func(outc chan []byte, errc chan error) {
|
|
errc <- nil
|
|
},
|
|
},
|
|
{
|
|
Name: "#875-diagnose-bugs-2",
|
|
Body: func(outc chan []byte, errc chan error) {
|
|
errc <- nil
|
|
},
|
|
Report: &report.Report{
|
|
Title: lostConnectionCrash,
|
|
Output: []byte(
|
|
"DIAGNOSE\n",
|
|
),
|
|
},
|
|
},
|
|
{
|
|
Name: "diagnose-no-wait",
|
|
Body: func(outc chan []byte, errc chan error) {
|
|
errc <- nil
|
|
},
|
|
DiagnoseNoWait: true,
|
|
Report: &report.Report{
|
|
Title: lostConnectionCrash,
|
|
Output: []byte(
|
|
"DIAGNOSIS:\nDIAGNOSE\n",
|
|
),
|
|
},
|
|
},
|
|
{
|
|
Name: "kernel-crashes",
|
|
Body: func(outc chan []byte, errc chan error) {
|
|
outc <- []byte("BUG: bad\n")
|
|
time.Sleep(time.Second)
|
|
outc <- []byte("other output\n")
|
|
},
|
|
Report: &report.Report{
|
|
Title: "BUG: bad",
|
|
Report: []byte(
|
|
"BUG: bad\n" +
|
|
"DIAGNOSE\n" +
|
|
"other output\n",
|
|
),
|
|
},
|
|
},
|
|
{
|
|
Name: "fuzzer-is-preempted",
|
|
Body: func(outc chan []byte, errc chan error) {
|
|
outc <- []byte("BUG: bad\n")
|
|
outc <- []byte(fuzzerPreemptedStr + "\n")
|
|
},
|
|
},
|
|
{
|
|
Name: "program-exits-but-kernel-crashes-afterwards",
|
|
Exit: ExitNormal,
|
|
Body: func(outc chan []byte, errc chan error) {
|
|
errc <- nil
|
|
time.Sleep(time.Second)
|
|
outc <- []byte("BUG: bad\n")
|
|
},
|
|
Report: &report.Report{
|
|
Title: "BUG: bad",
|
|
Report: []byte(
|
|
"BUG: bad\n" +
|
|
"DIAGNOSE\n",
|
|
),
|
|
},
|
|
},
|
|
{
|
|
Name: "timeout",
|
|
Exit: ExitTimeout,
|
|
Body: func(outc chan []byte, errc chan error) {
|
|
errc <- vmimpl.ErrTimeout
|
|
},
|
|
},
|
|
{
|
|
Name: "bad-timeout",
|
|
Body: func(outc chan []byte, errc chan error) {
|
|
errc <- vmimpl.ErrTimeout
|
|
},
|
|
Report: &report.Report{
|
|
Title: timeoutCrash,
|
|
},
|
|
},
|
|
{
|
|
Name: "program-crashes",
|
|
Body: func(outc chan []byte, errc chan error) {
|
|
errc <- fmt.Errorf("error")
|
|
},
|
|
Report: &report.Report{
|
|
Title: lostConnectionCrash,
|
|
},
|
|
},
|
|
{
|
|
Name: "program-crashes-expected",
|
|
Exit: ExitError,
|
|
Body: func(outc chan []byte, errc chan error) {
|
|
errc <- fmt.Errorf("error")
|
|
},
|
|
},
|
|
{
|
|
Name: "no-output-1",
|
|
Body: func(outc chan []byte, errc chan error) {
|
|
},
|
|
Report: &report.Report{
|
|
Title: noOutputCrash,
|
|
},
|
|
},
|
|
{
|
|
Name: "no-output-2",
|
|
Body: func(outc chan []byte, errc chan error) {
|
|
for i := 0; i < 5; i++ {
|
|
time.Sleep(time.Second)
|
|
outc <- []byte("something\n")
|
|
}
|
|
},
|
|
Report: &report.Report{
|
|
Title: noOutputCrash,
|
|
},
|
|
},
|
|
{
|
|
Name: "no-no-output-1",
|
|
Exit: ExitNormal,
|
|
Body: func(outc chan []byte, errc chan error) {
|
|
for i := 0; i < 5; i++ {
|
|
time.Sleep(time.Second)
|
|
outc <- []byte(executingProgramStr1 + "\n")
|
|
}
|
|
errc <- nil
|
|
},
|
|
},
|
|
{
|
|
Name: "no-no-output-2",
|
|
Exit: ExitNormal,
|
|
Body: func(outc chan []byte, errc chan error) {
|
|
for i := 0; i < 5; i++ {
|
|
time.Sleep(time.Second)
|
|
outc <- []byte(executingProgramStr2 + "\n")
|
|
}
|
|
errc <- nil
|
|
},
|
|
},
|
|
{
|
|
Name: "outc-closed",
|
|
Exit: ExitTimeout,
|
|
Body: func(outc chan []byte, errc chan error) {
|
|
close(outc)
|
|
time.Sleep(time.Second)
|
|
errc <- vmimpl.ErrTimeout
|
|
},
|
|
},
|
|
{
|
|
Name: "lots-of-output",
|
|
Exit: ExitTimeout,
|
|
Body: func(outc chan []byte, errc chan error) {
|
|
for i := 0; i < 100; i++ {
|
|
outc <- []byte("something\n")
|
|
}
|
|
time.Sleep(time.Second)
|
|
errc <- vmimpl.ErrTimeout
|
|
},
|
|
},
|
|
{
|
|
Name: "split-line",
|
|
Exit: ExitNormal,
|
|
Body: func(outc chan []byte, errc chan error) {
|
|
// "ODEBUG:" lines should be ignored, however the matchPos logic
|
|
// used to trim the lines so that we could see just "BUG:" later
|
|
// and detect it as crash.
|
|
buf := new(bytes.Buffer)
|
|
for i := 0; i < 50; i++ {
|
|
buf.WriteString("[ 2886.597572] ODEBUG: Out of memory. ODEBUG disabled\n")
|
|
buf.Write(bytes.Repeat([]byte{'-'}, i))
|
|
buf.WriteByte('\n')
|
|
}
|
|
output := buf.Bytes()
|
|
for i := range output {
|
|
outc <- output[i : i+1]
|
|
}
|
|
errc <- nil
|
|
},
|
|
},
|
|
}
|
|
|
|
func TestMonitorExecution(t *testing.T) {
|
|
for _, test := range tests {
|
|
test := test
|
|
t.Run(test.Name, func(t *testing.T) {
|
|
t.Parallel()
|
|
testMonitorExecution(t, test)
|
|
})
|
|
}
|
|
}
|
|
|
|
func testMonitorExecution(t *testing.T, test *Test) {
|
|
dir, err := ioutil.TempDir("", "syz-vm-test")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
cfg := &mgrconfig.Config{
|
|
Workdir: dir,
|
|
TargetOS: "linux",
|
|
TargetArch: "amd64",
|
|
TargetVMArch: "amd64",
|
|
Type: "test",
|
|
}
|
|
pool, err := Create(cfg, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
reporter, err := report.NewReporter(cfg)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
inst, err := pool.Create(0)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer inst.Close()
|
|
outc, errc, err := inst.Run(time.Second, nil, "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
testInst := inst.impl.(*testInstance)
|
|
testInst.diagnoseBug = test.DiagnoseBug
|
|
testInst.diagnoseNoWait = test.DiagnoseNoWait
|
|
done := make(chan bool)
|
|
go func() {
|
|
test.Body(testInst.outc, testInst.errc)
|
|
done <- true
|
|
}()
|
|
rep := inst.MonitorExecution(outc, errc, reporter, test.Exit)
|
|
<-done
|
|
if test.Report != nil && rep == nil {
|
|
t.Fatalf("got no report")
|
|
}
|
|
if test.Report == nil && rep != nil {
|
|
t.Fatalf("got unexpected report: %v", rep.Title)
|
|
}
|
|
if test.Report == nil {
|
|
return
|
|
}
|
|
if test.Report.Title != rep.Title {
|
|
t.Fatalf("want title %q, got title %q", test.Report.Title, rep.Title)
|
|
}
|
|
if !bytes.Equal(test.Report.Report, rep.Report) {
|
|
t.Fatalf("want report:\n%s\n\ngot report:\n%s\n", test.Report.Report, rep.Report)
|
|
}
|
|
if test.Report.Output != nil && !bytes.Equal(test.Report.Output, rep.Output) {
|
|
t.Fatalf("want output:\n%s\n\ngot output:\n%s\n", test.Report.Output, rep.Output)
|
|
}
|
|
}
|