fix(crash-reporting): stop discarding the whole module list on POSIX dumps (#16687)

This commit is contained in:
Neil
2026-08-26 22:31:39 -07:00
committed by GitHub
parent 0f522c35e5
commit 3d8b9eeeb0
3 changed files with 53 additions and 3 deletions
@@ -194,6 +194,22 @@ function buildDump(options: {
return { dump: builder.build(streams) }
}
/** Offset of a stream's directory entry: `{type, size, rva}`. */
function streamEntry(dump: Buffer, type: number): number {
const directoryRva = dump.readUInt32LE(12)
for (let index = 0; index < dump.readUInt32LE(8); index += 1) {
const entry = directoryRva + index * 12
if (dump.readUInt32LE(entry) === type) {
return entry
}
}
throw new Error(`no stream of type ${type}`)
}
function moduleListRva(dump: Buffer): number {
return dump.readUInt32LE(streamEntry(dump, STREAM_TYPE_MODULE_LIST) + 8)
}
const FATAL_LINE =
'[8104:1234:0815/143022.123456:FATAL:render_frame_impl.cc(4821)] Check failed: !is_detached_.'
@@ -340,6 +356,37 @@ describe('parseMinidumpCrashSignature', () => {
expect(signature?.faultingModuleOffset).toBe('0x1234')
})
it('resolves a faulting module past index 1024 on a real macOS image count', () => {
// A measured macOS renderer carries 1042 loaded images; a cap below that
// dropped the whole module list, so no macOS report could name a module.
const modules = Array.from({ length: 1042 }, (_, index) => ({
base: 0x1_0000_0000n + BigInt(index) * 0x1_0000n,
size: 0x1000,
name: `/Applications/Orca.app/Contents/Frameworks/lib${index}.dylib`
}))
const { dump } = buildDump({
exception: { code: 11, address: 0x1_0000_0000n + 1030n * 0x1_0000n + 0x24n },
modules
})
const signature = parseMinidumpCrashSignature(dump)
expect(signature?.faultingModule).toBe('lib1030.dylib')
expect(signature?.faultingModuleOffset).toBe('0x24')
})
it('still drops the module list when the claimed module count is absurd', () => {
const { dump } = buildDump({
exception: { code: 11, address: 0x7ff7_0000_0010n },
modules: [{ base: 0x7ff7_0000_0000n, size: 0x1000, name: '/opt/orca/orca' }]
})
const corrupt = Buffer.from(dump)
corrupt.writeUInt32LE(0xffff_ffff, moduleListRva(corrupt))
expect(() => parseMinidumpCrashSignature(corrupt)).not.toThrow()
expect(parseMinidumpCrashSignature(corrupt)?.faultingModule).toBeUndefined()
})
it('omits the faulting module when no image range covers the address', () => {
const { dump } = buildDump({
exception: { code: 11, address: 0x10n },
@@ -11,13 +11,16 @@
// rather than throwing: a truncated dump must degrade, not break crash
// reporting.
import { findStream, isMinidump, MAX_MODULES, MinidumpView } from './minidump-stream-reader'
import { findStream, isMinidump, MinidumpView } from './minidump-stream-reader'
import { readCrashpadAnnotations } from './minidump-crashpad-annotations'
const STREAM_TYPE_MODULE_LIST = 4
const STREAM_TYPE_EXCEPTION = 6
const MODULE_RECORD_SIZE = 108
// 8x headroom over a measured 1042-image macOS renderer, whose whole list a
// 1_024 cap dropped; a dump claiming more than this is corrupt.
const MAX_MODULE_LIST_MODULES = 8_192
const MODULE_BASE_OFFSET = 0
const MODULE_SIZE_OFFSET = 8
const MODULE_NAME_RVA_OFFSET = 20
@@ -70,7 +73,7 @@ function readModules(view: MinidumpView): ModuleRecord[] {
return []
}
const count = view.u32(stream.rva)
if (count === null || count > MAX_MODULES) {
if (count === null || count > MAX_MODULE_LIST_MODULES) {
return []
}
const modules: ModuleRecord[] = []
@@ -13,7 +13,7 @@ const DIRECTORY_ENTRY_SIZE = 12
// A dump claiming an absurd stream count is corrupt; cap before iterating.
const MAX_STREAMS = 4_096
export const MAX_ANNOTATION_VALUE_BYTES = 8_192
// Shared cap: both the MINIDUMP_MODULE_LIST and Crashpad's per-module info list.
// Cap for Crashpad's per-module info list; the MINIDUMP_MODULE_LIST is capped separately.
export const MAX_MODULES = 1_024
export type LocationDescriptor = {