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  }