{"record":{"id":"94633c27d57fed40","repo":"can1357/oh-my-pi","slug":"invalid-cab-archive-cfdata-expands-to-uncompres","errorCode":null,"errorMessage":"Invalid CAB archive: CFDATA expands to ${uncompressed} bytes (maximum 32768)","messagePattern":"Invalid CAB archive: CFDATA expands to (.+?) bytes \\(maximum 32768\\)","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/cab.ts","lineNumber":152,"sourceCode":"\t\t}\n\t\tif (description.method === 3 && (description.parameter < 15 || description.parameter > 21)) {\n\t\t\tthrow new ArchiveError(`Unsupported CAB LZX window size: ${description.parameter} bits (expected 15-21)`);\n\t\t}\n\n\t\tconst compressedSize = description.dataEnd - description.dataStart;\n\t\tassertInMemorySize(compressedSize, this.#limits);\n\t\tconst bytes = await readExact(this.#source, description.dataStart, description.dataEnd);\n\t\tlet position = 0;\n\t\tlet outputSize = 0;\n\t\tfor (let block = 0; block < description.blockCount; block++) {\n\t\t\tif (position + DATA_BLOCK_SIZE + this.#dataReserveSize > bytes.byteLength) {\n\t\t\t\tthrow new ArchiveError(\"Invalid CAB archive: truncated CFDATA header\");\n\t\t\t}\n\t\t\tconst compressed = readUInt16LE(bytes, position + 4);\n\t\t\tconst uncompressed = readUInt16LE(bytes, position + 6);\n\t\t\tif (uncompressed === 0) throw new ArchiveError(\"Unsupported multi-volume CAB archive: split CFDATA block\");\n\t\t\tif (uncompressed > MAX_DATA_OUTPUT) {\n\t\t\t\tthrow new ArchiveError(`Invalid CAB archive: CFDATA expands to ${uncompressed} bytes (maximum 32768)`);\n\t\t\t}\n\t\t\tconst payloadStart = position + DATA_BLOCK_SIZE + this.#dataReserveSize;\n\t\t\tconst payloadEnd = payloadStart + compressed;\n\t\t\tif (payloadEnd > bytes.byteLength) throw new ArchiveError(\"Invalid CAB archive: truncated CFDATA payload\");\n\t\t\tconst expectedChecksum = readUInt32LE(bytes, position);\n\t\t\tif (expectedChecksum !== 0) {\n\t\t\t\tconst payloadChecksum = cabChecksum(bytes.subarray(payloadStart, payloadEnd));\n\t\t\t\tconst actualChecksum = cabChecksum(bytes.subarray(position + 4, payloadStart), payloadChecksum);\n\t\t\t\tif (actualChecksum !== expectedChecksum) {\n\t\t\t\t\tthrow new ArchiveError(`Invalid CAB archive: CFDATA block ${block} checksum mismatch`);\n\t\t\t\t}\n\t\t\t}\n\t\t\toutputSize += uncompressed;\n\t\t\tassertInMemorySize(outputSize, this.#limits);\n\t\t\tposition = payloadEnd;\n\t\t}\n\t\tif (outputSize < description.requiredSize) {\n\t\t\tthrow new ArchiveError(\"Invalid CAB archive: folder data is shorter than its file table declares\");","sourceCodeStart":134,"sourceCodeEnd":170,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/cab.ts#L134-L170","documentation":"Each CFDATA block must expand to at most 32768 bytes (the CAB spec's cbUncomp maximum). If the block's stored uncompressed size exceeds MAX_DATA_OUTPUT, the header is lying or corrupt, so the reader aborts with this error before allocating or decoding — this also bounds memory use against malicious files.","triggerScenarios":"readAll() encountering a CFDATA block whose cbUncomp field (read at position+6, little-endian uint16) is > 32768. Since the field is 16-bit, the only possible offending value is 0xFFFF (65535).","commonSituations":"Corruption where 0xFFFF sentinel/garbage replaced cbUncomp; crafted archives attempting to blow up allocation; buggy writers that stored sizes > 32 KB per block by violating the spec.","solutions":["Verify the archive independently (`cabextract -t`); if it extracts fine externally, the block is out-of-spec — re-create the CAB with a standard tool.","Re-download the file and check its checksum; a single-region corruption can zero/FF out cbUncomp.","Hex-dump the failing CFDATA header (cbChecksum, cbCompressed at +4, cbUncomp at +6) to see whether 0xFFFF is isolated corruption.","If you produce these CABs, fix the packer so no CFDATA block's uncompressed size exceeds 32768 bytes.","Keep this check as-is in your deployment: it is a deliberate zip-bomb-style guard and should not be bypassed."],"exampleFix":"// before: reading a corrupt CAB\nawait readCabArchive(buf); // CFDATA expands to 65535 bytes\n// after: verify integrity and re-obtain\nif ((await Bun.hash(await Bun.file('fresh.cab').arrayBuffer())) !== expectedHash)\n  throw new Error('CAB failed checksum verification');\nawait readCabArchive(await Bun.file('fresh.cab').bytes());","handlingStrategy":"try-catch","validationCode":"// cbUncomp is a 16-bit LE field; anything above 32768 means corruption (only 0xFFFF can exceed)\nconst cbUncomp = view.getUint16(cfdataOffset + 6, true);\nif (cbUncomp > 32768)\n  throw new Error(`CFDATA cbUncomp=${cbUncomp} exceeds the 32768 spec maximum — archive is corrupt`);","typeGuard":"function isLegalCfdataSize(uncomp: number): boolean {\n  return uncomp >= 0 && uncomp <= 32768;\n}","tryCatchPattern":"try {\n  return await readCabArchive(bytes);\n} catch (err) {\n  if (err instanceof ArchiveError && err.message.includes('CFDATA expands to')) {\n    throw new Error('Corrupt CFDATA block size (likely 0xFFFF) — re-download or re-pack the CAB');\n  }\n  throw err;\n}","preventionTips":["Verify checksums after transfer; single-region corruption triggers this error.","Never bypass the 32768 block cap — it is a memory/zip-bomb guard.","Use spec-compliant CAB writers so no block exceeds 32 KB uncompressed.","Treat this error as corruption first, hostile input second — both warrant rejection."],"tags":["archive","cab","validation","memory-safety"],"backgroundTag":"archive-block-size-exceeded","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}