github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/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  	"context"
     8  	"fmt"
     9  	"math/rand"
    10  	"regexp"
    11  	"sort"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/google/go-cmp/cmp"
    16  	"github.com/google/syzkaller/pkg/csource"
    17  	"github.com/google/syzkaller/pkg/flatrpc"
    18  	"github.com/google/syzkaller/pkg/instance"
    19  	"github.com/google/syzkaller/pkg/mgrconfig"
    20  	"github.com/google/syzkaller/pkg/report"
    21  	"github.com/google/syzkaller/pkg/testutil"
    22  	"github.com/google/syzkaller/prog"
    23  	"github.com/google/syzkaller/sys/targets"
    24  	"github.com/stretchr/testify/assert"
    25  	"github.com/stretchr/testify/require"
    26  )
    27  
    28  func initTest(t *testing.T) (*rand.Rand, int) {
    29  	iters := 1000
    30  	if testing.Short() {
    31  		iters = 100
    32  	}
    33  	return rand.New(testutil.RandSource(t)), iters
    34  }
    35  
    36  func TestBisect(t *testing.T) {
    37  	ctx := &reproContext{
    38  		stats: new(Stats),
    39  		logf:  t.Logf,
    40  	}
    41  
    42  	rd, iters := initTest(t)
    43  	for n := 0; n < iters; n++ {
    44  		var progs []*prog.LogEntry
    45  		numTotal := rd.Intn(300)
    46  		numGuilty := 0
    47  		for i := 0; i < numTotal; i++ {
    48  			var prog prog.LogEntry
    49  			if rd.Intn(30) == 0 {
    50  				prog.Proc = 42
    51  				numGuilty++
    52  			}
    53  			progs = append(progs, &prog)
    54  		}
    55  		if numGuilty == 0 {
    56  			var prog prog.LogEntry
    57  			prog.Proc = 42
    58  			progs = append(progs, &prog)
    59  			numGuilty++
    60  		}
    61  		progs, _ = ctx.bisectProgs(progs, func(p []*prog.LogEntry) (bool, error) {
    62  			guilty := 0
    63  			for _, prog := range p {
    64  				if prog.Proc == 42 {
    65  					guilty++
    66  				}
    67  			}
    68  			return guilty == numGuilty, nil
    69  		})
    70  		if numGuilty > 6 && len(progs) == 0 {
    71  			// Bisection has been aborted.
    72  			continue
    73  		}
    74  		if len(progs) != numGuilty {
    75  			t.Fatalf("bisect test failed: wrong number of guilty progs: got: %v, want: %v", len(progs), numGuilty)
    76  		}
    77  		for _, prog := range progs {
    78  			if prog.Proc != 42 {
    79  				t.Fatalf("bisect test failed: wrong program is guilty: progs: %v", progs)
    80  			}
    81  		}
    82  	}
    83  }
    84  
    85  func TestSimplifies(t *testing.T) {
    86  	opts := csource.Options{
    87  		Threaded:     true,
    88  		Repeat:       true,
    89  		Procs:        10,
    90  		Sandbox:      "namespace",
    91  		NetInjection: true,
    92  		NetDevices:   true,
    93  		NetReset:     true,
    94  		Cgroups:      true,
    95  		UseTmpDir:    true,
    96  		HandleSegv:   true,
    97  	}
    98  	var check func(opts csource.Options, i int)
    99  	check = func(opts csource.Options, i int) {
   100  		if err := opts.Check(targets.Linux); err != nil {
   101  			t.Fatalf("opts are invalid: %v", err)
   102  		}
   103  		if i == len(cSimplifies) {
   104  			return
   105  		}
   106  		check(opts, i+1)
   107  		if cSimplifies[i](&opts) {
   108  			check(opts, i+1)
   109  		}
   110  	}
   111  	check(opts, 0)
   112  }
   113  
   114  type testExecInterface struct {
   115  	// For now only do the simplest imitation.
   116  	run func([]byte) (*instance.RunResult, error)
   117  }
   118  
   119  func (tei *testExecInterface) Run(_ context.Context, params instance.ExecParams,
   120  	_ instance.ExecutorLogger) (*instance.RunResult, error) {
   121  	syzProg := params.SyzProg
   122  	if params.CProg != nil {
   123  		syzProg = params.CProg.Serialize()
   124  	}
   125  	return tei.run(syzProg)
   126  }
   127  
   128  func runTestRepro(t *testing.T, log string, exec execInterface) (*Result, *Stats, error) {
   129  	mgrConfig := &mgrconfig.Config{
   130  		Derived: mgrconfig.Derived{
   131  			TargetOS:     targets.Linux,
   132  			TargetVMArch: targets.AMD64,
   133  		},
   134  		Sandbox: "namespace",
   135  	}
   136  	var err error
   137  	mgrConfig.Target, err = prog.GetTarget(targets.Linux, targets.AMD64)
   138  	if err != nil {
   139  		t.Fatal(err)
   140  	}
   141  	reporter, err := report.NewReporter(mgrConfig)
   142  	if err != nil {
   143  		t.Fatal(err)
   144  	}
   145  	env := Environment{
   146  		Config:   mgrConfig,
   147  		Features: flatrpc.AllFeatures,
   148  		Fast:     false,
   149  		Reporter: reporter,
   150  		logf:     t.Logf,
   151  	}
   152  	return runInner(context.Background(), []byte(log), env, exec)
   153  }
   154  
   155  const testReproLog = `
   156  2015/12/21 12:18:05 executing program 1:
   157  getpid()
   158  pause()
   159  2015/12/21 12:18:10 executing program 2:
   160  getpid()
   161  getuid()
   162  2015/12/21 12:18:15 executing program 1:
   163  alarm(0x5)
   164  pause()
   165  2015/12/21 12:18:20 executing program 3:
   166  alarm(0xa)
   167  getpid()
   168  `
   169  
   170  // Only crash if `pause()` is followed by `alarm(0xa)`.
   171  var testCrashCondition = regexp.MustCompile(`(?s)pause\(\).*alarm\(0xa\)`)
   172  
   173  var (
   174  	expectedReproducer = "pause()\nalarm(0xa)\n"
   175  )
   176  
   177  func fakeCrashResult(title string) *instance.RunResult {
   178  	ret := &instance.RunResult{}
   179  	if title != "" {
   180  		ret.Report = &report.Report{
   181  			Title: title,
   182  		}
   183  	}
   184  	return ret
   185  }
   186  
   187  func testExecRunner(log []byte) (*instance.RunResult, error) {
   188  	crash := testCrashCondition.Match(log)
   189  	if crash {
   190  		return fakeCrashResult("crashed"), nil
   191  	}
   192  	return fakeCrashResult(""), nil
   193  }
   194  
   195  // Just a pkg/repro smoke test: check that we can extract a two-call reproducer.
   196  // No focus on error handling and minor corner cases.
   197  func TestPlainRepro(t *testing.T) {
   198  	result, _, err := runTestRepro(t, testReproLog, &testExecInterface{
   199  		run: testExecRunner,
   200  	})
   201  	if err != nil {
   202  		t.Fatal(err)
   203  	}
   204  	if diff := cmp.Diff(expectedReproducer, string(result.Prog.Serialize())); diff != "" {
   205  		t.Fatal(diff)
   206  	}
   207  }
   208  
   209  // There happen to be transient errors like ssh/scp connection failures.
   210  // Ensure that the code just retries.
   211  func TestVMErrorResilience(t *testing.T) {
   212  	fail := false
   213  	result, _, err := runTestRepro(t, testReproLog, &testExecInterface{
   214  		run: func(log []byte) (*instance.RunResult, error) {
   215  			fail = !fail
   216  			if fail {
   217  				return nil, fmt.Errorf("some random error")
   218  			}
   219  			return testExecRunner(log)
   220  		},
   221  	})
   222  	if err != nil {
   223  		t.Fatal(err)
   224  	}
   225  	if diff := cmp.Diff(`pause()
   226  alarm(0xa)
   227  `, string(result.Prog.Serialize())); diff != "" {
   228  		t.Fatal(diff)
   229  	}
   230  }
   231  
   232  func TestTooManyErrors(t *testing.T) {
   233  	counter := 0
   234  	_, _, err := runTestRepro(t, testReproLog, &testExecInterface{
   235  		run: func(log []byte) (*instance.RunResult, error) {
   236  			counter++
   237  			if counter%4 != 0 {
   238  				return nil, fmt.Errorf("some random error")
   239  			}
   240  			return testExecRunner(log)
   241  		},
   242  	})
   243  	if err == nil {
   244  		t.Fatalf("expected an error")
   245  	}
   246  }
   247  
   248  func TestProgConcatenation(t *testing.T) {
   249  	// Since the crash condition is alarm() after pause(), the code
   250  	// would have to work around the prog.MaxCall limitation.
   251  	execLog := "2015/12/21 12:18:05 executing program 1:\n"
   252  	for i := 0; i < prog.MaxCalls; i++ {
   253  		if i == 10 {
   254  			execLog += "pause()\n"
   255  		} else {
   256  			execLog += "getpid()\n"
   257  		}
   258  	}
   259  	execLog += "2015/12/21 12:18:10 executing program 2:\n"
   260  	for i := 0; i < prog.MaxCalls; i++ {
   261  		if i == 10 {
   262  			execLog += "alarm(0xa)\n"
   263  		} else {
   264  			execLog += "getpid()\n"
   265  		}
   266  	}
   267  	result, _, err := runTestRepro(t, execLog, &testExecInterface{
   268  		run: testExecRunner,
   269  	})
   270  	if err != nil {
   271  		t.Fatal(err)
   272  	}
   273  	if diff := cmp.Diff(`pause()
   274  alarm(0xa)
   275  `, string(result.Prog.Serialize())); diff != "" {
   276  		t.Fatal(diff)
   277  	}
   278  }
   279  
   280  func TestFlakyCrashes(t *testing.T) {
   281  	t.Parallel()
   282  	// A single flaky crash may divert the whole process.
   283  	// Let's check if the Reliability score provides a reasonable cut-off for such fake results.
   284  
   285  	r := rand.New(testutil.RandSource(t))
   286  	iters := 250
   287  
   288  	success := 0
   289  	for i := 0; i < iters; i++ {
   290  		counter, lastFake := 0, 0
   291  		result, _, err := runTestRepro(t, testReproLog, &testExecInterface{
   292  			run: func(log []byte) (*instance.RunResult, error) {
   293  				// Throw in a fake crash with 5% probability,
   294  				// but not more often than once in 10 consecutive runs.
   295  				counter++
   296  				if r.Intn(20) == 0 && counter-lastFake >= 10 {
   297  					lastFake = counter
   298  					return fakeCrashResult("flaky crash"), nil
   299  				}
   300  				return testExecRunner(log)
   301  			},
   302  		})
   303  		// It should either find nothing (=> validation worked) or find the exact reproducer.
   304  		require.NoError(t, err)
   305  		if result == nil {
   306  			continue
   307  		}
   308  		success++
   309  		assert.Equal(t, expectedReproducer, string(result.Prog.Serialize()), "reliability: %.2f", result.Reliability)
   310  	}
   311  
   312  	// There was no deep reasoning behind the success rate. It's not 100% due to flakiness,
   313  	// but there should still be some significant number of success cases.
   314  	assert.Greater(t, success, iters/3*2, "must succeed >2/3 of cases")
   315  }
   316  
   317  func BenchmarkCalculateReliability(b *testing.B) {
   318  	r := rand.New(rand.NewSource(time.Now().UnixNano()))
   319  
   320  	for base := 0.0; base < 1.0; base += 0.1 {
   321  		b.Run(fmt.Sprintf("p=%.2f", base), func(b *testing.B) {
   322  			if b.N == 0 {
   323  				return
   324  			}
   325  			neededRuns := make([]int, 0, b.N)
   326  			reliability := make([]float64, 0, b.N)
   327  
   328  			b.ResetTimer()
   329  			for i := 0; i < b.N; i++ {
   330  				runs := 0
   331  				ret, err := calculateReliability(func() (bool, error) {
   332  					runs++
   333  					return r.Float64() < base, nil
   334  				})
   335  				require.NoError(b, err)
   336  				neededRuns = append(neededRuns, runs)
   337  				reliability = append(reliability, ret)
   338  			}
   339  			b.StopTimer()
   340  
   341  			sort.Ints(neededRuns)
   342  			b.ReportMetric(float64(neededRuns[len(neededRuns)/2]), "runs")
   343  
   344  			sort.Float64s(reliability)
   345  			b.ReportMetric(reliability[len(reliability)/10], "p10")
   346  			b.ReportMetric(reliability[len(reliability)/2], "median")
   347  			b.ReportMetric(reliability[len(reliability)*9/10], "p90")
   348  		})
   349  	}
   350  }