github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/pkg/repro/repro_test.go (about)

     1  // Copyright 2017 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 repro
     5  
     6  import (
     7  	"fmt"
     8  	"math/rand"
     9  	"regexp"
    10  	"sync"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/google/go-cmp/cmp"
    15  	"github.com/google/syzkaller/pkg/csource"
    16  	"github.com/google/syzkaller/pkg/flatrpc"
    17  	"github.com/google/syzkaller/pkg/instance"
    18  	"github.com/google/syzkaller/pkg/mgrconfig"
    19  	"github.com/google/syzkaller/pkg/report"
    20  	"github.com/google/syzkaller/pkg/testutil"
    21  	"github.com/google/syzkaller/prog"
    22  	"github.com/google/syzkaller/sys/targets"
    23  )
    24  
    25  func initTest(t *testing.T) (*rand.Rand, int) {
    26  	iters := 1000
    27  	if testing.Short() {
    28  		iters = 100
    29  	}
    30  	return rand.New(testutil.RandSource(t)), iters
    31  }
    32  
    33  func TestBisect(t *testing.T) {
    34  	ctx := &context{
    35  		stats: new(Stats),
    36  		logf:  t.Logf,
    37  	}
    38  
    39  	rd, iters := initTest(t)
    40  	for n := 0; n < iters; n++ {
    41  		var progs []*prog.LogEntry
    42  		numTotal := rd.Intn(300)
    43  		numGuilty := 0
    44  		for i := 0; i < numTotal; i++ {
    45  			var prog prog.LogEntry
    46  			if rd.Intn(30) == 0 {
    47  				prog.Proc = 42
    48  				numGuilty++
    49  			}
    50  			progs = append(progs, &prog)
    51  		}
    52  		if numGuilty == 0 {
    53  			var prog prog.LogEntry
    54  			prog.Proc = 42
    55  			progs = append(progs, &prog)
    56  			numGuilty++
    57  		}
    58  		progs, _ = ctx.bisectProgs(progs, func(p []*prog.LogEntry) (bool, error) {
    59  			guilty := 0
    60  			for _, prog := range p {
    61  				if prog.Proc == 42 {
    62  					guilty++
    63  				}
    64  			}
    65  			return guilty == numGuilty, nil
    66  		})
    67  		if numGuilty > 6 && len(progs) == 0 {
    68  			// Bisection has been aborted.
    69  			continue
    70  		}
    71  		if len(progs) != numGuilty {
    72  			t.Fatalf("bisect test failed: wrong number of guilty progs: got: %v, want: %v", len(progs), numGuilty)
    73  		}
    74  		for _, prog := range progs {
    75  			if prog.Proc != 42 {
    76  				t.Fatalf("bisect test failed: wrong program is guilty: progs: %v", progs)
    77  			}
    78  		}
    79  	}
    80  }
    81  
    82  func TestSimplifies(t *testing.T) {
    83  	opts := csource.Options{
    84  		Threaded:     true,
    85  		Repeat:       true,
    86  		Procs:        10,
    87  		Sandbox:      "namespace",
    88  		NetInjection: true,
    89  		NetDevices:   true,
    90  		NetReset:     true,
    91  		Cgroups:      true,
    92  		UseTmpDir:    true,
    93  		HandleSegv:   true,
    94  	}
    95  	var check func(opts csource.Options, i int)
    96  	check = func(opts csource.Options, i int) {
    97  		if err := opts.Check(targets.Linux); err != nil {
    98  			t.Fatalf("opts are invalid: %v", err)
    99  		}
   100  		if i == len(cSimplifies) {
   101  			return
   102  		}
   103  		check(opts, i+1)
   104  		if cSimplifies[i](&opts) {
   105  			check(opts, i+1)
   106  		}
   107  	}
   108  	check(opts, 0)
   109  }
   110  
   111  func generateTestInstances(ctx *context, count int, execInterface execInterface) {
   112  	for i := 0; i < count; i++ {
   113  		ctx.bootRequests <- i
   114  	}
   115  	var wg sync.WaitGroup
   116  	wg.Add(1)
   117  	go func() {
   118  		defer wg.Done()
   119  		for vmIndex := range ctx.bootRequests {
   120  			ctx.instances <- &reproInstance{execProg: execInterface, index: vmIndex}
   121  		}
   122  	}()
   123  	wg.Wait()
   124  }
   125  
   126  type testExecInterface struct {
   127  	t *testing.T
   128  	// For now only do the simplest imitation.
   129  	run func([]byte) (*instance.RunResult, error)
   130  }
   131  
   132  func (tei *testExecInterface) Close() {}
   133  
   134  func (tei *testExecInterface) RunCProg(p *prog.Prog, duration time.Duration,
   135  	opts csource.Options) (*instance.RunResult, error) {
   136  	return tei.RunSyzProg(p.Serialize(), duration, opts)
   137  }
   138  
   139  func (tei *testExecInterface) RunSyzProg(syzProg []byte, duration time.Duration,
   140  	opts csource.Options) (*instance.RunResult, error) {
   141  	return tei.run(syzProg)
   142  }
   143  
   144  func prepareTestCtx(t *testing.T, log string) *context {
   145  	mgrConfig := &mgrconfig.Config{
   146  		Derived: mgrconfig.Derived{
   147  			TargetOS:     targets.Linux,
   148  			TargetVMArch: targets.AMD64,
   149  		},
   150  		Sandbox: "namespace",
   151  	}
   152  	var err error
   153  	mgrConfig.Target, err = prog.GetTarget(targets.Linux, targets.AMD64)
   154  	if err != nil {
   155  		t.Fatal(err)
   156  	}
   157  	reporter, err := report.NewReporter(mgrConfig)
   158  	if err != nil {
   159  		t.Fatal(err)
   160  	}
   161  	ctx, err := prepareCtx([]byte(log), mgrConfig, flatrpc.AllFeatures, reporter, 3)
   162  	if err != nil {
   163  		t.Fatal(err)
   164  	}
   165  	return ctx
   166  }
   167  
   168  const testReproLog = `
   169  2015/12/21 12:18:05 executing program 1:
   170  getpid()
   171  pause()
   172  2015/12/21 12:18:10 executing program 2:
   173  getpid()
   174  getuid()
   175  2015/12/21 12:18:15 executing program 1:
   176  alarm(0x5)
   177  pause()
   178  2015/12/21 12:18:20 executing program 3:
   179  alarm(0xa)
   180  getpid()
   181  `
   182  
   183  // Only crash if `pause()` is followed by `alarm(0xa)`.
   184  var testCrashCondition = regexp.MustCompile(`(?s)pause\(\).*alarm\(0xa\)`)
   185  
   186  func testExecRunner(log []byte) (*instance.RunResult, error) {
   187  	crash := testCrashCondition.Match(log)
   188  	if crash {
   189  		ret := &instance.RunResult{}
   190  		ret.Report = &report.Report{
   191  			Title: `some crash`,
   192  		}
   193  		return ret, nil
   194  	}
   195  	return &instance.RunResult{}, nil
   196  }
   197  
   198  // Just a pkg/repro smoke test: check that we can extract a two-call reproducer.
   199  // No focus on error handling and minor corner cases.
   200  func TestPlainRepro(t *testing.T) {
   201  	ctx := prepareTestCtx(t, testReproLog)
   202  	go generateTestInstances(ctx, 3, &testExecInterface{
   203  		t:   t,
   204  		run: testExecRunner,
   205  	})
   206  	result, _, err := ctx.run()
   207  	if err != nil {
   208  		t.Fatal(err)
   209  	}
   210  	if diff := cmp.Diff(`pause()
   211  alarm(0xa)
   212  `, string(result.Prog.Serialize())); diff != "" {
   213  		t.Fatal(diff)
   214  	}
   215  }
   216  
   217  // There happen to be transient errors like ssh/scp connection failures.
   218  // Ensure that the code just retries.
   219  func TestVMErrorResilience(t *testing.T) {
   220  	ctx := prepareTestCtx(t, testReproLog)
   221  	fail := false
   222  	go generateTestInstances(ctx, 3, &testExecInterface{
   223  		t: t,
   224  		run: func(log []byte) (*instance.RunResult, error) {
   225  			fail = !fail
   226  			if fail {
   227  				return nil, fmt.Errorf("some random error")
   228  			}
   229  			return testExecRunner(log)
   230  		},
   231  	})
   232  	result, _, err := ctx.run()
   233  	if err != nil {
   234  		t.Fatal(err)
   235  	}
   236  	if diff := cmp.Diff(`pause()
   237  alarm(0xa)
   238  `, string(result.Prog.Serialize())); diff != "" {
   239  		t.Fatal(diff)
   240  	}
   241  }
   242  
   243  func TestTooManyErrors(t *testing.T) {
   244  	ctx := prepareTestCtx(t, testReproLog)
   245  	counter := 0
   246  	go generateTestInstances(ctx, 3, &testExecInterface{
   247  		t: t,
   248  		run: func(log []byte) (*instance.RunResult, error) {
   249  			counter++
   250  			if counter%4 != 0 {
   251  				return nil, fmt.Errorf("some random error")
   252  			}
   253  			return testExecRunner(log)
   254  		},
   255  	})
   256  	_, _, err := ctx.run()
   257  	if err == nil {
   258  		t.Fatalf("expected an error")
   259  	}
   260  }