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)