can1357/oh-my-pi · error · ArchiveError

Invalid XZ BCJ filter 0x${filter.id.toString(16)} properties

Error message

Invalid XZ BCJ filter 0x${filter.id.toString(16)} properties

What it means

Thrown while applying XZ block filters: BCJ branch/call/jump filters (x86, PowerPC, IA-64, ARM, SPARC, ARM64, RISC-V) must have either empty properties or exactly 4 bytes holding the little-endian start offset. The library throws when a BCJ filter's properties have any other byte length, indicating a malformed block header.

Source

Thrown at packages/utils/src/ar/codecs/xz.ts:376

			}
			const address = (read32BE(bytes, index + 4) - startOffset - index) >>> 0;
			instruction2 = ((instruction >>> 12) | (address << 20)) >>> 0;
			instruction = (0x17 | (register << 7) | ((address + 0x800) & 0xfffff000)) >>> 0;
		}
		write32LE(bytes, index, instruction);
		write32LE(bytes, index + 4, instruction2);
		index += 6;
	}
}

function applyFilter(bytes: Uint8Array, filter: XzFilter): void {
	if (filter.id === 3) {
		if (filter.properties.byteLength !== 1) throw new ArchiveError("Invalid XZ Delta filter properties");
		deltaDecode(bytes, filter.properties[0]! + 1);
		return;
	}
	if (filter.properties.byteLength !== 0 && filter.properties.byteLength !== 4)
		throw new ArchiveError(`Invalid XZ BCJ filter 0x${filter.id.toString(16)} properties`);
	const startOffset = filter.properties.byteLength === 4 ? read32LE(filter.properties, 0) : 0;
	const alignment = filter.id === 6 ? 16 : filter.id === 8 || filter.id === 11 ? 2 : filter.id === 4 ? 1 : 4;
	if ((startOffset & (alignment - 1)) !== 0)
		throw new ArchiveError(`Invalid XZ BCJ filter 0x${filter.id.toString(16)} start offset`);
	switch (filter.id) {
		case 4:
			x86Decode(bytes, startOffset);
			break;
		case 5:
			powerPcDecode(bytes, startOffset);
			break;
		case 6:
			ia64Decode(bytes, startOffset);
			break;
		case 7:
			armDecode(bytes, startOffset);
			break;
		case 8:

View on GitHub (pinned to 9690622007)

Solutions

  1. Validate the archive externally (`xz -t`) and replace the corrupt file with a known-good copy
  2. Re-download or re-extract; check whether the transfer (FTP ascii mode, partial download) mangled the bytes
  3. Re-compress the data with the standard xz tool if the file came from a custom pipeline
  4. Wrap decode in error handling that reports 'corrupt archive' to the user rather than retrying the same bytes

Example fix

// before: truncated BCJ properties (2 bytes)
const filter = { id: 4, properties: new Uint8Array([0, 0]) };
// after: BCJ properties are empty or a 4-byte LE start offset
const filter = { id: 4, properties: new Uint8Array(4) };
Defensive patterns

Strategy: validation

Validate before calling

const t = await $`xz -t archive.xz`.quiet().nothrow();
if (t.exitCode !== 0) throw new Error('archive.xz fails XZ integrity validation');

Type guard

function isBcjFilter(f: { id: number; properties: Uint8Array }): boolean {
  return f.id === 3 || f.properties.byteLength === 0 || f.properties.byteLength === 4;
}

Try / catch

try {
  await decodeXz(bytes);
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes('Invalid XZ BCJ filter')) {
    throw new Error(`Archive uses malformed BCJ filter properties: ${err.message}`);
  }
  throw err;
}

Prevention

When it happens

Trigger: Decompressing an XZ stream whose block header declares a BCJ filter (id 4,5,6,8,9,10,11) with a properties blob of length 1, 2, 3, or >4 bytes.

Common situations: Corrupt or maliciously crafted archives; files produced by broken encoders; bit-flip corruption in the filter properties region of the block header.

Related errors


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