github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/pkg/report/report.go (about) 1 // Copyright 2016 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 contains functions that process kernel output, 5 // detect/extract crash messages, symbolize them, etc. 6 package report 7 8 import ( 9 "bytes" 10 "fmt" 11 "regexp" 12 "strings" 13 14 "github.com/google/syzkaller/pkg/mgrconfig" 15 "github.com/google/syzkaller/pkg/report/crash" 16 "github.com/google/syzkaller/pkg/vcs" 17 "github.com/google/syzkaller/sys/targets" 18 ) 19 20 type reporterImpl interface { 21 // ContainsCrash searches kernel console output for oops messages. 22 ContainsCrash(output []byte) bool 23 24 // Parse extracts information about oops from console output. 25 // Returns nil if no oops found. 26 Parse(output []byte) *Report 27 28 // Symbolize symbolizes rep.Report and fills in Maintainers. 29 Symbolize(rep *Report) error 30 } 31 32 type Reporter struct { 33 typ string 34 impl reporterImpl 35 suppressions []*regexp.Regexp 36 interests []*regexp.Regexp 37 } 38 39 type Report struct { 40 // Title contains a representative description of the first oops. 41 Title string 42 // Alternative titles, used for better deduplication. 43 // If two crashes have a non-empty intersection of Title/AltTitles, they are considered the same bug. 44 AltTitles []string 45 // Bug type (e.g. hang, memory leak, etc). 46 Type crash.Type 47 // The indicative function name. 48 Frame string 49 // Report contains whole oops text. 50 Report []byte 51 // Output contains whole raw console output as passed to Reporter.Parse. 52 Output []byte 53 // StartPos/EndPos denote region of output with oops message(s). 54 StartPos int 55 EndPos int 56 // SkipPos is position in output where parsing for the next report should start. 57 SkipPos int 58 // Suppressed indicates whether the report should not be reported to user. 59 Suppressed bool 60 // Corrupted indicates whether the report is truncated of corrupted in some other way. 61 Corrupted bool 62 // CorruptedReason contains reason why the report is marked as corrupted. 63 CorruptedReason string 64 // Recipients is a list of RecipientInfo with Email, Display Name, and type. 65 Recipients vcs.Recipients 66 // GuiltyFile is the source file that we think is to blame for the crash (filled in by Symbolize). 67 GuiltyFile string 68 // reportPrefixLen is length of additional prefix lines that we added before actual crash report. 69 reportPrefixLen int 70 // symbolized is set if the report is symbolized. 71 symbolized bool 72 } 73 74 func (r Report) String() string { 75 return fmt.Sprintf("crash: %v\n%s", r.Title, r.Report) 76 } 77 78 // unspecifiedType can be used to cancel oops.reportType from oopsFormat.reportType. 79 const unspecifiedType = crash.Type("UNSPECIFIED") 80 81 // NewReporter creates reporter for the specified OS/Type. 82 func NewReporter(cfg *mgrconfig.Config) (*Reporter, error) { 83 typ := cfg.TargetOS 84 if cfg.Type == "gvisor" || cfg.Type == "starnix" { 85 typ = cfg.Type 86 } 87 ctor := ctors[typ] 88 if ctor == nil { 89 return nil, fmt.Errorf("unknown OS: %v", typ) 90 } 91 ignores, err := compileRegexps(cfg.Ignores) 92 if err != nil { 93 return nil, err 94 } 95 interests, err := compileRegexps(cfg.Interests) 96 if err != nil { 97 return nil, err 98 } 99 config := &config{ 100 target: cfg.SysTarget, 101 vmType: cfg.Type, 102 kernelSrc: cfg.KernelSrc, 103 kernelBuildSrc: cfg.KernelBuildSrc, 104 kernelObj: cfg.KernelObj, 105 ignores: ignores, 106 } 107 rep, suppressions, err := ctor(config) 108 if err != nil { 109 return nil, err 110 } 111 suppressions = append(suppressions, []string{ 112 // Go runtime OOM messages: 113 "fatal error: runtime: out of memory", 114 "fatal error: runtime: cannot allocate memory", 115 "fatal error: out of memory", 116 "fatal error: newosproc", 117 // Panic with ENOMEM err: 118 "panic: .*cannot allocate memory", 119 }...) 120 suppressions = append(suppressions, cfg.Suppressions...) 121 supps, err := compileRegexps(suppressions) 122 if err != nil { 123 return nil, err 124 } 125 reporter := &Reporter{ 126 typ: typ, 127 impl: rep, 128 suppressions: supps, 129 interests: interests, 130 } 131 return reporter, nil 132 } 133 134 const ( 135 corruptedNoFrames = "extracted no frames" 136 ) 137 138 var ctors = map[string]fn{ 139 targets.Linux: ctorLinux, 140 "starnix": ctorFuchsia, 141 "gvisor": ctorGvisor, 142 targets.FreeBSD: ctorFreebsd, 143 targets.Darwin: ctorDarwin, 144 targets.NetBSD: ctorNetbsd, 145 targets.OpenBSD: ctorOpenbsd, 146 targets.Fuchsia: ctorFuchsia, 147 targets.Windows: ctorStub, 148 } 149 150 type config struct { 151 target *targets.Target 152 vmType string 153 kernelSrc string 154 kernelBuildSrc string 155 kernelObj string 156 ignores []*regexp.Regexp 157 } 158 159 type fn func(cfg *config) (reporterImpl, []string, error) 160 161 func compileRegexps(list []string) ([]*regexp.Regexp, error) { 162 compiled := make([]*regexp.Regexp, len(list)) 163 for i, str := range list { 164 re, err := regexp.Compile(str) 165 if err != nil { 166 return nil, fmt.Errorf("failed to compile %q: %w", str, err) 167 } 168 compiled[i] = re 169 } 170 return compiled, nil 171 } 172 173 func (reporter *Reporter) Parse(output []byte) *Report { 174 return reporter.ParseFrom(output, 0) 175 } 176 177 func (reporter *Reporter) ParseFrom(output []byte, minReportPos int) *Report { 178 rep := reporter.impl.Parse(output[minReportPos:]) 179 if rep == nil { 180 return nil 181 } 182 rep.Output = output 183 rep.StartPos += minReportPos 184 rep.EndPos += minReportPos 185 rep.Title = sanitizeTitle(replaceTable(dynamicTitleReplacement, rep.Title)) 186 for i, title := range rep.AltTitles { 187 rep.AltTitles[i] = sanitizeTitle(replaceTable(dynamicTitleReplacement, title)) 188 } 189 rep.Suppressed = matchesAny(rep.Output, reporter.suppressions) 190 if bytes.Contains(rep.Output, gceConsoleHangup) { 191 rep.Corrupted = true 192 } 193 if match := reportFrameRe.FindStringSubmatch(rep.Title); match != nil { 194 rep.Frame = match[1] 195 } 196 rep.SkipPos = len(output) 197 if pos := bytes.IndexByte(rep.Output[rep.StartPos:], '\n'); pos != -1 { 198 rep.SkipPos = rep.StartPos + pos 199 } 200 if rep.EndPos < rep.SkipPos { 201 // This generally should not happen. 202 // But openbsd does some hacks with /r/n which may lead to off-by-one EndPos. 203 rep.EndPos = rep.SkipPos 204 } 205 return rep 206 } 207 208 func (reporter *Reporter) ContainsCrash(output []byte) bool { 209 return reporter.impl.ContainsCrash(output) 210 } 211 212 func (reporter *Reporter) Symbolize(rep *Report) error { 213 if rep.symbolized { 214 panic("Symbolize is called twice") 215 } 216 rep.symbolized = true 217 if err := reporter.impl.Symbolize(rep); err != nil { 218 return err 219 } 220 if !reporter.isInteresting(rep) { 221 rep.Suppressed = true 222 } 223 return nil 224 } 225 226 func setReportType(rep *Report, oops *oops, format oopsFormat) { 227 if format.reportType == unspecifiedType { 228 rep.Type = crash.UnknownType 229 } else if format.reportType != crash.UnknownType { 230 rep.Type = format.reportType 231 } else if oops.reportType != crash.UnknownType { 232 rep.Type = oops.reportType 233 } 234 } 235 236 func (reporter *Reporter) isInteresting(rep *Report) bool { 237 if len(reporter.interests) == 0 { 238 return true 239 } 240 if matchesAnyString(rep.Title, reporter.interests) || 241 matchesAnyString(rep.GuiltyFile, reporter.interests) { 242 return true 243 } 244 for _, title := range rep.AltTitles { 245 if matchesAnyString(title, reporter.interests) { 246 return true 247 } 248 } 249 for _, recipient := range rep.Recipients { 250 if matchesAnyString(recipient.Address.Address, reporter.interests) { 251 return true 252 } 253 } 254 return false 255 } 256 257 // There are cases when we need to extract a guilty file, but have no ability to do it the 258 // proper way -- parse and symbolize the raw console output log. One of such cases is 259 // the syz-fillreports tool, which only has access to the already symbolized logs. 260 // ReportToGuiltyFile does its best to extract the data. 261 func (reporter *Reporter) ReportToGuiltyFile(title string, report []byte) string { 262 ii, ok := reporter.impl.(interface { 263 extractGuiltyFileRaw(title string, report []byte) string 264 }) 265 if !ok { 266 return "" 267 } 268 return ii.extractGuiltyFileRaw(title, report) 269 } 270 271 func IsSuppressed(reporter *Reporter, output []byte) bool { 272 return matchesAny(output, reporter.suppressions) || 273 bytes.Contains(output, gceConsoleHangup) 274 } 275 276 // ParseAll returns all successive reports in output. 277 func ParseAll(reporter *Reporter, output []byte) (reports []*Report) { 278 skipPos := 0 279 for { 280 rep := reporter.ParseFrom(output, skipPos) 281 if rep == nil { 282 return 283 } 284 reports = append(reports, rep) 285 skipPos = rep.SkipPos 286 } 287 } 288 289 // GCE console connection sometimes fails with this message. 290 // The message frequently happens right after a kernel panic. 291 // So if we see it in output where we recognized a crash, we mark the report as corrupted 292 // because the crash message is usually truncated (maybe we don't even have the title line). 293 // If we see it in no output/lost connection reports then we mark them as suppressed instead 294 // because the crash itself may have been caused by the console connection error. 295 var gceConsoleHangup = []byte("serialport: VM disconnected.") 296 297 type replacement struct { 298 match *regexp.Regexp 299 replacement string 300 } 301 302 func replaceTable(replacements []replacement, str string) string { 303 for _, repl := range replacements { 304 for stop := false; !stop; { 305 newStr := repl.match.ReplaceAllString(str, repl.replacement) 306 stop = newStr == str 307 str = newStr 308 } 309 } 310 return str 311 } 312 313 var dynamicTitleReplacement = []replacement{ 314 { 315 // Executor PIDs are not interesting. 316 regexp.MustCompile(`syz-executor\.?[0-9]+((/|:)[0-9]+)?`), 317 "syz-executor", 318 }, 319 { 320 // Executor process IDs are dynamic and are not interesting. 321 regexp.MustCompile(`syzkaller[0-9]+((/|:)[0-9]+)?`), 322 "syzkaller", 323 }, 324 { 325 // Replace that everything looks like an address with "ADDR", 326 // addresses in descriptions can't be good regardless of the oops regexps. 327 regexp.MustCompile(`([^a-zA-Z0-9])(?:0x)?[0-9a-f]{6,}`), 328 "${1}ADDR", 329 }, 330 { 331 // Replace IP addresses. 332 regexp.MustCompile(`([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})`), 333 "IP", 334 }, 335 { 336 // Replace that everything looks like a file line number with "LINE". 337 regexp.MustCompile(`(\.\w+)(:[0-9]+)+`), 338 "${1}:LINE", 339 }, 340 { 341 // Replace all raw references to runctions (e.g. "ip6_fragment+0x1052/0x2d80") 342 // with just function name ("ip6_fragment"). Offsets and sizes are not stable. 343 regexp.MustCompile(`([a-zA-Z][a-zA-Z0-9_.]+)\+0x[0-9a-z]+/0x[0-9a-z]+`), 344 "${1}", 345 }, 346 { 347 // CPU numbers are not interesting. 348 regexp.MustCompile(`CPU#[0-9]+`), 349 "CPU", 350 }, 351 { 352 // Replace with "NUM" everything that looks like a decimal number and has not 353 // been replaced yet. It might require multiple replacement executions as the 354 // matching substrings may overlap (e.g. "0,1,2"). 355 regexp.MustCompile(`(\W)(\d+)(\W|$)`), 356 "${1}NUM${3}", 357 }, 358 { 359 // Some decimal numbers can be a part of a function name, 360 // we need to preserve them (e.g. cfg80211* or nl802154*). 361 // However, if the number is too long, it's probably something else. 362 regexp.MustCompile(`(\d+){7,}`), 363 "NUM", 364 }, 365 } 366 367 func sanitizeTitle(title string) string { 368 const maxTitleLen = 120 // Corrupted/intermixed lines can be very long. 369 res := make([]byte, 0, len(title)) 370 prev := byte(' ') 371 for i := 0; i < len(title) && i < maxTitleLen; i++ { 372 ch := title[i] 373 switch { 374 case ch == '\t': 375 ch = ' ' 376 case ch < 0x20 || ch >= 0x7f: 377 continue 378 } 379 if ch == ' ' && prev == ' ' { 380 continue 381 } 382 res = append(res, ch) 383 prev = ch 384 } 385 return strings.TrimSpace(string(res)) 386 } 387 388 type oops struct { 389 header []byte 390 formats []oopsFormat 391 suppressions []*regexp.Regexp 392 // This reportType will be used if oopsFormat's reportType is empty. 393 reportType crash.Type 394 } 395 396 type oopsFormat struct { 397 title *regexp.Regexp 398 // If title is matched but report is not, the report is considered corrupted. 399 report *regexp.Regexp 400 // Format string to create report title. 401 // Strings captured by title (or by report if present) are passed as input. 402 // If stack is not nil, extracted function name is passed as an additional last argument. 403 fmt string 404 // Alternative titles used for better crash deduplication. 405 // Format is the same as for fmt. 406 alt []string 407 // If not nil, a function name is extracted from the report and passed to fmt. 408 // If not nil but frame extraction fails, the report is considered corrupted. 409 stack *stackFmt 410 // Disable stack report corruption checking as it would expect one of stackStartRes to be 411 // present, but this format does not comply with that. 412 noStackTrace bool 413 corrupted bool 414 // If not empty, report will have this type. 415 reportType crash.Type 416 } 417 418 type stackFmt struct { 419 // parts describe how guilty stack frame must be extracted from the report. 420 // parts are matched consecutively potentially capturing frames. 421 // parts can be of 3 types: 422 // - non-capturing regexp, matched against report and advances current position 423 // - capturing regexp, same as above, but also yields a frame 424 // - special value parseStackTrace means that a stack trace must be parsed 425 // starting from current position 426 parts []*regexp.Regexp 427 // If parts2 is present it is tried when parts matching fails. 428 parts2 []*regexp.Regexp 429 // Skip these functions in stack traces (matched as substring). 430 skip []string 431 // Custom frame extractor (optional). 432 // Accepts set of all frames, returns guilty frame and corruption reason. 433 extractor frameExtractor 434 } 435 436 type frameExtractor func(frames []string) string 437 438 var parseStackTrace *regexp.Regexp 439 440 func compile(re string) *regexp.Regexp { 441 re = strings.Replace(re, "{{ADDR}}", "0x[0-9a-f]+", -1) 442 re = strings.Replace(re, "{{PC}}", "\\[\\<?(?:0x)?[0-9a-f]+\\>?\\]", -1) 443 re = strings.Replace(re, "{{FUNC}}", "([a-zA-Z0-9_]+)(?:\\.|\\+)", -1) 444 re = strings.Replace(re, "{{SRC}}", "([a-zA-Z0-9-_/.]+\\.[a-z]+:[0-9]+)", -1) 445 return regexp.MustCompile(re) 446 } 447 448 func containsCrash(output []byte, oopses []*oops, ignores []*regexp.Regexp) bool { 449 for pos := 0; pos < len(output); { 450 next := bytes.IndexByte(output[pos:], '\n') 451 if next != -1 { 452 next += pos 453 } else { 454 next = len(output) 455 } 456 for _, oops := range oopses { 457 if matchOops(output[pos:next], oops, ignores) { 458 return true 459 } 460 } 461 pos = next + 1 462 } 463 return false 464 } 465 466 func matchOops(line []byte, oops *oops, ignores []*regexp.Regexp) bool { 467 match := bytes.Index(line, oops.header) 468 if match == -1 { 469 return false 470 } 471 if matchesAny(line, oops.suppressions) { 472 return false 473 } 474 if matchesAny(line, ignores) { 475 return false 476 } 477 return true 478 } 479 480 func extractDescription(output []byte, oops *oops, params *stackParams) ( 481 desc, corrupted string, altTitles []string, format oopsFormat) { 482 startPos := len(output) 483 matchedTitle := false 484 for _, f := range oops.formats { 485 match := f.title.FindSubmatchIndex(output) 486 if match == nil || match[0] > startPos { 487 continue 488 } 489 if match[0] == startPos && desc != "" { 490 continue 491 } 492 if match[0] < startPos { 493 desc = "" 494 format = oopsFormat{} 495 startPos = match[0] 496 } 497 matchedTitle = true 498 if f.report != nil { 499 match = f.report.FindSubmatchIndex(output) 500 if match == nil { 501 continue 502 } 503 } 504 var args []interface{} 505 for i := 2; i < len(match); i += 2 { 506 args = append(args, string(output[match[i]:match[i+1]])) 507 } 508 corrupted = "" 509 if f.stack != nil { 510 frames, ok := extractStackFrame(params, f.stack, output[match[0]:]) 511 if !ok { 512 corrupted = corruptedNoFrames 513 } 514 for _, frame := range frames { 515 args = append(args, frame) 516 } 517 } 518 desc = fmt.Sprintf(f.fmt, args...) 519 for _, alt := range f.alt { 520 altTitles = append(altTitles, fmt.Sprintf(alt, args...)) 521 } 522 format = f 523 } 524 if desc == "" { 525 // If we are here and matchedTitle is set, it means that we've matched 526 // a title of an oops but not full report regexp or stack trace, 527 // which means the report was corrupted. 528 if matchedTitle { 529 corrupted = "matched title but not report regexp" 530 } 531 pos := bytes.Index(output, oops.header) 532 if pos == -1 { 533 return 534 } 535 end := bytes.IndexByte(output[pos:], '\n') 536 if end == -1 { 537 end = len(output) 538 } else { 539 end += pos 540 } 541 desc = string(output[pos:end]) 542 } 543 if corrupted == "" && format.corrupted { 544 corrupted = "report format is marked as corrupted" 545 } 546 return 547 } 548 549 type stackParams struct { 550 // stackStartRes matches start of stack traces. 551 stackStartRes []*regexp.Regexp 552 // frameRes match different formats of lines containing kernel frames (capture function name). 553 frameRes []*regexp.Regexp 554 // skipPatterns match functions that must be unconditionally skipped. 555 skipPatterns []string 556 // If we looked at any lines that match corruptedLines during report analysis, 557 // then the report is marked as corrupted. 558 corruptedLines []*regexp.Regexp 559 // Prefixes that need to be removed from frames. 560 // E.g. syscall prefixes as different arches have different prefixes. 561 stripFramePrefixes []string 562 } 563 564 func extractStackFrame(params *stackParams, stack *stackFmt, output []byte) ([]string, bool) { 565 skip := append([]string{}, params.skipPatterns...) 566 skip = append(skip, stack.skip...) 567 var skipRe *regexp.Regexp 568 if len(skip) != 0 { 569 skipRe = regexp.MustCompile(strings.Join(skip, "|")) 570 } 571 extractor := func(frames []string) string { 572 if len(frames) == 0 { 573 return "" 574 } 575 if stack.extractor == nil { 576 return frames[0] 577 } 578 return stack.extractor(frames) 579 } 580 frames, ok := extractStackFrameImpl(params, output, skipRe, stack.parts, extractor) 581 if ok || len(stack.parts2) == 0 { 582 return frames, ok 583 } 584 return extractStackFrameImpl(params, output, skipRe, stack.parts2, extractor) 585 } 586 587 func lines(text []byte) [][]byte { 588 return bytes.Split(text, []byte("\n")) 589 } 590 591 func extractStackFrameImpl(params *stackParams, output []byte, skipRe *regexp.Regexp, 592 parts []*regexp.Regexp, extractor frameExtractor) ([]string, bool) { 593 lines := lines(output) 594 var frames, results []string 595 ok := true 596 numStackTraces := 0 597 nextPart: 598 for partIdx := 0; ; partIdx++ { 599 if partIdx == len(parts) || parts[partIdx] == parseStackTrace && numStackTraces > 0 { 600 keyFrame := extractor(frames) 601 if keyFrame == "" { 602 keyFrame, ok = "corrupted", false 603 } 604 results = append(results, keyFrame) 605 frames = nil 606 } 607 if partIdx == len(parts) { 608 break 609 } 610 part := parts[partIdx] 611 if part == parseStackTrace { 612 numStackTraces++ 613 var ln []byte 614 for len(lines) > 0 { 615 ln, lines = lines[0], lines[1:] 616 if matchesAny(ln, params.corruptedLines) { 617 ok = false 618 continue nextPart 619 } 620 if matchesAny(ln, params.stackStartRes) { 621 continue nextPart 622 } 623 624 if partIdx != len(parts)-1 { 625 match := parts[partIdx+1].FindSubmatch(ln) 626 if match != nil { 627 frames = appendStackFrame(frames, match, params, skipRe) 628 partIdx++ 629 continue nextPart 630 } 631 } 632 var match [][]byte 633 for _, re := range params.frameRes { 634 match = re.FindSubmatch(ln) 635 if match != nil { 636 break 637 } 638 } 639 frames = appendStackFrame(frames, match, params, skipRe) 640 } 641 } else { 642 var ln []byte 643 for len(lines) > 0 { 644 ln, lines = lines[0], lines[1:] 645 if matchesAny(ln, params.corruptedLines) { 646 ok = false 647 continue nextPart 648 } 649 match := part.FindSubmatch(ln) 650 if match == nil { 651 continue 652 } 653 frames = appendStackFrame(frames, match, params, skipRe) 654 break 655 } 656 } 657 } 658 return results, ok 659 } 660 661 func appendStackFrame(frames []string, match [][]byte, params *stackParams, skipRe *regexp.Regexp) []string { 662 if len(match) < 2 { 663 return frames 664 } 665 for _, frame := range match[1:] { 666 if frame != nil && (skipRe == nil || !skipRe.Match(frame)) { 667 frameName := string(frame) 668 for _, prefix := range params.stripFramePrefixes { 669 frameName = strings.TrimPrefix(frameName, prefix) 670 } 671 frames = append(frames, frameName) 672 } 673 } 674 return frames 675 } 676 677 func simpleLineParser(output []byte, oopses []*oops, params *stackParams, ignores []*regexp.Regexp) *Report { 678 rep := &Report{ 679 Output: output, 680 } 681 var oops *oops 682 for pos := 0; pos < len(output); { 683 next := bytes.IndexByte(output[pos:], '\n') 684 if next != -1 { 685 next += pos 686 } else { 687 next = len(output) 688 } 689 line := output[pos:next] 690 for _, oops1 := range oopses { 691 if matchOops(line, oops1, ignores) { 692 oops = oops1 693 rep.StartPos = pos 694 rep.EndPos = next 695 break 696 } 697 } 698 if oops != nil { 699 break 700 } 701 pos = next + 1 702 } 703 if oops == nil { 704 return nil 705 } 706 title, corrupted, altTitles, format := extractDescription(output[rep.StartPos:], oops, params) 707 rep.Title = title 708 rep.AltTitles = altTitles 709 rep.Report = output[rep.StartPos:] 710 rep.Corrupted = corrupted != "" 711 rep.CorruptedReason = corrupted 712 setReportType(rep, oops, format) 713 714 return rep 715 } 716 717 func matchesAny(line []byte, res []*regexp.Regexp) bool { 718 for _, re := range res { 719 if re.Match(line) { 720 return true 721 } 722 } 723 return false 724 } 725 726 func matchesAnyString(str string, res []*regexp.Regexp) bool { 727 for _, re := range res { 728 if re.MatchString(str) { 729 return true 730 } 731 } 732 return false 733 } 734 735 // replace replaces [start:end] in where with what, inplace. 736 func replace(where []byte, start, end int, what []byte) []byte { 737 if len(what) >= end-start { 738 where = append(where, what[end-start:]...) 739 copy(where[start+len(what):], where[end:]) 740 copy(where[start:], what) 741 } else { 742 copy(where[start+len(what):], where[end:]) 743 where = where[:len(where)-(end-start-len(what))] 744 copy(where[start:], what) 745 } 746 return where 747 } 748 749 // Truncate leaves up to `begin` bytes at the beginning of log and 750 // up to `end` bytes at the end of the log. 751 func Truncate(log []byte, begin, end int) []byte { 752 if begin+end >= len(log) { 753 return log 754 } 755 var b bytes.Buffer 756 b.Write(log[:begin]) 757 if begin > 0 { 758 b.WriteString("\n\n") 759 } 760 fmt.Fprintf(&b, "<<cut %d bytes out>>", 761 len(log)-begin-end, 762 ) 763 if end > 0 { 764 b.WriteString("\n\n") 765 } 766 b.Write(log[len(log)-end:]) 767 return b.Bytes() 768 } 769 770 var ( 771 filenameRe = regexp.MustCompile(`([a-zA-Z0-9_\-\./]*[a-zA-Z0-9_\-]+\.(c|h)):[0-9]+`) 772 reportFrameRe = regexp.MustCompile(`.* in ([a-zA-Z0-9_]+)`) 773 // Matches a slash followed by at least one directory nesting before .c/.h file. 774 deeperPathRe = regexp.MustCompile(`^/[a-zA-Z0-9_\-\./]+/[a-zA-Z0-9_\-]+\.(c|h)$`) 775 ) 776 777 // These are produced by syzkaller itself. 778 // But also catches crashes in Go programs in gvisor/fuchsia. 779 var commonOopses = []*oops{ 780 { 781 // Errors produced by executor's fail function. 782 []byte("SYZFAIL:"), 783 []oopsFormat{ 784 { 785 title: compile("SYZFAIL:(.*)"), 786 fmt: "SYZFAIL:%[1]v", 787 noStackTrace: true, 788 }, 789 }, 790 []*regexp.Regexp{}, 791 crash.SyzFailure, 792 }, 793 { 794 // Errors produced by log.Fatal functions. 795 []byte("SYZFATAL:"), 796 []oopsFormat{ 797 { 798 title: compile("SYZFATAL:(.*)()"), 799 alt: []string{"SYZFATAL%[2]s"}, 800 fmt: "SYZFATAL:%[1]v", 801 noStackTrace: true, 802 }, 803 }, 804 []*regexp.Regexp{}, 805 crash.SyzFailure, 806 }, 807 { 808 []byte("panic:"), 809 []oopsFormat{ 810 { 811 // This is gvisor-specific, but we need to handle it here since we handle "panic:" here. 812 title: compile("panic: Sentry detected .* stuck task"), 813 fmt: "panic: Sentry detected stuck tasks", 814 noStackTrace: true, 815 }, 816 { 817 title: compile("panic:(.*)"), 818 fmt: "panic:%[1]v", 819 noStackTrace: true, 820 }, 821 }, 822 []*regexp.Regexp{ 823 // This can match some kernel functions (skb_panic, skb_over_panic). 824 compile("_panic:"), 825 // Android prints this sometimes during boot. 826 compile("xlog_status:"), 827 compile(`ddb\.onpanic:`), 828 compile(`evtlog_status:`), 829 }, 830 crash.UnknownType, 831 }, 832 } 833 834 var groupGoRuntimeErrors = oops{ 835 []byte("fatal error:"), 836 []oopsFormat{ 837 { 838 title: compile("fatal error:"), 839 fmt: "go runtime error", 840 noStackTrace: true, 841 }, 842 }, 843 []*regexp.Regexp{ 844 compile("ALSA"), 845 }, 846 crash.UnknownType, 847 }