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  }