{"record":{"id":"268c86d0cd55072a","repo":"can1357/oh-my-pi","slug":"archive-uses-offsets-or-sizes-too-large-to-read-sa","errorCode":null,"errorMessage":"Archive uses offsets or sizes too large to read safely","messagePattern":"Archive uses offsets or sizes too large to read safely","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/bytes.ts","lineNumber":18,"sourceCode":"import { ArchiveError } from \"./error\";\n\n/** Shared UTF-8 decoder for archive member names and text payloads. */\nexport const UTF8_DECODER = new TextDecoder();\n\nexport function readUInt16LE(bytes: Uint8Array, offset: number): number {\n\treturn bytes[offset]! | (bytes[offset + 1]! << 8);\n}\n\nexport function readUInt32LE(bytes: Uint8Array, offset: number): number {\n\treturn (bytes[offset]! | (bytes[offset + 1]! << 8) | (bytes[offset + 2]! << 16) | (bytes[offset + 3]! << 24)) >>> 0;\n}\n\n/** Read a u64 as a JS number, rejecting values beyond `Number.MAX_SAFE_INTEGER`. */\nexport function readUInt64LE(bytes: Uint8Array, offset: number): number {\n\tconst value = readUInt32LE(bytes, offset) + readUInt32LE(bytes, offset + 4) * 0x100000000;\n\tif (!Number.isSafeInteger(value)) {\n\t\tthrow new ArchiveError(\"Archive uses offsets or sizes too large to read safely\");\n\t}\n\treturn value;\n}\n\nexport function readUInt16BE(bytes: Uint8Array, offset: number): number {\n\treturn (bytes[offset]! << 8) | bytes[offset + 1]!;\n}\n\nexport function readUInt32BE(bytes: Uint8Array, offset: number): number {\n\treturn ((bytes[offset]! << 24) | (bytes[offset + 1]! << 16) | (bytes[offset + 2]! << 8) | bytes[offset + 3]!) >>> 0;\n}\n\n/** Read a big-endian u64 as a JS number, rejecting unsafe values. */\nexport function readUInt64BE(bytes: Uint8Array, offset: number): number {\n\tconst value = readUInt32BE(bytes, offset) * 0x100000000 + readUInt32BE(bytes, offset + 4);\n\tif (!Number.isSafeInteger(value)) {\n\t\tthrow new ArchiveError(\"Archive uses offsets or sizes too large to read safely\");\n\t}","sourceCodeStart":1,"sourceCodeEnd":36,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/bytes.ts#L1-L36","documentation":"readUInt64LE decodes a little-endian 64-bit integer as a JS number. JavaScript numbers can only exactly represent integers up to Number.MAX_SAFE_INTEGER (2^53-1), so any u64 whose value exceeds that is rejected with this ArchiveError rather than silently returning a corrupted value that could cause wrong offsets and out-of-bounds reads.","triggerScenarios":"Reading a ZIP (incl. ZIP64), ASAR, or CAB field via readUInt64LE where the encoded value exceeds 2^53-1 — e.g. a ZIP64 extra field declaring an astronomically large offset/size, or a corrupted/truncated byte stream misaligned so adjacent bytes form a huge value.","commonSituations":"Maliciously crafted or corrupted archives (poisoned ZIP64 fields); reading at a wrong offset due to earlier parsing drift; archives produced by tools writing placeholder 0xFFFFFFFFFFFFFFFF values that the reader does not expect.","solutions":["Verify the archive is well-formed: re-check parse alignment (record signatures, header sizes) before this read — a huge value often means misalignment.","Treat the input as untrusted/corrupt: reject the archive rather than attempting repair.","If you legitimately need >2^53 values, decode with BigInt locally instead of this helper (the library intentionally does not).","Confirm the file is not truncated or bit-rotted; re-download or re-extract the source archive."],"exampleFix":"// before\nconst offset = readUInt64LE(bytes, extraFieldOffset); // throws on 0xFFFFFFFFFFFFFFFF\n// after\nif (bytes.slice(extraFieldOffset, extraFieldOffset + 8).every(b => b === 0xff)) {\n  throw new Error(\"archive declares invalid ZIP64 sentinel size\");\n}\nconst offset = readUInt64LE(bytes, extraFieldOffset);","handlingStrategy":"validation","validationCode":"function safeU64LE(bytes, offset) {\n  const lo = bytes[offset] | bytes[offset+1]<<8 | bytes[offset+2]<<16 | bytes[offset+3]<<24;\n  const hi = bytes[offset+4] | bytes[offset+5]<<8 | bytes[offset+6]<<16 | bytes[offset+7]<<24;\n  if (hi > 0x1fffff) throw new Error(\"u64 field exceeds safe integer range at offset \" + offset);\n  return lo + hi * 0x100000000;\n}","typeGuard":"function isSafeArchiveValue(v) {\n  return typeof v === \"number\" && Number.isSafeInteger(v) && v >= 0;\n}","tryCatchPattern":"let offset;\ntry {\n  offset = readUInt64LE(bytes, pos);\n} catch (err) {\n  if (err instanceof ArchiveError && err.message.includes(\"too large to read safely\")) {\n    throw new Error(`corrupt or malicious archive: unsafe 64-bit field at byte ${pos}`);\n  }\n  throw err;\n}","preventionTips":["Sanity-check every offset/size against the actual file size before use.","Validate record signatures/magic before trusting subsequent field reads.","Reject 0xFFFFFFFF/0xFFFFFFFFFFFFFFFF sentinel patterns when your format version does not expect them.","Bound total parsed structures (entries, extra fields) to protect against crafted archives."],"tags":["archive","integer-overflow","zip64","corrupt-file"],"backgroundTag":"unsafe-integer-archive-offset","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}