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