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 }