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)