can1357/oh-my-pi · error · ArchiveError
Invalid CPIO archive: name size must include a NUL terminato
Error message
Invalid CPIO archive: name size must include a NUL terminator
What it means
Every CPIO member name field must contain at least the terminating NUL byte, so header nameSize must be >= 1; the library throws ArchiveError otherwise. A zero nameSize is structurally impossible in conforming archives (even the 'TRAILER!!!' entry has nameSize 11) and would make the name/data offset math nonsensical, so it is rejected as corruption or a crafted header.
Source
Thrown at packages/utils/src/ar/cpio.ts:227
if (path.posix.isAbsolute(portableTarget)) return { path: portableTarget, resolveTarget: false };
const normalized = normalizeArchiveLookupPath(path.posix.join(path.posix.dirname(recordPath), portableTarget));
return normalized === undefined
? { path: portableTarget, resolveTarget: false }
: { path: normalized, resolveTarget: true };
}
/** Parse an already-materialized CPIO stream for direct and RPM-composed readers. */
export function readCpioEntriesFromBuffer(bytes: Uint8Array, options: FormatReadOptions): ArchiveIndexEntry[] {
assertInMemorySize(bytes.byteLength, options.limits);
const records: ParsedRecord[] = [];
let offset = 0;
let metadataSize = 0;
let foundTrailer = false;
while (offset < bytes.byteLength) {
const header = parseHeader(bytes, offset);
if (header.mode > 0xffff) throw new ArchiveError("Invalid CPIO archive: mode exceeds 16 bits");
if (header.nameSize < 1) throw new ArchiveError("Invalid CPIO archive: name size must include a NUL terminator");
assertArchivePathBytes(header.nameSize - 1, "member path", options.limits.maxPathBytes);
assertArchiveMemberSize(header.fileSize, "(CPIO entry)", options.limits);
const nameStart = offset + header.headerSize;
const nameEnd = nameStart + header.nameSize;
const dataOffset = align(nameEnd, header.alignment);
const dataEnd = dataOffset + header.fileSize;
const nextOffset = align(dataEnd, header.alignment);
requireRange(bytes, nameStart, nameEnd, "member name");
requireRange(bytes, dataOffset, dataEnd, "member data");
requireRange(bytes, dataEnd, nextOffset, "member padding");
if (bytes[nameEnd - 1] !== 0) throw new ArchiveError("Invalid CPIO archive: member name is not NUL-terminated");
for (let index = nameStart; index < nameEnd - 1; index++) {
if (bytes[index] === 0) throw new ArchiveError("Invalid CPIO archive: member name contains an embedded NUL");
}
validateZeroPadding(bytes, nameEnd, dataOffset, "name");
validateZeroPadding(bytes, dataEnd, nextOffset, "data");
View on GitHub (pinned to 9690622007)
Solutions
- Regenerate the archive with standard cpio tooling; nameSize must include the trailing NUL
- Fix a custom writer to write name bytes plus a NUL and set nameSize = nameBytes.length + 1
- Verify the archive against a checksum to distinguish corruption from a hostile input and reject untrusted sources
- Locate the offending entry by scanning headers for the zero name-size field before ingestion
Example fix
// before: writer omits NUL from size const name = Buffer.from(entryPath); header.nameSize = name.length; // after: include the NUL terminator const name = Buffer.from(entryPath + '\0'); header.nameSize = name.length;
Defensive patterns
Strategy: validation
Validate before calling
// pre-check first header before full parse
const head = Buffer.from(new Uint8Array(await source.slice(0, 110)));
const nameSize = parseInt(head.toString('ascii', 6 + 11 * 8, 6 + 12 * 8), 16);
if (nameSize < 1) throw new Error('header nameSize must be >= 1'); Try / catch
try {
const entries = await readCpio(source, options);
} catch (err) {
if (err instanceof ArchiveError && err.message.includes('name size must include')) {
// fix writer to count the NUL or reject the corrupt archive
} else throw err;
} Prevention
- Custom writers: nameSize = byteLength(name) + 1 (NUL included)
- Round-trip written archives through the reader in CI
- Reject untrusted archives declaring zero name sizes
- Regenerate corrupted archives from source
When it happens
Trigger: Parsing an archive whose header declares nameSize = 0 — from a broken writer, corruption zeroing the field, or a crafted archive (newc hex field '00000000', odc octal '000000').
Common situations: Malicious/fuzzed archives in ingestion pipelines; truncated or bit-rotted archives; buggy custom archive writers that forget to count the NUL terminator in nameSize.
Related errors
- CPIO member '${memberPath}' has an inconsistent declared siz
- Invalid CPIO archive: mode exceeds 16 bits
- Invalid ARJ ${field}: missing terminator
- Invalid ARJ local header
- Invalid ARJ archive: no members
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/f46783ad56a2f847.
Report an issue: GitHub.