diff --git a/src/main/crash-reporting/minidump-crash-signature.test.ts b/src/main/crash-reporting/minidump-crash-signature.test.ts index 2d1fbb90972..8e878cf3d23 100644 --- a/src/main/crash-reporting/minidump-crash-signature.test.ts +++ b/src/main/crash-reporting/minidump-crash-signature.test.ts @@ -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 }, diff --git a/src/main/crash-reporting/minidump-crash-signature.ts b/src/main/crash-reporting/minidump-crash-signature.ts index 3e246d4b77a..fe4e930504d 100644 --- a/src/main/crash-reporting/minidump-crash-signature.ts +++ b/src/main/crash-reporting/minidump-crash-signature.ts @@ -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[] = [] diff --git a/src/main/crash-reporting/minidump-stream-reader.ts b/src/main/crash-reporting/minidump-stream-reader.ts index 1230a6f9c48..4b9f32a07a3 100644 --- a/src/main/crash-reporting/minidump-stream-reader.ts +++ b/src/main/crash-reporting/minidump-stream-reader.ts @@ -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 = {