github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/vm/vm_test.go (about)

     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 vm
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/google/syzkaller/pkg/mgrconfig"
    13  	"github.com/google/syzkaller/pkg/report"
    14  	"github.com/google/syzkaller/sys/targets"
    15  	"github.com/google/syzkaller/vm/vmimpl"
    16  )
    17  
    18  type testPool struct {
    19  }
    20  
    21  func (pool *testPool) Count() int {
    22  	return 1
    23  }
    24  
    25  func (pool *testPool) Create(workdir string, index int) (vmimpl.Instance, error) {
    26  	return &testInstance{
    27  		outc: make(chan []byte, 10),
    28  		errc: make(chan error, 1),
    29  	}, nil
    30  }
    31  
    32  func (pool *testPool) Close() error {
    33  	return nil
    34  }
    35  
    36  type testInstance struct {
    37  	outc           chan []byte
    38  	errc           chan error
    39  	diagnoseBug    bool
    40  	diagnoseNoWait bool
    41  }
    42  
    43  func (inst *testInstance) Copy(hostSrc string) (string, error) {
    44  	return "", nil
    45  }
    46  
    47  func (inst *testInstance) Forward(port int) (string, error) {
    48  	return "", nil
    49  }
    50  
    51  func (inst *testInstance) Run(timeout time.Duration, stop <-chan bool, command string) (
    52  	outc <-chan []byte, errc <-chan error, err error) {
    53  	return inst.outc, inst.errc, nil
    54  }
    55  
    56  func (inst *testInstance) Diagnose(rep *report.Report) ([]byte, bool) {
    57  	var diag []byte
    58  	if inst.diagnoseBug {
    59  		diag = []byte("BUG: DIAGNOSE\n")
    60  	} else {
    61  		diag = []byte("DIAGNOSE\n")
    62  	}
    63  
    64  	if inst.diagnoseNoWait {
    65  		return diag, false
    66  	}
    67  
    68  	inst.outc <- diag
    69  	return nil, true
    70  }
    71  
    72  func (inst *testInstance) Close() {
    73  }
    74  
    75  func init() {
    76  	beforeContextDefault = maxErrorLength + 100
    77  	tickerPeriod = 1 * time.Second
    78  	waitForOutputTimeout = 3 * time.Second
    79  
    80  	ctor := func(env *vmimpl.Env) (vmimpl.Pool, error) {
    81  		return &testPool{}, nil
    82  	}
    83  	vmimpl.Register("test", ctor, false)
    84  }
    85  
    86  type Test struct {
    87  	Name           string
    88  	Exit           ExitCondition
    89  	DiagnoseBug    bool // Diagnose produces output that is detected as kernel crash.
    90  	DiagnoseNoWait bool // Diagnose returns output directly rather than to console.
    91  	InjectOutput   string
    92  	Body           func(outc chan []byte, errc chan error)
    93  	Report         *report.Report
    94  }
    95  
    96  // nolint: goconst // "DIAGNOSE\n", "BUG: bad\n" and "other output\n"
    97  var tests = []*Test{
    98  	{
    99  		Name: "program-exits-normally",
   100  		Exit: ExitNormal,
   101  		Body: func(outc chan []byte, errc chan error) {
   102  			time.Sleep(time.Second)
   103  			errc <- nil
   104  		},
   105  	},
   106  	{
   107  		Name: "program-exits-when-it-should-not",
   108  		Body: func(outc chan []byte, errc chan error) {
   109  			time.Sleep(time.Second)
   110  			errc <- nil
   111  		},
   112  		Report: &report.Report{
   113  			Title: lostConnectionCrash,
   114  		},
   115  	},
   116  	{
   117  		Name:        "#875-diagnose-bugs",
   118  		Exit:        ExitNormal,
   119  		DiagnoseBug: true,
   120  		Body: func(outc chan []byte, errc chan error) {
   121  			errc <- nil
   122  		},
   123  	},
   124  	{
   125  		Name: "#875-diagnose-bugs-2",
   126  		Body: func(outc chan []byte, errc chan error) {
   127  			errc <- nil
   128  		},
   129  		Report: &report.Report{
   130  			Title: lostConnectionCrash,
   131  			Output: []byte(
   132  				"DIAGNOSE\n",
   133  			),
   134  		},
   135  	},
   136  	{
   137  		Name: "diagnose-no-wait",
   138  		Body: func(outc chan []byte, errc chan error) {
   139  			errc <- nil
   140  		},
   141  		DiagnoseNoWait: true,
   142  		Report: &report.Report{
   143  			Title: lostConnectionCrash,
   144  			Output: []byte(
   145  				"\n" +
   146  					"VM DIAGNOSIS:\n" +
   147  					"DIAGNOSE\n",
   148  			),
   149  		},
   150  	},
   151  	{
   152  		Name: "diagnose-bug-no-wait",
   153  		Body: func(outc chan []byte, errc chan error) {
   154  			outc <- []byte("BUG: bad\n")
   155  			time.Sleep(time.Second)
   156  			outc <- []byte("other output\n")
   157  		},
   158  		DiagnoseNoWait: true,
   159  		Report: &report.Report{
   160  			Title: "BUG: bad",
   161  			Report: []byte(
   162  				"BUG: bad\n" +
   163  					"other output\n",
   164  			),
   165  			Output: []byte(
   166  				"BUG: bad\n" +
   167  					"other output\n" +
   168  					"\n" +
   169  					"VM DIAGNOSIS:\n" +
   170  					"DIAGNOSE\n",
   171  			),
   172  		},
   173  	},
   174  	{
   175  		Name: "kernel-crashes",
   176  		Body: func(outc chan []byte, errc chan error) {
   177  			outc <- []byte("BUG: bad\n")
   178  			time.Sleep(time.Second)
   179  			outc <- []byte("other output\n")
   180  		},
   181  		Report: &report.Report{
   182  			Title: "BUG: bad",
   183  			Report: []byte(
   184  				"BUG: bad\n" +
   185  					"DIAGNOSE\n" +
   186  					"other output\n",
   187  			),
   188  		},
   189  	},
   190  	{
   191  		Name: "fuzzer-is-preempted",
   192  		Body: func(outc chan []byte, errc chan error) {
   193  			outc <- []byte("BUG: bad\n")
   194  			outc <- []byte(fuzzerPreemptedStr + "\n")
   195  		},
   196  	},
   197  	{
   198  		Name: "program-exits-but-kernel-crashes-afterwards",
   199  		Exit: ExitNormal,
   200  		Body: func(outc chan []byte, errc chan error) {
   201  			errc <- nil
   202  			time.Sleep(time.Second)
   203  			outc <- []byte("BUG: bad\n")
   204  		},
   205  		Report: &report.Report{
   206  			Title: "BUG: bad",
   207  			Report: []byte(
   208  				"BUG: bad\n" +
   209  					"DIAGNOSE\n",
   210  			),
   211  		},
   212  	},
   213  	{
   214  		Name: "timeout",
   215  		Exit: ExitTimeout,
   216  		Body: func(outc chan []byte, errc chan error) {
   217  			errc <- vmimpl.ErrTimeout
   218  		},
   219  	},
   220  	{
   221  		Name: "bad-timeout",
   222  		Body: func(outc chan []byte, errc chan error) {
   223  			errc <- vmimpl.ErrTimeout
   224  		},
   225  		Report: &report.Report{
   226  			Title: timeoutCrash,
   227  		},
   228  	},
   229  	{
   230  		Name: "program-crashes",
   231  		Body: func(outc chan []byte, errc chan error) {
   232  			errc <- fmt.Errorf("error")
   233  		},
   234  		Report: &report.Report{
   235  			Title: lostConnectionCrash,
   236  		},
   237  	},
   238  	{
   239  		Name: "program-crashes-expected",
   240  		Exit: ExitError,
   241  		Body: func(outc chan []byte, errc chan error) {
   242  			errc <- fmt.Errorf("error")
   243  		},
   244  	},
   245  	{
   246  		Name: "no-output-1",
   247  		Body: func(outc chan []byte, errc chan error) {
   248  		},
   249  		Report: &report.Report{
   250  			Title: noOutputCrash,
   251  		},
   252  	},
   253  	{
   254  		Name: "no-output-2",
   255  		Body: func(outc chan []byte, errc chan error) {
   256  			for i := 0; i < 5; i++ {
   257  				time.Sleep(time.Second)
   258  				outc <- []byte("something\n")
   259  			}
   260  		},
   261  		Report: &report.Report{
   262  			Title: noOutputCrash,
   263  		},
   264  	},
   265  	{
   266  		Name: "no-no-output-1",
   267  		Exit: ExitNormal,
   268  		Body: func(outc chan []byte, errc chan error) {
   269  			for i := 0; i < 5; i++ {
   270  				time.Sleep(time.Second)
   271  				outc <- append(executingProgram1, '\n')
   272  			}
   273  			errc <- nil
   274  		},
   275  	},
   276  	{
   277  		Name: "no-no-output-2",
   278  		Exit: ExitNormal,
   279  		Body: func(outc chan []byte, errc chan error) {
   280  			for i := 0; i < 5; i++ {
   281  				time.Sleep(time.Second)
   282  				outc <- append(executingProgram2, '\n')
   283  			}
   284  			errc <- nil
   285  		},
   286  	},
   287  	{
   288  		Name: "outc-closed",
   289  		Exit: ExitTimeout,
   290  		Body: func(outc chan []byte, errc chan error) {
   291  			close(outc)
   292  			time.Sleep(time.Second)
   293  			errc <- vmimpl.ErrTimeout
   294  		},
   295  	},
   296  	{
   297  		Name: "lots-of-output",
   298  		Exit: ExitTimeout,
   299  		Body: func(outc chan []byte, errc chan error) {
   300  			for i := 0; i < 100; i++ {
   301  				outc <- []byte("something\n")
   302  			}
   303  			time.Sleep(time.Second)
   304  			errc <- vmimpl.ErrTimeout
   305  		},
   306  	},
   307  	{
   308  		Name: "split-line",
   309  		Exit: ExitNormal,
   310  		Body: func(outc chan []byte, errc chan error) {
   311  			// "ODEBUG:" lines should be ignored, however the matchPos logic
   312  			// used to trim the lines so that we could see just "BUG:" later
   313  			// and detect it as crash.
   314  			buf := new(bytes.Buffer)
   315  			for i := 0; i < 50; i++ {
   316  				buf.WriteString("[ 2886.597572] ODEBUG: Out of memory. ODEBUG disabled\n")
   317  				buf.Write(bytes.Repeat([]byte{'-'}, i))
   318  				buf.WriteByte('\n')
   319  			}
   320  			output := buf.Bytes()
   321  			for i := range output {
   322  				outc <- output[i : i+1]
   323  			}
   324  			errc <- nil
   325  		},
   326  	},
   327  	{
   328  		Name:         "inject-error",
   329  		Exit:         ExitNormal,
   330  		InjectOutput: "BUG: foo\n",
   331  		Body: func(outc chan []byte, errc chan error) {
   332  			time.Sleep(time.Second)
   333  			errc <- nil
   334  		},
   335  		Report: &report.Report{
   336  			Title:  "BUG: foo",
   337  			Report: []byte("BUG: foo\nDIAGNOSE\n"),
   338  		},
   339  	},
   340  	{
   341  		Name:         "inject-output",
   342  		Exit:         ExitNormal,
   343  		InjectOutput: "INJECTED\n",
   344  		Body: func(outc chan []byte, errc chan error) {
   345  			time.Sleep(time.Second)
   346  			outc <- []byte("BUG: foo\n")
   347  		},
   348  		Report: &report.Report{
   349  			Title:  "BUG: foo",
   350  			Report: []byte("INJECTED\nBUG: foo\nDIAGNOSE\n"),
   351  		},
   352  	},
   353  }
   354  
   355  func TestMonitorExecution(t *testing.T) {
   356  	for _, test := range tests {
   357  		test := test
   358  		t.Run(test.Name, func(t *testing.T) {
   359  			t.Parallel()
   360  			testMonitorExecution(t, test)
   361  		})
   362  	}
   363  }
   364  
   365  func testMonitorExecution(t *testing.T, test *Test) {
   366  	dir := t.TempDir()
   367  	cfg := &mgrconfig.Config{
   368  		Derived: mgrconfig.Derived{
   369  			TargetOS:     targets.Linux,
   370  			TargetArch:   targets.AMD64,
   371  			TargetVMArch: targets.AMD64,
   372  			Timeouts: targets.Timeouts{
   373  				Scale:    1,
   374  				Slowdown: 1,
   375  				NoOutput: 5 * time.Second,
   376  			},
   377  			SysTarget: targets.Get(targets.Linux, targets.AMD64),
   378  		},
   379  		Workdir: dir,
   380  		Type:    "test",
   381  	}
   382  	pool, err := Create(cfg, false)
   383  	if err != nil {
   384  		t.Fatal(err)
   385  	}
   386  	defer pool.Close()
   387  	reporter, err := report.NewReporter(cfg)
   388  	if err != nil {
   389  		t.Fatal(err)
   390  	}
   391  	inst, err := pool.Create(0)
   392  	if err != nil {
   393  		t.Fatal(err)
   394  	}
   395  	defer inst.Close()
   396  	testInst := inst.impl.(*testInstance)
   397  	testInst.diagnoseBug = test.DiagnoseBug
   398  	testInst.diagnoseNoWait = test.DiagnoseNoWait
   399  	done := make(chan bool)
   400  	go func() {
   401  		test.Body(testInst.outc, testInst.errc)
   402  		done <- true
   403  	}()
   404  	finishCalled := 0
   405  	finishCb := EarlyFinishCb(func() { finishCalled++ })
   406  	opts := []any{test.Exit, finishCb}
   407  	if test.InjectOutput != "" {
   408  		c := make(chan []byte, 1)
   409  		c <- []byte(test.InjectOutput)
   410  		opts = append(opts, InjectOutput(c))
   411  	}
   412  	_, rep, err := inst.Run(time.Second, reporter, "", opts...)
   413  	if err != nil {
   414  		t.Fatal(err)
   415  	}
   416  	<-done
   417  	if finishCalled != 1 {
   418  		t.Fatalf("finish callback is called %v times", finishCalled)
   419  	}
   420  	if test.Report != nil && rep == nil {
   421  		t.Fatalf("got no report")
   422  	}
   423  	if test.Report == nil && rep != nil {
   424  		t.Fatalf("got unexpected report: %v", rep.Title)
   425  	}
   426  	if test.Report == nil {
   427  		return
   428  	}
   429  	if test.Report.Title != rep.Title {
   430  		t.Fatalf("want title %q, got title %q", test.Report.Title, rep.Title)
   431  	}
   432  	if !bytes.Equal(test.Report.Report, rep.Report) {
   433  		t.Fatalf("want report:\n%s\n\ngot report:\n%s", test.Report.Report, rep.Report)
   434  	}
   435  	if test.Report.Output != nil && !bytes.Equal(test.Report.Output, rep.Output) {
   436  		t.Fatalf("want output:\n%s\n\ngot output:\n%s", test.Report.Output, rep.Output)
   437  	}
   438  }
   439  
   440  func TestVMType(t *testing.T) {
   441  	testCases := []struct {
   442  		in   string
   443  		want string
   444  	}{
   445  		{"gvisor", "gvisor"},
   446  		{"proxyapp:android", "proxyapp"},
   447  	}
   448  
   449  	for _, tc := range testCases {
   450  		if got := vmType(tc.in); got != tc.want {
   451  			t.Errorf("vmType(%q) = %q, want %q", tc.in, got, tc.want)
   452  		}
   453  	}
   454  }