can1357/oh-my-pi · error · ArchiveError

Invalid CAB archive: CFDATA expands to ${uncompressed} bytes

Error message

Invalid CAB archive: CFDATA expands to ${uncompressed} bytes (maximum 32768)

What it means

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.

Source

Thrown at packages/utils/src/ar/cab.ts:152

		}
		if (description.method === 3 && (description.parameter < 15 || description.parameter > 21)) {
			throw new ArchiveError(`Unsupported CAB LZX window size: ${description.parameter} bits (expected 15-21)`);
		}

		const compressedSize = description.dataEnd - description.dataStart;
		assertInMemorySize(compressedSize, this.#limits);
		const bytes = await readExact(this.#source, description.dataStart, description.dataEnd);
		let position = 0;
		let outputSize = 0;
		for (let block = 0; block < description.blockCount; block++) {
			if (position + DATA_BLOCK_SIZE + this.#dataReserveSize > bytes.byteLength) {
				throw new ArchiveError("Invalid CAB archive: truncated CFDATA header");
			}
			const compressed = readUInt16LE(bytes, position + 4);
			const uncompressed = readUInt16LE(bytes, position + 6);
			if (uncompressed === 0) throw new ArchiveError("Unsupported multi-volume CAB archive: split CFDATA block");
			if (uncompressed > MAX_DATA_OUTPUT) {
				throw new ArchiveError(`Invalid CAB archive: CFDATA expands to ${uncompressed} bytes (maximum 32768)`);
			}
			const payloadStart = position + DATA_BLOCK_SIZE + this.#dataReserveSize;
			const payloadEnd = payloadStart + compressed;
			if (payloadEnd > bytes.byteLength) throw new ArchiveError("Invalid CAB archive: truncated CFDATA payload");
			const expectedChecksum = readUInt32LE(bytes, position);
			if (expectedChecksum !== 0) {
				const payloadChecksum = cabChecksum(bytes.subarray(payloadStart, payloadEnd));
				const actualChecksum = cabChecksum(bytes.subarray(position + 4, payloadStart), payloadChecksum);
				if (actualChecksum !== expectedChecksum) {
					throw new ArchiveError(`Invalid CAB archive: CFDATA block ${block} checksum mismatch`);
				}
			}
			outputSize += uncompressed;
			assertInMemorySize(outputSize, this.#limits);
			position = payloadEnd;
		}
		if (outputSize < description.requiredSize) {
			throw new ArchiveError("Invalid CAB archive: folder data is shorter than its file table declares");

View on GitHub (pinned to 9690622007)

Solutions

  1. 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.
  2. Re-download the file and check its checksum; a single-region corruption can zero/FF out cbUncomp.
  3. Hex-dump the failing CFDATA header (cbChecksum, cbCompressed at +4, cbUncomp at +6) to see whether 0xFFFF is isolated corruption.
  4. If you produce these CABs, fix the packer so no CFDATA block's uncompressed size exceeds 32768 bytes.
  5. Keep this check as-is in your deployment: it is a deliberate zip-bomb-style guard and should not be bypassed.

Example fix

// before: reading a corrupt CAB
await readCabArchive(buf); // CFDATA expands to 65535 bytes
// after: verify integrity and re-obtain
if ((await Bun.hash(await Bun.file('fresh.cab').arrayBuffer())) !== expectedHash)
  throw new Error('CAB failed checksum verification');
await readCabArchive(await Bun.file('fresh.cab').bytes());
Defensive patterns

Strategy: try-catch

Validate before calling

// cbUncomp is a 16-bit LE field; anything above 32768 means corruption (only 0xFFFF can exceed)
const cbUncomp = view.getUint16(cfdataOffset + 6, true);
if (cbUncomp > 32768)
  throw new Error(`CFDATA cbUncomp=${cbUncomp} exceeds the 32768 spec maximum — archive is corrupt`);

Type guard

function isLegalCfdataSize(uncomp: number): boolean {
  return uncomp >= 0 && uncomp <= 32768;
}

Try / catch

try {
  return await readCabArchive(bytes);
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes('CFDATA expands to')) {
    throw new Error('Corrupt CFDATA block size (likely 0xFFFF) — re-download or re-pack the CAB');
  }
  throw err;
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/94633c27d57fed40. Report an issue: GitHub.