github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/pkg/report/fuchsia.go (about)

     1  // Copyright 2017 syzkaller project authors. All rights reserved.
     2  // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
     3  
     4  package report
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"path/filepath"
    10  	"regexp"
    11  	"strconv"
    12  	"strings"
    13  
    14  	"github.com/google/syzkaller/pkg/report/crash"
    15  	"github.com/google/syzkaller/pkg/symbolizer"
    16  	"github.com/ianlancetaylor/demangle"
    17  )
    18  
    19  type fuchsia struct {
    20  	*config
    21  	obj string
    22  }
    23  
    24  var (
    25  	// Ignore these strings when detecting crashes.
    26  	fuchsiaIgnores = []*regexp.Regexp{
    27  		// Don't generate a crash report for a Rust panic, unless it causes a kernel panic.
    28  		regexp.MustCompile(`panic::`),
    29  	}
    30  	rustBacktrace      = regexp.MustCompile(` (stack backtrace:)`)
    31  	starnixLinePrefix  = regexp.MustCompile(`^\[\d+\.\d+\]`)
    32  	zirconRIP          = regexp.MustCompile(` RIP: (0x[0-9a-f]+) `)
    33  	zirconBT           = regexp.MustCompile(`^bt#[0-9]+: (0x[0-9a-f]+)`)
    34  	zirconReportEnd    = []byte("Halted")
    35  	zirconAssertFailed = []byte("ASSERT FAILED at")
    36  	zirconLinePrefix   = regexp.MustCompile(`^\[\d+\.\d+\] \d+\.\d+> `)
    37  	zirconUnrelated    = []*regexp.Regexp{
    38  		regexp.MustCompile(`^$`),
    39  		regexp.MustCompile(`stopping other cpus`),
    40  		regexp.MustCompile(`^halting cpu`),
    41  		regexp.MustCompile(`^dso: `),
    42  		regexp.MustCompile(`^UPTIME: `),
    43  		regexp.MustCompile(`^BUILDID `),
    44  		regexp.MustCompile(`^Halting\.\.\.`),
    45  	}
    46  )
    47  
    48  func ctorFuchsia(cfg *config) (reporterImpl, []string, error) {
    49  	ctx := &fuchsia{
    50  		config: cfg,
    51  	}
    52  	ctx.ignores = append(ctx.ignores, fuchsiaIgnores...)
    53  	if ctx.kernelObj != "" {
    54  		ctx.obj = filepath.Join(ctx.kernelObj, ctx.target.KernelObject)
    55  	}
    56  	suppressions := []string{
    57  		"fatal exception: process /tmp/syz-fuzzer", // OOM presumably
    58  	}
    59  	return ctx, suppressions, nil
    60  }
    61  
    62  func (ctx *fuchsia) ContainsCrash(output []byte) bool {
    63  	return containsCrash(output, fuchsiaOopses, ctx.ignores)
    64  }
    65  
    66  func (ctx *fuchsia) Parse(output []byte) *Report {
    67  	// We symbolize here because zircon output does not contain even function names.
    68  	symbolized := ctx.symbolize(output)
    69  	rep := simpleLineParser(symbolized, fuchsiaOopses, fuchsiaStackParams, ctx.ignores)
    70  	if rep == nil {
    71  		return nil
    72  	}
    73  	rep.Output = output
    74  	if report := ctx.shortenReport(rep.Report); len(report) != 0 {
    75  		rep.Report = report
    76  	}
    77  	if strings.HasPrefix(rep.Title, "starnix kernel panic") {
    78  		if report := ctx.shortenStarnixPanicReport(rep.Report, 5, 20); len(report) != 0 {
    79  			rep.Report = report
    80  		}
    81  	}
    82  	return rep
    83  }
    84  
    85  // Captures lines that match one of `starnixFramePatterns`, plus some surrounding lines that may
    86  // or may not be interesting.
    87  //
    88  // Captures up to `maxUnrelatedLines` of consecutive lines that do not start with the usual starnix
    89  // log prefix `starnixLinePrefix` before suppressing unrelated lines. These lines are often
    90  // syzkaller log lines, but are sometimes continuations of newline-containing logs from starnix.
    91  //
    92  // Captures up to `maxUnmatchedLines` of consecutive starnix log lines that do not match one of
    93  // `starnixFramePatterns` before ending the report. These lines (usually in relatively short groups)
    94  // may separate portions of the stack trace.
    95  func (ctx *fuchsia) shortenStarnixPanicReport(report []byte, maxUnrelatedLines, maxUnmatchedLines int) []byte {
    96  	out := new(bytes.Buffer)
    97  	unrelatedLines := 0
    98  	unmatchedLines := 0
    99  	for _, line := range lines(report) {
   100  		if matchesAny(line, starnixFramePatterns) {
   101  			unrelatedLines = 0
   102  			unmatchedLines = 0
   103  		} else if starnixLinePrefix.FindSubmatch(line) == nil {
   104  			unrelatedLines += 1
   105  			if unrelatedLines > maxUnrelatedLines {
   106  				continue
   107  			}
   108  		} else {
   109  			unmatchedLines += 1
   110  		}
   111  		out.Write(line)
   112  		out.WriteByte('\n')
   113  		if unmatchedLines == maxUnmatchedLines {
   114  			break
   115  		}
   116  	}
   117  	return append(bytes.TrimRight(out.Bytes(), "\n"), '\n')
   118  }
   119  
   120  func (ctx *fuchsia) shortenReport(report []byte) []byte {
   121  	out := new(bytes.Buffer)
   122  	for _, rawLine := range lines(report) {
   123  		line := zirconLinePrefix.ReplaceAll(rawLine, nil)
   124  		if matchesAny(line, zirconUnrelated) {
   125  			continue
   126  		}
   127  		if bytes.Contains(line, zirconReportEnd) {
   128  			break
   129  		}
   130  		out.Write(line)
   131  		out.WriteByte('\n')
   132  	}
   133  	return out.Bytes()
   134  }
   135  
   136  func (ctx *fuchsia) symbolize(output []byte) []byte {
   137  	symb := symbolizer.NewSymbolizer(ctx.config.target)
   138  	defer symb.Close()
   139  	out := new(bytes.Buffer)
   140  
   141  	lines := lines(output)
   142  	for i := 0; i < len(lines); i++ {
   143  		line := lines[i]
   144  		if bytes.Contains(line, zirconAssertFailed) && len(line) == 127 {
   145  			// This is super hacky: but zircon splits the most important information in long assert lines
   146  			// (and they are always long) into several lines in irreversible way. Try to restore full line.
   147  			line = append([]byte{}, line...)
   148  			if i+1 < len(lines) {
   149  				line = append(bytes.Clone(line), lines[i+1]...)
   150  				i++
   151  			}
   152  		}
   153  		if ctx.obj != "" {
   154  			if match := zirconRIP.FindSubmatchIndex(line); match != nil {
   155  				if ctx.processPC(out, symb, line, match, false) {
   156  					continue
   157  				}
   158  			} else if match := zirconBT.FindSubmatchIndex(line); match != nil {
   159  				if ctx.processPC(out, symb, line, match, true) {
   160  					continue
   161  				}
   162  			}
   163  		}
   164  		out.Write(line)
   165  		out.WriteByte('\n')
   166  	}
   167  	return out.Bytes()
   168  }
   169  
   170  func (ctx *fuchsia) processPC(out *bytes.Buffer, symb *symbolizer.Symbolizer,
   171  	line []byte, match []int, call bool) bool {
   172  	prefix := line[match[0]:match[1]]
   173  	pcStart := match[2] - match[0]
   174  	pcEnd := match[3] - match[0]
   175  	pcStr := prefix[pcStart:pcEnd]
   176  	pc, err := strconv.ParseUint(string(pcStr), 0, 64)
   177  	if err != nil {
   178  		return false
   179  	}
   180  	shortPC := pc & 0xfffffff
   181  	pc = 0xffffffff80000000 | shortPC
   182  	if call {
   183  		pc--
   184  	}
   185  	frames, err := symb.Symbolize(ctx.obj, pc)
   186  	if err != nil || len(frames) == 0 {
   187  		return false
   188  	}
   189  	for _, frame := range frames {
   190  		file := ctx.trimFile(frame.File)
   191  		name := demangle.Filter(frame.Func, demangle.NoParams, demangle.NoTemplateParams)
   192  		if strings.Contains(name, "<lambda(") {
   193  			// Demangling produces super long (full) names for lambdas.
   194  			name = "lambda"
   195  		}
   196  		id := "[ inline ]"
   197  		if !frame.Inline {
   198  			id = fmt.Sprintf("0x%08x", shortPC)
   199  		}
   200  		start := replace(append([]byte{}, prefix...), pcStart, pcEnd, []byte(id))
   201  		fmt.Fprintf(out, "%s %v %v:%v\n", start, name, file, frame.Line)
   202  	}
   203  	return true
   204  }
   205  
   206  func (ctx *fuchsia) trimFile(file string) string {
   207  	const (
   208  		prefix1 = "zircon/kernel/"
   209  		prefix2 = "zircon/"
   210  	)
   211  	if pos := strings.LastIndex(file, prefix1); pos != -1 {
   212  		return file[pos+len(prefix1):]
   213  	}
   214  	if pos := strings.LastIndex(file, prefix2); pos != -1 {
   215  		return file[pos+len(prefix2):]
   216  	}
   217  	return file
   218  }
   219  
   220  func (ctx *fuchsia) Symbolize(rep *Report) error {
   221  	// We symbolize in Parse because zircon stacktraces don't contain even function names.
   222  	return nil
   223  }
   224  
   225  var zirconStartRes = []*regexp.Regexp{}
   226  
   227  var zirconFramePatterns = []*regexp.Regexp{
   228  	compile(` RIP: 0x[0-9a-f]{8} +([a-zA-Z0-9_:~]+)`),
   229  	compile(` RIP: \[ inline \] +([a-zA-Z0-9_:~]+)`),
   230  	compile(`^bt#[0-9]+: 0x[0-9a-f]{8} +([a-zA-Z0-9_:~]+)`),
   231  	compile(`^bt#[0-9]+: \[ inline \] +([a-zA-Z0-9_:~]+)`),
   232  }
   233  
   234  var zirconSkipPatterns = []string{
   235  	"^platform_halt$",
   236  	"^exception_die$",
   237  	"^_panic$",
   238  }
   239  
   240  var starnixStartRes = []*regexp.Regexp{
   241  	rustBacktrace,
   242  }
   243  
   244  var starnixFramePatterns = []*regexp.Regexp{
   245  	compile(` \[\[\[ELF module #0x[\da-f]+ "(.*)" (BuildID=[\da-f]{16}) (0x[\da-f]{12})\]\]\]`),
   246  	compile(`#\d+\.?\d*[\s]+(0x[\da-f]{16}) in (.+):([\d]+)[\s]+<(.*)>\+(0x[\da-f]+)`),
   247  	compile(`#\d+\.?\d*[\s]+(0x[\da-f]{16}) in ([^\s]+)[\s]+<(.*)>\+(0x[\da-f]+)`),
   248  }
   249  
   250  var starnixSkipPatterns = []string{}
   251  
   252  var fuchsiaStackParams = &stackParams{
   253  	stackStartRes: append(zirconStartRes, starnixStartRes...),
   254  	frameRes:      append(zirconFramePatterns, starnixFramePatterns...),
   255  	skipPatterns:  append(zirconSkipPatterns, starnixSkipPatterns...),
   256  }
   257  
   258  var zirconOopses = []*oops{
   259  	{
   260  		[]byte("ZIRCON KERNEL PANIC"),
   261  		[]oopsFormat{
   262  			{
   263  				title: compile("ZIRCON KERNEL PANIC(?:.*\\n)+?.*ASSERT FAILED(?:.*\\n)+?.*bt#00:"),
   264  				fmt:   "ASSERT FAILED in %[1]v",
   265  				stack: &stackFmt{
   266  					parts: []*regexp.Regexp{
   267  						parseStackTrace,
   268  					},
   269  				},
   270  			},
   271  			{
   272  				// Some debug asserts don't contain stack trace.
   273  				title:        compile("ZIRCON KERNEL PANIC(?:.*\\n)+?.*ASSERT FAILED at \\(.+?\\): (.*)"),
   274  				fmt:          "ASSERT FAILED: %[1]v",
   275  				noStackTrace: true,
   276  			},
   277  			{
   278  				title: compile("ZIRCON KERNEL PANIC(?:.*\\n)+?.*double fault, halting(?:.*\\n)+?.*bt#00:"),
   279  				fmt:   "double fault in %[1]v",
   280  				stack: &stackFmt{
   281  					parts: []*regexp.Regexp{
   282  						parseStackTrace,
   283  					},
   284  				},
   285  			},
   286  			{
   287  				// Some double faults don't contain stack trace.
   288  				title:        compile("ZIRCON KERNEL PANIC(?:.*\\n)+?.*double fault, halting"),
   289  				fmt:          "double fault",
   290  				noStackTrace: true,
   291  			},
   292  			{
   293  				title: compile("ZIRCON KERNEL PANIC(?:.*\\n)+?.*Supervisor Page Fault exception, halting"),
   294  				fmt:   "Supervisor Fault in %[1]v",
   295  				stack: &stackFmt{
   296  					parts: []*regexp.Regexp{
   297  						parseStackTrace,
   298  					},
   299  				},
   300  			},
   301  			{
   302  				title: compile("ZIRCON KERNEL PANIC(?:.*\\n)+?.*recursion in interrupt handler"),
   303  				fmt:   "recursion in interrupt handler in %[1]v",
   304  				stack: &stackFmt{
   305  					parts: []*regexp.Regexp{
   306  						parseStackTrace,
   307  					},
   308  				},
   309  			},
   310  			{
   311  				title:        compile("ZIRCON KERNEL PANIC(?:.*\\n)+?.*KVM internal error"),
   312  				fmt:          "KVM internal error",
   313  				noStackTrace: true,
   314  			},
   315  			{
   316  				title: compile("ZIRCON KERNEL PANIC"),
   317  				fmt:   "KERNEL PANIC in %[1]v",
   318  				stack: &stackFmt{
   319  					parts: []*regexp.Regexp{
   320  						parseStackTrace,
   321  					},
   322  				},
   323  			},
   324  		},
   325  		[]*regexp.Regexp{},
   326  		crash.UnknownType,
   327  	},
   328  	{
   329  		[]byte("recursion in interrupt handler"),
   330  		[]oopsFormat{
   331  			{
   332  				title: compile("recursion in interrupt handler(?:.*\\n)+?.*(?:bt#00:|RIP:)"),
   333  				fmt:   "recursion in interrupt handler in %[1]v",
   334  				stack: &stackFmt{
   335  					parts: []*regexp.Regexp{
   336  						parseStackTrace,
   337  					},
   338  				},
   339  			},
   340  			{
   341  				title:        compile("recursion in interrupt handler"),
   342  				fmt:          "recursion in interrupt handler",
   343  				noStackTrace: true,
   344  			},
   345  		},
   346  		[]*regexp.Regexp{},
   347  		crash.UnknownType,
   348  	},
   349  	// We should detect just "stopping other cpus" as some kernel crash rather then as "lost connection",
   350  	// but if we add oops for "stopping other cpus", then it will interfere with other formats,
   351  	// because "stopping other cpus" usually goes after "ZIRCON KERNEL PANIC", but sometimes before. Mess.
   352  	// {
   353  	//	[]byte("stopping other cpus"),
   354  	// },
   355  	{
   356  		[]byte("welcome to Zircon"),
   357  		[]oopsFormat{
   358  			{
   359  				title:        compile("welcome to Zircon"),
   360  				fmt:          "unexpected kernel reboot",
   361  				noStackTrace: true,
   362  				reportType:   crash.UnexpectedReboot,
   363  			},
   364  		},
   365  		[]*regexp.Regexp{},
   366  		crash.UnknownType,
   367  	},
   368  	{
   369  		[]byte("KVM internal error"),
   370  		[]oopsFormat{
   371  			{
   372  				title:        compile("KVM internal error"),
   373  				fmt:          "KVM internal error",
   374  				noStackTrace: true,
   375  			},
   376  		},
   377  		[]*regexp.Regexp{},
   378  		crash.UnknownType,
   379  	},
   380  	{
   381  		[]byte("<== fatal exception"),
   382  		[]oopsFormat{
   383  			{
   384  				title:        compile("<== fatal exception"),
   385  				report:       compile("<== fatal exception: process ([a-zA-Z0-9_/-]+)"),
   386  				fmt:          "fatal exception in %[1]v",
   387  				noStackTrace: true,
   388  			},
   389  		},
   390  		[]*regexp.Regexp{
   391  			compile("<== fatal exception: process .+?syz.+?\\["),
   392  		},
   393  		crash.UnknownType,
   394  	},
   395  }
   396  
   397  var starnixOopses = []*oops{
   398  	{
   399  		[]byte("STARNIX KERNEL PANIC"),
   400  		[]oopsFormat{
   401  			{
   402  				title:  compile("STARNIX KERNEL PANIC"),
   403  				report: compile("STARNIX KERNEL PANIC(?:.|\\n)*PANIC info=panicked at [./]*(.*):.*:.*:\\n(.*)\\n"),
   404  				fmt:    "starnix kernel panic: panic in %[1]v: %[2]v",
   405  				stack: &stackFmt{
   406  					parts: []*regexp.Regexp{
   407  						rustBacktrace,
   408  						parseStackTrace,
   409  					},
   410  				},
   411  			},
   412  		},
   413  		[]*regexp.Regexp{},
   414  		crash.UnknownType,
   415  	},
   416  }
   417  
   418  var fuchsiaOopses = append(append(append(zirconOopses, starnixOopses...), commonOopses...), &groupGoRuntimeErrors)