can1357/oh-my-pi · error · ArchiveError

Invalid ARJ archive: too many extended headers

Error message

Invalid ARJ archive: too many extended headers

What it means

ARJ headers may be followed by a chain of extended headers, each a 16-bit size (0 terminates the chain). This reader caps the chain at 65,535 entries; exceeding that means the loop is not finding its terminator and would run unboundedly over the buffer, so it throws to bound work on malformed input.

Solutions

  1. Treat the file as malicious or severely corrupt: do not attempt repair; obtain the archive from a trusted source.
  2. Confirm with an external test (`arj t`) whether the archive is otherwise valid; if not, discard it.
  3. If you produce ARJ files, always terminate the extended-header chain with a 0x0000 size word before the next block.
  4. Keep the library's limits (options.limits) in place — do not raise them to 'make the file parse'; this error indicates malformed structure, not a too-small budget.
Defensive patterns

Strategy: try-catch

Try / catch

try {
  return readArj(data, options);
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes("too many extended headers")) {
    // treat as malformed/hostile input: reject outright
    throw new Error("ARJ rejected: extended-header chain does not terminate");
  }
  throw err;
}

Prevention

When it happens

Trigger: parseArjBlock() walks 65,536+ non-zero extension sizes without hitting a 0 u16 — a corrupt or malicious archive whose extended-header chain never terminates, or garbage after the basic header interpreted as extension sizes.

Common situations: Fuzzed/attacker-crafted archives (this cap is a DoS guard), a corrupted file where the terminating 0x0000 was overwritten, or bytes after the basic header belonging to a different structure because the header size itself was wrong.

Related errors


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

Appendix: source

Thrown at packages/utils/src/ar/arj.ts:83

	}
	const bodySize = u16(bytes, offset + 2);
	if (bodySize === 0)
		return { bodyStart: offset + 4, bodySize: 0, nextOffset: offset + 4, metadataSize: 4, isEnd: true };
	if (bodySize < 30 || bodySize > ARJ_MAX_BASIC_HEADER) throw new ArchiveError("Invalid ARJ basic header size");
	const bodyStart = offset + 4;
	const bodyEnd = bodyStart + bodySize;
	assertRange(bytes, bodyStart, bodyEnd + 4, "basic header");
	if (crc32(bytes.subarray(bodyStart, bodyEnd)) !== u32(bytes, bodyEnd)) {
		throw new ArchiveError("Invalid ARJ basic header CRC32");
	}
	let cursor = bodyEnd + 4;
	let extensionCount = 0;
	for (;;) {
		assertRange(bytes, cursor, cursor + 2, "extended header size");
		const extensionSize = u16(bytes, cursor);
		cursor += 2;
		if (extensionSize === 0) break;
		if (++extensionCount > 65_535) throw new ArchiveError("Invalid ARJ archive: too many extended headers");
		assertRange(bytes, cursor, cursor + extensionSize + 4, "extended header");
		if (crc32(bytes.subarray(cursor, cursor + extensionSize)) !== u32(bytes, cursor + extensionSize)) {
			throw new ArchiveError("Invalid ARJ extended header CRC32");
		}
		cursor += extensionSize + 4;
		assertIndexSize(cursor - offset, options.limits, "header metadata");
	}
	return { bodyStart, bodySize, nextOffset: cursor, metadataSize: cursor - offset, isEnd: false };
}

class ArjBitReader {
	readonly #bytes: Uint8Array;
	#position = 0;

	constructor(bytes: Uint8Array) {
		this.#bytes = bytes;
	}

View on GitHub (pinned to 9690622007)