github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/report/linux_test.go (about) 1 // Copyright 2015 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 report 5 6 import ( 7 "bytes" 8 "fmt" 9 "os" 10 "path/filepath" 11 "reflect" 12 "runtime" 13 "strings" 14 "testing" 15 16 "github.com/google/syzkaller/pkg/mgrconfig" 17 "github.com/google/syzkaller/pkg/osutil" 18 "github.com/google/syzkaller/pkg/symbolizer" 19 "github.com/google/syzkaller/pkg/vminfo" 20 "github.com/google/syzkaller/sys/targets" 21 "github.com/stretchr/testify/assert" 22 ) 23 24 func TestLinuxIgnores(t *testing.T) { 25 cfg := &mgrconfig.Config{ 26 Derived: mgrconfig.Derived{ 27 TargetOS: targets.Linux, 28 TargetArch: targets.AMD64, 29 }, 30 } 31 reporter, err := NewReporter(cfg) 32 if err != nil { 33 t.Fatal(err) 34 } 35 cfg.Ignores = []string{"BUG: bug3"} 36 reporter1, err := NewReporter(cfg) 37 if err != nil { 38 t.Fatal(err) 39 } 40 cfg.Ignores = []string{"BUG: bug3", "BUG: bug1"} 41 reporter2, err := NewReporter(cfg) 42 if err != nil { 43 t.Fatal(err) 44 } 45 cfg.Ignores = []string{"BUG: bug3", "BUG: bug1", "BUG: bug2"} 46 reporter3, err := NewReporter(cfg) 47 if err != nil { 48 t.Fatal(err) 49 } 50 51 const log = ` 52 [ 0.000000] BUG: bug1 53 [ 0.000000] BUG: bug2 54 ` 55 if !reporter.ContainsCrash([]byte(log)) { 56 t.Fatalf("no crash") 57 } 58 if rep := reporter.Parse([]byte(log)); rep.Title != "BUG: bug1" { 59 t.Fatalf("want `BUG: bug1`, found `%v`", rep.Title) 60 } 61 62 if !reporter1.ContainsCrash([]byte(log)) { 63 t.Fatalf("no crash") 64 } 65 if rep := reporter1.Parse([]byte(log)); rep.Title != "BUG: bug1" { 66 t.Fatalf("want `BUG: bug1`, found `%v`", rep.Title) 67 } 68 69 if !reporter2.ContainsCrash([]byte(log)) { 70 t.Fatalf("no crash") 71 } 72 if rep := reporter2.Parse([]byte(log)); rep.Title != "BUG: bug2" { 73 t.Fatalf("want `BUG: bug2`, found `%v`", rep.Title) 74 } 75 76 if reporter3.ContainsCrash([]byte(log)) { 77 t.Fatalf("found crash, should be ignored") 78 } 79 if rep := reporter3.Parse([]byte(log)); rep != nil { 80 t.Fatalf("found `%v`, should be ignored", rep.Title) 81 } 82 } 83 84 func TestLinuxSymbolizeLine(t *testing.T) { 85 tests := []struct { 86 line string 87 result string 88 }{ 89 // Normal symbolization. 90 { 91 "[ 2713.153531] [<ffffffff82d1b1d9>] foo+0x101/0x185\n", 92 "[ 2713.153531] [<ffffffff82d1b1d9>] foo+0x101/0x185 foo.c:555\n", 93 }, 94 { 95 "RIP: 0010:[<ffffffff8188c0e6>] [<ffffffff8188c0e6>] foo+0x101/0x185\n", 96 "RIP: 0010:[<ffffffff8188c0e6>] [<ffffffff8188c0e6>] foo+0x101/0x185 foo.c:550\n", 97 }, 98 // Strip "./" file prefix. 99 { 100 "[ 2713.153531] [<ffffffff82d1b1d9>] foo+0x111/0x185\n", 101 "[ 2713.153531] [<ffffffff82d1b1d9>] foo+0x111/0x185 foo.h:111\n", 102 }, 103 // Needs symbolization, but symbolizer returns nothing. 104 { 105 "[ 2713.153531] [<ffffffff82d1b1d9>] foo+0x121/0x185\n", 106 "[ 2713.153531] [<ffffffff82d1b1d9>] foo+0x121/0x185\n", 107 }, 108 // Needs symbolization, but symbolizer returns error. 109 { 110 "[ 2713.153531] [<ffffffff82d1b1d9>] foo+0x131/0x185\n", 111 "[ 2713.153531] [<ffffffff82d1b1d9>] foo+0x131/0x185\n", 112 }, 113 // Needs symbolization, but symbol is missing. 114 { 115 "[ 2713.153531] [<ffffffff82d1b1d9>] bar+0x131/0x185\n", 116 "[ 2713.153531] [<ffffffff82d1b1d9>] bar+0x131/0x185\n", 117 }, 118 // Bad offset. 119 { 120 "[ 2713.153531] [<ffffffff82d1b1d9>] bar+0xffffffffffffffffffff/0x185\n", 121 "[ 2713.153531] [<ffffffff82d1b1d9>] bar+0xffffffffffffffffffff/0x185\n", 122 }, 123 // Should not be symbolized. 124 { 125 "WARNING: CPU: 2 PID: 2636 at ipc/shm.c:162 foo+0x101/0x185\n", 126 "WARNING: CPU: 2 PID: 2636 at ipc/shm.c:162 foo+0x101/0x185 foo.c:555\n", 127 }, 128 // Tricky function name. 129 { 130 " [<ffffffff84e5bea0>] do_ipv6_setsockopt.isra.7.part.3+0x101/0x2830 \n", 131 " [<ffffffff84e5bea0>] do_ipv6_setsockopt.isra.7.part.3+0x101/0x2830 net.c:111 \n", 132 }, 133 // Old KASAN frame format (with tab). 134 { 135 "[ 50.419727] baz+0x101/0x200\n", 136 "[ 50.419727] baz+0x101/0x200 baz.c:100\n", 137 }, 138 // Inlined frames. 139 { 140 " [<ffffffff84e5bea0>] foo+0x141/0x185\n", 141 " [<ffffffff84e5bea0>] inlined1 net.c:111 [inline]\n" + 142 " [<ffffffff84e5bea0>] inlined2 mm.c:222 [inline]\n" + 143 " [<ffffffff84e5bea0>] foo+0x141/0x185 kasan.c:333\n", 144 }, 145 // Several symbols with the same name. 146 { 147 "[<ffffffff82d1b1d9>] baz+0x101/0x200\n", 148 "[<ffffffff82d1b1d9>] baz+0x101/0x200 baz.c:100\n", 149 }, 150 // Frame format with module+offset. 151 { 152 "[ 50.419727][ T3822] baz+0x101/0x200 [beep]\n", 153 "[ 50.419727][ T3822] baz+0x101/0x200 baz.c:100 [beep]\n", 154 }, 155 // Frame format with module+offset and stracktrace_build_id. 156 { 157 "[ 50.419727][ T3822] baz+0x101/0x200 [beep b31b29679ab712c360bddd861f655ab24898b4db]\n", 158 "[ 50.419727][ T3822] baz+0x101/0x200 baz.c:100 [beep]\n", 159 }, 160 161 // Frame format with module+offset for invalid module. 162 { 163 "[ 50.419727][ T3822] baz+0x101/0x200 [invalid_module]\n", 164 "[ 50.419727][ T3822] baz+0x101/0x200 [invalid_module]\n", 165 }, 166 // Frame format with module+offset for missing symbol. 167 { 168 "[ 50.419727][ T3822] missing_symbol+0x101/0x200 [beep]\n", 169 "[ 50.419727][ T3822] missing_symbol+0x101/0x200 [beep]\n", 170 }, 171 // Frame format with module+offset for invalid offset. 172 { 173 "[ 50.419727][ T3822] baz+0x300/0x200 [beep]\n", 174 "[ 50.419727][ T3822] baz+0x300/0x200 [beep]\n", 175 }, 176 } 177 symbols := map[string]map[string][]symbolizer.Symbol{ 178 "": { 179 "foo": { 180 {Addr: 0x1000000, Size: 0x190}, 181 }, 182 "do_ipv6_setsockopt.isra.7.part.3": { 183 {Addr: 0x2000000, Size: 0x2830}, 184 }, 185 "baz": { 186 {Addr: 0x3000000, Size: 0x100}, 187 {Addr: 0x4000000, Size: 0x200}, 188 {Addr: 0x5000000, Size: 0x300}, 189 }, 190 }, 191 "beep": { 192 "baz": { 193 {Addr: 0x4000000, Size: 0x200}, 194 }, 195 }, 196 } 197 symb := func(bin string, pc uint64) ([]symbolizer.Frame, error) { 198 if bin == "beep" { 199 switch pc { 200 case 0x4000100: 201 return []symbolizer.Frame{ 202 { 203 File: "/linux/baz.c", 204 Line: 100, 205 }, 206 }, nil 207 default: 208 return nil, fmt.Errorf("unknown pc 0x%x", pc) 209 } 210 } 211 if bin != "vmlinux" { 212 return nil, fmt.Errorf("unknown pc 0x%x", pc) 213 } 214 switch pc { 215 case 0x1000100: 216 return []symbolizer.Frame{ 217 { 218 File: "/linux/foo.c", 219 Line: 555, 220 }, 221 }, nil 222 case 0x1000101: 223 return []symbolizer.Frame{ 224 { 225 File: "/linux/foo.c", 226 Line: 550, 227 }, 228 }, nil 229 case 0x1000110: 230 return []symbolizer.Frame{ 231 { 232 File: "/linux/./foo.h", 233 Line: 111, 234 }, 235 }, nil 236 case 0x1000120: 237 return nil, nil 238 case 0x1000130: 239 return nil, fmt.Errorf("unknown pc 0x%x", pc) 240 case 0x2000100: 241 return []symbolizer.Frame{ 242 { 243 File: "/linux/net.c", 244 Line: 111, 245 }, 246 }, nil 247 case 0x1000140: 248 return []symbolizer.Frame{ 249 { 250 Func: "inlined1", 251 File: "/linux/net.c", 252 Line: 111, 253 Inline: true, 254 }, 255 { 256 Func: "inlined2", 257 File: "/linux/mm.c", 258 Line: 222, 259 Inline: true, 260 }, 261 { 262 Func: "noninlined3", 263 File: "/linux/kasan.c", 264 Line: 333, 265 Inline: false, 266 }, 267 }, nil 268 case 0x4000100: 269 return []symbolizer.Frame{ 270 { 271 File: "/linux/baz.c", 272 Line: 100, 273 }, 274 }, nil 275 default: 276 return nil, fmt.Errorf("unknown pc 0x%x", pc) 277 } 278 } 279 modules := []*vminfo.KernelModule{ 280 { 281 Name: "", 282 Path: "vmlinux", 283 }, 284 { 285 Name: "beep", 286 Path: "beep", 287 }, 288 } 289 290 cfg := &config{ 291 kernelDirs: mgrconfig.KernelDirs{ 292 Obj: "/linux", 293 }, 294 kernelModules: modules, 295 } 296 ctx := &linux{ 297 config: cfg, 298 vmlinux: "vmlinux", 299 symbols: symbols, 300 } 301 for i, test := range tests { 302 t.Run(fmt.Sprint(i), func(t *testing.T) { 303 rep := &Report{ 304 Report: []byte(test.line), 305 } 306 err := ctx.symbolize(rep, symb) 307 assert.NoError(t, err) 308 assert.Equal(t, test.result, string(rep.Report)) 309 }) 310 } 311 } 312 313 func prepareLinuxReporter(t *testing.T, arch string) (*Reporter, *linux) { 314 cfg := &mgrconfig.Config{ 315 Derived: mgrconfig.Derived{ 316 TargetOS: targets.Linux, 317 TargetArch: arch, 318 SysTarget: targets.Get(targets.Linux, arch), 319 }, 320 } 321 reporter, err := NewReporter(cfg) 322 if err != nil { 323 t.Errorf("failed to create a reporter instance for %#v: %v", arch, err) 324 } 325 return reporter, reporter.impl.(*linux) 326 } 327 328 func TestParseLinuxOpcodes(t *testing.T) { 329 type opcodeTest struct { 330 arch string 331 input string 332 output *parsedOpcodes 333 } 334 335 tests := []opcodeTest{ 336 // LE tests. 337 { 338 arch: targets.AMD64, 339 input: "31 c0 <e8> f5 bf f7 ff", 340 output: &parsedOpcodes{ 341 rawBytes: []byte{0x31, 0xc0, 0xe8, 0xf5, 0xbf, 0xf7, 0xff}, 342 offset: 2, 343 }, 344 }, 345 { 346 arch: targets.AMD64, 347 input: "c031 <f5e8> f7bf fff7 00ff", 348 output: &parsedOpcodes{ 349 rawBytes: []byte{0x31, 0xc0, 0xe8, 0xf5, 0xbf, 0xf7, 0xf7, 0xff, 0xff, 0x00}, 350 offset: 2, 351 }, 352 }, 353 { 354 arch: targets.AMD64, 355 input: "(33221100) 77665544", 356 output: &parsedOpcodes{ 357 rawBytes: []byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77}, 358 offset: 0, 359 }, 360 }, 361 // BE tests. 362 { 363 arch: targets.S390x, 364 input: "31 c0 <e8> f5 bf f7 ff", 365 output: &parsedOpcodes{ 366 rawBytes: []byte{0x31, 0xc0, 0xe8, 0xf5, 0xbf, 0xf7, 0xff}, 367 offset: 2, 368 }, 369 }, 370 { 371 arch: targets.S390x, 372 input: "31c0 <e8f5> bff5 f7ff ff00", 373 output: &parsedOpcodes{ 374 rawBytes: []byte{0x31, 0xc0, 0xe8, 0xf5, 0xbf, 0xf5, 0xf7, 0xff, 0xff, 0x00}, 375 offset: 2, 376 }, 377 }, 378 { 379 arch: targets.S390x, 380 input: "<00112233> 44556677", 381 output: &parsedOpcodes{ 382 rawBytes: []byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77}, 383 offset: 0, 384 }, 385 }, 386 // ARM Thumb tests. 387 { 388 arch: targets.ARM, 389 input: "0011 (2233) 4455", 390 output: &parsedOpcodes{ 391 rawBytes: []byte{0x11, 0x00, 0x33, 0x22, 0x55, 0x44}, 392 decompileFlags: FlagForceArmThumbMode, 393 offset: 2, 394 }, 395 }, 396 { 397 arch: targets.ARM, 398 input: "(33221100) 77665544", 399 output: &parsedOpcodes{ 400 rawBytes: []byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77}, 401 offset: 0, 402 }, 403 }, 404 // Bad input tests. 405 { 406 arch: targets.AMD64, 407 input: "00 11 22 33", 408 output: nil, 409 }, 410 { 411 arch: targets.AMD64, 412 input: "aa bb <cc> zz", 413 output: nil, 414 }, 415 { 416 arch: targets.AMD64, 417 input: "<00> 11 22 <33>", 418 output: nil, 419 }, 420 { 421 arch: targets.AMD64, 422 input: "aa <bb>", 423 output: nil, 424 }, 425 { 426 arch: targets.AMD64, 427 input: "001122 334455", 428 output: nil, 429 }, 430 { 431 arch: targets.AMD64, 432 input: "0011223344556677", 433 output: nil, 434 }, 435 } 436 437 for idx, test := range tests { 438 t.Run(fmt.Sprintf("%s/%v", test.arch, idx), func(t *testing.T) { 439 t.Parallel() 440 _, linuxReporter := prepareLinuxReporter(t, test.arch) 441 ret, err := linuxReporter.parseOpcodes(test.input) 442 if test.output == nil && err == nil { 443 t.Errorf("expected an error on input %#v", test) 444 } else if test.output != nil && err != nil { 445 t.Errorf("unexpected error %v on input %#v", err, test.input) 446 } else if test.output != nil && !reflect.DeepEqual(ret, *test.output) { 447 t.Errorf("expected: %#v, got: %#v", test.output, ret) 448 } 449 }) 450 } 451 } 452 453 func TestDisassemblyInReports(t *testing.T) { 454 if runtime.GOOS != targets.Linux { 455 t.Skipf("the test is meant to be run only under Linux") 456 } 457 458 archPath := filepath.Join("testdata", "linux", "decompile") 459 subFolders, err := os.ReadDir(archPath) 460 if err != nil { 461 t.Fatalf("disassembly reports failed: %v", err) 462 } 463 464 for _, obj := range subFolders { 465 if !obj.IsDir() { 466 continue 467 } 468 reporter, linuxReporter := prepareLinuxReporter(t, obj.Name()) 469 if linuxReporter.target.BrokenCompiler != "" { 470 t.Skip("skipping the test due to broken cross-compiler:\n" + linuxReporter.target.BrokenCompiler) 471 } 472 473 testPath := filepath.Join(archPath, obj.Name()) 474 testFiles, err := os.ReadDir(testPath) 475 if err != nil { 476 t.Fatalf("failed to list tests for %v: %v", obj.Name(), err) 477 } 478 479 for _, file := range testFiles { 480 if !strings.HasSuffix(file.Name(), ".in") { 481 continue 482 } 483 filePath := filepath.Join(testPath, strings.TrimSuffix(file.Name(), ".in")) 484 t.Run(obj.Name()+"/"+file.Name(), func(t *testing.T) { 485 testDisassembly(t, reporter, linuxReporter, filePath) 486 }) 487 } 488 } 489 } 490 491 func testDisassembly(t *testing.T, reporter *Reporter, linuxReporter *linux, testFilePrefix string) { 492 t.Parallel() 493 494 input, err := os.ReadFile(testFilePrefix + ".in") 495 if err != nil { 496 t.Fatalf("failed to read input file: %v", err) 497 } 498 499 report := reporter.Parse(input) 500 if report == nil { 501 t.Fatalf("no bug report was found") 502 } 503 504 result := linuxReporter.decompileOpcodes(input, report) 505 if *flagUpdate { 506 osutil.WriteFile(testFilePrefix+".out", result) 507 } 508 509 output, err := os.ReadFile(testFilePrefix + ".out") 510 if err != nil { 511 t.Fatalf("failed to read output file: %v", err) 512 } 513 514 if !bytes.Equal(output, result) { 515 t.Fatalf("expected:\n%s\ngot:\n%s", output, result) 516 } 517 }