{"record":{"id":"920b670ca2837493","repo":"can1357/oh-my-pi","slug":"lzh-uses-sizes-too-large-to-read-safely","errorCode":null,"errorMessage":"LZH uses sizes too large to read safely","messagePattern":"LZH uses sizes too large to read safely","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/lzh.ts","lineNumber":292,"sourceCode":"\nfunction crc16WithZeroRange(bytes: Uint8Array, zeroStart: number, zeroEnd: number): number {\n\tif (zeroStart < 0) return crc16Arc(bytes);\n\tlet value = crc16Arc(bytes.subarray(0, zeroStart));\n\tvalue = crc16Arc(ZERO_CRC16_BYTES.subarray(0, zeroEnd - zeroStart), value);\n\treturn crc16Arc(bytes.subarray(zeroEnd), value);\n}\n\nfunction u16(bytes: Uint8Array, offset: number): number {\n\treturn bytes[offset]! | (bytes[offset + 1]! << 8);\n}\n\nfunction u32(bytes: Uint8Array, offset: number): number {\n\treturn (bytes[offset]! | (bytes[offset + 1]! << 8) | (bytes[offset + 2]! << 16) | (bytes[offset + 3]! << 24)) >>> 0;\n}\n\nfunction u64(bytes: Uint8Array, offset: number): number {\n\tconst value = u32(bytes, offset) + u32(bytes, offset + 4) * 0x100000000;\n\tif (!Number.isSafeInteger(value)) throw new ArchiveError(\"LZH uses sizes too large to read safely\");\n\treturn value;\n}\n\nfunction assertRange(bytes: Uint8Array, start: number, end: number, what: string): void {\n\tif (\n\t\t!Number.isSafeInteger(start) ||\n\t\t!Number.isSafeInteger(end) ||\n\t\tstart < 0 ||\n\t\tend < start ||\n\t\tend > bytes.byteLength\n\t) {\n\t\tthrow new ArchiveError(`Invalid LZH archive: truncated ${what}`);\n\t}\n}\n\nfunction decodeLegacy(bytes: Uint8Array): string {\n\tlet end = bytes.indexOf(0);\n\tif (end < 0) end = bytes.byteLength;","sourceCodeStart":274,"sourceCodeEnd":310,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/lzh.ts#L274-L310","documentation":"u64 combines two 32-bit little-endian words into a JS number; if the result exceeds Number.MAX_SAFE_INTEGER, sizes can no longer be compared or used for allocation safely. The library throws rather than proceeding with an imprecise size.","triggerScenarios":"processExtendedHeader reading a 64-bit size field (common in modern LZH extended headers) whose value exceeds ~9 PB — practically always corrupt/misaligned data rather than a genuine size.","commonSituations":"Extended-header parsing at a wrong offset so garbage is read as the high 32 bits; fuzzed archives; very old archives misinterpreted as 64-bit sizes.","solutions":["Check the extended-header offset/length parsing before the u64 read","Re-validate the archive (CRC) and re-download","Treat as corrupt: catch ArchiveError and skip the entry"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Reject implausible sizes before header processing\nif (packed.length < 12) throw new Error(\"archive too small for extended header\");","typeGuard":null,"tryCatchPattern":"try {\n  const entries = readArchive(packed);\n} catch (err) {\n  if (err instanceof ArchiveError && err.message.includes(\"too large\")) {\n    // header offsets are likely wrong or the file is corrupt\n    return null;\n  }\n  throw err;\n}","preventionTips":["Verify extended-header chain offsets before reading 64-bit size fields","Check archive integrity (CRC) first","Treat absurd sizes as corruption, not valid input"],"tags":["archive","corruption","integer-overflow","lzh"],"backgroundTag":"corrupt-archive-data","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}