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