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 }