github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/prog/minimization_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 prog 5 6 import ( 7 "fmt" 8 "math/rand" 9 "strings" 10 "testing" 11 12 "github.com/google/syzkaller/pkg/hash" 13 ) 14 15 // nolint:gocyclo 16 func TestMinimize(t *testing.T) { 17 attempt := 0 18 // nolint: lll 19 tests := []struct { 20 os string 21 arch string 22 mode MinimizeMode 23 orig string 24 callIndex int 25 pred func(*Prog, int) bool 26 result string 27 resultCallIndex int 28 }{ 29 // Predicate always returns false, so must get the same program. 30 { 31 "linux", "amd64", MinimizeCorpus, 32 "mmap(&(0x7f0000000000/0x1000)=nil, 0x1000, 0x3, 0x32, 0xffffffffffffffff, 0x0)\n" + 33 "sched_yield()\n" + 34 "pipe2(&(0x7f0000000000), 0x0)\n", 35 2, 36 func(p *Prog, callIndex int) bool { 37 if len(p.Calls) == 0 { 38 t.Fatalf("got an empty program") 39 } 40 if p.Calls[len(p.Calls)-1].Meta.Name != "pipe2" { 41 t.Fatalf("last call is removed") 42 } 43 return false 44 }, 45 "mmap(&(0x7f0000000000/0x1000)=nil, 0x1000, 0x3, 0x32, 0xffffffffffffffff, 0x0)\n" + 46 "sched_yield()\n" + 47 "pipe2(&(0x7f0000000000), 0x0)\n", 48 2, 49 }, 50 // Remove a call. 51 { 52 "linux", "amd64", MinimizeCorpus, 53 "mmap(&(0x7f0000000000/0x1000)=nil, 0x1000, 0x3, 0x32, 0xffffffffffffffff, 0x0)\n" + 54 "sched_yield()\n" + 55 "pipe2(&(0x7f0000000000)={0xffffffffffffffff, 0xffffffffffffffff}, 0x0)\n", 56 2, 57 func(p *Prog, callIndex int) bool { 58 // Aim at removal of sched_yield. 59 return len(p.Calls) == 2 && p.Calls[0].Meta.Name == "mmap" && p.Calls[1].Meta.Name == "pipe2" 60 }, 61 "mmap(&(0x7f0000000000/0x1000)=nil, 0x1000, 0x3, 0x32, 0xffffffffffffffff, 0x0)\n" + 62 "pipe2(0x0, 0x0)\n", 63 1, 64 }, 65 // Remove two dependent calls. 66 { 67 "linux", "amd64", MinimizeCorpus, 68 "mmap(&(0x7f0000000000/0x1000)=nil, 0x1000, 0x3, 0x32, 0xffffffffffffffff, 0x0)\n" + 69 "pipe2(&(0x7f0000000000)={0x0, 0x0}, 0x0)\n" + 70 "sched_yield()\n", 71 2, 72 func(p *Prog, callIndex int) bool { 73 // Aim at removal of pipe2 and then mmap. 74 if len(p.Calls) == 2 && p.Calls[0].Meta.Name == "mmap" && p.Calls[1].Meta.Name == "sched_yield" { 75 return true 76 } 77 if len(p.Calls) == 1 && p.Calls[0].Meta.Name == "sched_yield" { 78 return true 79 } 80 return false 81 }, 82 "sched_yield()\n", 83 0, 84 }, 85 // Remove a call and replace results. 86 { 87 "linux", "amd64", MinimizeCorpus, 88 "mmap(&(0x7f0000000000/0x1000)=nil, 0x1000, 0x3, 0x32, 0xffffffffffffffff, 0x0)\n" + 89 "pipe2(&(0x7f0000000000)={<r0=>0x0, 0x0}, 0x0)\n" + 90 "write(r0, &(0x7f0000000000)=\"1155\", 0x2)\n" + 91 "sched_yield()\n", 92 3, 93 func(p *Prog, callIndex int) bool { 94 return p.String() == "mmap-write-sched_yield" 95 }, 96 "mmap(&(0x7f0000000000/0x1000)=nil, 0x1000, 0x3, 0x32, 0xffffffffffffffff, 0x0)\n" + 97 "write(0xffffffffffffffff, 0x0, 0x0)\n" + 98 "sched_yield()\n", 99 2, 100 }, 101 // Remove a call and replace results. 102 { 103 "linux", "amd64", MinimizeCorpus, 104 "mmap(&(0x7f0000000000/0x1000)=nil, 0x1000, 0x3, 0x32, 0xffffffffffffffff, 0x0)\n" + 105 "r0=open(&(0x7f0000000000)=\"1155\", 0x0, 0x0)\n" + 106 "write(r0, &(0x7f0000000000)=\"1155\", 0x2)\n" + 107 "sched_yield()\n", 108 -1, 109 func(p *Prog, callIndex int) bool { 110 return p.String() == "mmap-write-sched_yield" 111 }, 112 "mmap(&(0x7f0000000000/0x1000)=nil, 0x1000, 0x3, 0x32, 0xffffffffffffffff, 0x0)\n" + 113 "write(0xffffffffffffffff, 0x0, 0x0)\n" + 114 "sched_yield()\n", 115 -1, 116 }, 117 // Minimize pointer. 118 { 119 "linux", "amd64", MinimizeCorpus, 120 "pipe2(&(0x7f0000001000)={0xffffffffffffffff, 0xffffffffffffffff}, 0x0)\n", 121 -1, 122 func(p *Prog, callIndex int) bool { 123 return len(p.Calls) == 1 && p.Calls[0].Meta.Name == "pipe2" 124 }, 125 "pipe2(0x0, 0x0)\n", 126 -1, 127 }, 128 // Minimize pointee. 129 { 130 "linux", "amd64", MinimizeCorpus, 131 "pipe2(&(0x7f0000001000)={0xffffffffffffffff, 0xffffffffffffffff}, 0x0)\n", 132 -1, 133 func(p *Prog, callIndex int) bool { 134 return len(p.Calls) == 1 && p.Calls[0].Meta.Name == "pipe2" && p.Calls[0].Args[0].(*PointerArg).Address != 0 135 }, 136 "pipe2(&(0x7f0000001000), 0x0)\n", 137 -1, 138 }, 139 // Make sure we don't hang when minimizing resources. 140 { 141 "test", "64", MinimizeCorpus, 142 "r0 = test$res0()\n" + 143 "test$res1(r0)\n", 144 -1, 145 func(p *Prog, callIndex int) bool { 146 return false 147 }, 148 "r0 = test$res0()\n" + 149 "test$res1(r0)\n", 150 -1, 151 }, 152 { 153 "test", "64", MinimizeCorpus, 154 "minimize$0(0x1, 0x1)\n", 155 -1, 156 func(p *Prog, callIndex int) bool { return len(p.Calls) == 1 }, 157 "minimize$0(0x1, 0x1)\n", 158 -1, 159 }, 160 // Clear unneeded fault injection. 161 { 162 "linux", "amd64", MinimizeCorpus, 163 "pipe2(0x0, 0x0) (fail_nth: 5)\n", 164 -1, 165 func(p *Prog, callIndex int) bool { 166 return len(p.Calls) == 1 && p.Calls[0].Meta.Name == "pipe2" 167 }, 168 "pipe2(0x0, 0x0)\n", 169 -1, 170 }, 171 // Keep important fault injection. 172 { 173 "linux", "amd64", MinimizeCorpus, 174 "pipe2(0x0, 0x0) (fail_nth: 5)\n", 175 -1, 176 func(p *Prog, callIndex int) bool { 177 return len(p.Calls) == 1 && p.Calls[0].Meta.Name == "pipe2" && p.Calls[0].Props.FailNth == 5 178 }, 179 "pipe2(0x0, 0x0) (fail_nth: 5)\n", 180 -1, 181 }, 182 // Clear unneeded async flag. 183 { 184 "linux", "amd64", MinimizeCorpus, 185 "pipe2(0x0, 0x0) (async)\n", 186 -1, 187 func(p *Prog, callIndex int) bool { 188 return len(p.Calls) == 1 && p.Calls[0].Meta.Name == "pipe2" 189 }, 190 "pipe2(0x0, 0x0)\n", 191 -1, 192 }, 193 // Keep important async flag. 194 { 195 "linux", "amd64", MinimizeCorpus, 196 "pipe2(0x0, 0x0) (async)\n", 197 -1, 198 func(p *Prog, callIndex int) bool { 199 return len(p.Calls) == 1 && p.Calls[0].Meta.Name == "pipe2" && p.Calls[0].Props.Async 200 }, 201 "pipe2(0x0, 0x0) (async)\n", 202 -1, 203 }, 204 // Clear unneeded rerun. 205 { 206 "linux", "amd64", MinimizeCorpus, 207 "pipe2(0x0, 0x0) (rerun: 100)\n", 208 -1, 209 func(p *Prog, callIndex int) bool { 210 return len(p.Calls) == 1 && p.Calls[0].Meta.Name == "pipe2" 211 }, 212 "pipe2(0x0, 0x0)\n", 213 -1, 214 }, 215 // Keep important rerun. 216 { 217 "linux", "amd64", MinimizeCorpus, 218 "pipe2(0x0, 0x0) (rerun: 100)\n", 219 -1, 220 func(p *Prog, callIndex int) bool { 221 return len(p.Calls) == 1 && p.Calls[0].Meta.Name == "pipe2" && p.Calls[0].Props.Rerun >= 100 222 }, 223 "pipe2(0x0, 0x0) (rerun: 100)\n", 224 -1, 225 }, 226 // Undo target.SpecialFileLenghts mutation (reduce file name length). 227 { 228 "test", "64", MinimizeCrash, 229 "mutate9(&(0x7f0000000000)='./file0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\\x00')\n", 230 0, 231 func(p *Prog, callIndex int) bool { 232 return p.Calls[0].Args[0].(*PointerArg).Res != nil 233 }, 234 "mutate9(&(0x7f0000000000)='./file0\\x00')\n", 235 0, 236 }, 237 // Ensure `no_minimize` calls are untouched. 238 { 239 "linux", "amd64", MinimizeCorpus, 240 "syz_mount_image$ext4(&(0x7f0000000000)='ext4\\x00', &(0x7f0000000100)='./file0\\x00', 0x0, &(0x7f0000010020), 0x1, 0x15, &(0x7f0000000200)=\"$eJwqrqzKTszJSS0CBAAA//8TyQPi\")\n", 241 0, 242 func(p *Prog, callIndex int) bool { 243 // Anything is allowed except removing a call. 244 return len(p.Calls) > 0 245 }, 246 "syz_mount_image$ext4(&(0x7f0000000000)='ext4\\x00', &(0x7f0000000100)='./file0\\x00', 0x0, &(0x7f0000010020), 0x1, 0x15, &(0x7f0000000200)=\"$eJwqrqzKTszJSS0CBAAA//8TyQPi\")\n", 247 0, 248 }, 249 // Test for removeUnrelatedCalls. 250 // We test exact candidates we get on each step. 251 // First candidate should be removal of the trailing calls, which we reject. 252 // Next candidate is removal of unrelated calls, which we accept. 253 { 254 "linux", "amd64", MinimizeCorpus, 255 ` 256 getpid() 257 r0 = open(&(0x7f0000000040)='./file0', 0x0, 0x0) 258 r1 = open(&(0x7f0000000040)='./file1', 0x0, 0x0) 259 getuid() 260 read(r1, &(0x7f0000000040), 0x10) 261 read(r0, &(0x7f0000000040), 0x10) 262 pipe(&(0x7f0000000040)={<r2=>0x0, <r3=>0x0}) 263 creat(&(0x7f0000000040)='./file0', 0x0) 264 close(r1) 265 sendfile(r0, r2, &(0x7f0000000040), 0x1) 266 getgid() 267 fcntl$getflags(r0, 0x0) 268 getpid() 269 close(r3) 270 getuid() 271 `, 272 11, 273 func(p *Prog, callIndex int) bool { 274 pp := strings.TrimSpace(string(p.Serialize())) 275 switch attempt { 276 case 0: 277 if pp == strings.TrimSpace(` 278 getpid() 279 r0 = open(&(0x7f0000000040)='./file0', 0x0, 0x0) 280 r1 = open(&(0x7f0000000040)='./file1', 0x0, 0x0) 281 getuid() 282 read(r1, &(0x7f0000000040), 0x10) 283 read(r0, &(0x7f0000000040), 0x10) 284 pipe(&(0x7f0000000040)={<r2=>0x0, 0x0}) 285 creat(&(0x7f0000000040)='./file0', 0x0) 286 close(r1) 287 sendfile(r0, r2, &(0x7f0000000040), 0x1) 288 getgid() 289 fcntl$getflags(r0, 0x0) 290 `) { 291 return false 292 } 293 case 1: 294 if pp == strings.TrimSpace(` 295 r0 = open(&(0x7f0000000040)='./file0', 0x0, 0x0) 296 read(r0, &(0x7f0000000040), 0x10) 297 pipe(&(0x7f0000000040)={<r1=>0x0, <r2=>0x0}) 298 creat(&(0x7f0000000040)='./file0', 0x0) 299 sendfile(r0, r1, &(0x7f0000000040), 0x1) 300 fcntl$getflags(r0, 0x0) 301 close(r2) 302 `) { 303 return true 304 } 305 default: 306 return false 307 } 308 panic(fmt.Sprintf("unexpected candidate on attempt %v:\n%v", attempt, pp)) 309 }, 310 ` 311 r0 = open(&(0x7f0000000040)='./file0', 0x0, 0x0) 312 read(r0, &(0x7f0000000040), 0x10) 313 pipe(&(0x7f0000000040)={<r1=>0x0, <r2=>0x0}) 314 creat(&(0x7f0000000040)='./file0', 0x0) 315 sendfile(r0, r1, &(0x7f0000000040), 0x1) 316 fcntl$getflags(r0, 0x0) 317 close(r2) 318 `, 319 5, 320 }, 321 } 322 t.Parallel() 323 for ti, test := range tests { 324 t.Run(fmt.Sprint(ti), func(t *testing.T) { 325 target, err := GetTarget(test.os, test.arch) 326 if err != nil { 327 t.Fatal(err) 328 } 329 p, err := target.Deserialize([]byte(strings.TrimSpace(test.orig)), Strict) 330 if err != nil { 331 t.Fatalf("failed to deserialize original program #%v: %v", ti, err) 332 } 333 attempt = 0 334 pred := func(p *Prog, callIndex int) bool { 335 res := test.pred(p, callIndex) 336 attempt++ 337 return res 338 } 339 p1, ci := Minimize(p, test.callIndex, test.mode, pred) 340 res := strings.TrimSpace(string(p1.Serialize())) 341 expect := strings.TrimSpace(test.result) 342 if res != expect { 343 t.Fatalf("minimization produced wrong result #%v\norig:\n%v\nexpect:\n%v\ngot:\n%v", 344 ti, test.orig, expect, res) 345 } 346 if ci != test.resultCallIndex { 347 t.Fatalf("minimization broke call index #%v: got %v, want %v", 348 ti, ci, test.resultCallIndex) 349 } 350 }) 351 } 352 } 353 354 func TestMinimizeRandom(t *testing.T) { 355 target, rs, iters := initTest(t) 356 iters /= 10 // Long test. 357 ct := target.DefaultChoiceTable() 358 r := rand.New(rs) 359 for i := 0; i < iters; i++ { 360 for _, mode := range []MinimizeMode{MinimizeCorpus, MinimizeCrash} { 361 p := target.Generate(rs, 5, ct) 362 copyP := p.Clone() 363 seen := make(map[string]bool) 364 seen[hash.String(p.Serialize())] = true 365 minP, _ := Minimize(p, len(p.Calls)-1, mode, func(p1 *Prog, callIndex int) bool { 366 id := hash.String(p1.Serialize()) 367 if seen[id] { 368 t.Fatalf("program:\n%s\nobserved the same candidate twice:\n%s", 369 p.Serialize(), p1.Serialize()) 370 } 371 seen[id] = true 372 if r.Intn(2) == 0 { 373 return false 374 } 375 copyP = p1.Clone() 376 return true 377 }) 378 got := string(minP.Serialize()) 379 want := string(copyP.Serialize()) 380 if got != want { 381 t.Fatalf("program:\n%s\ngot:\n%s\nwant:\n%s", p.Serialize(), got, want) 382 } 383 } 384 } 385 } 386 387 func TestMinimizeCallIndex(t *testing.T) { 388 target, rs, iters := initTest(t) 389 ct := target.DefaultChoiceTable() 390 r := rand.New(rs) 391 for i := 0; i < iters; i++ { 392 p := target.Generate(rs, 5, ct) 393 ci := r.Intn(len(p.Calls)) 394 mode := MinimizeCorpus 395 if r.Intn(2) == 0 { 396 mode = MinimizeCrash 397 } 398 p1, ci1 := Minimize(p, ci, mode, func(p1 *Prog, callIndex int) bool { 399 return r.Intn(2) == 0 400 }) 401 if ci1 < 0 || ci1 >= len(p1.Calls) || p.Calls[ci].Meta.Name != p1.Calls[ci1].Meta.Name { 402 t.Fatalf("bad call index after minimization") 403 } 404 } 405 }