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