gchq/CyberChef · error · OperationError
Malformed LZNT1 stream: Block too small! Has the stream been
Error message
Malformed LZNT1 stream: Block too small! Has the stream been truncated?
What it means
LZNT1 is the compression format used by NTFS for compressed files. decompress() reads 2-byte block headers, extracts a 12-bit size field, and requires the remaining buffer to hold that many bytes. If compressed.length < coffset + size the block cannot be complete, so the stream is reported as truncated.
Source
Thrown at src/core/lib/LZNT1.mjs:51
* @returns {byteArray}
*/
export function decompress(compressed) {
const decompressed = Array();
let coffset = 0;
while (coffset + 2 <= compressed.length) {
const doffset = decompressed.length;
const blockHeader = Utils.byteArrayToInt(compressed.slice(coffset, coffset + 2), "little");
coffset += 2;
const size = blockHeader & SIZE_MASK;
const blockEnd = coffset + size + 1;
if (size === 0) {
break;
} else if (compressed.length < coffset + size) {
throw new OperationError("Malformed LZNT1 stream: Block too small! Has the stream been truncated?");
}
if ((blockHeader & COMPRESSED_MASK) !== 0) {
while (coffset < blockEnd) {
let header = compressed[coffset++];
for (let i = 0; i < 8 && coffset < blockEnd; i++) {
if ((header & 1) === 0) {
decompressed.push(compressed[coffset++]);
} else {
const pointer = Utils.byteArrayToInt(compressed.slice(coffset, coffset + 2), "little");
coffset += 2;
const displacement = getDisplacement(decompressed.length - doffset - 1);
const symbolOffset = (pointer >> (12 - displacement)) + 1;
const symbolLength = (pointer & (0xFFF >> displacement)) + 2;
const shiftOffset = decompressed.length - symbolOffset;
View on GitHub (pinned to 4290ea7539)
Solutions
- Re-extract the source buffer with extra trailing bytes to confirm whether it is genuinely truncated.
- Verify the input was produced by NTFS LZNT1 compression and not by another algorithm (LZSS, XPRESS).
- Check the offset where the LZNT1 stream starts in the parent structure (e.g. NTFS $DATA attribute) and re-slice.
- If you control the producer, ensure the full compressed attribute is captured including the terminating zero-size block.
Example fix
// before
const out = LZNT1.decompress(truncatedBytes);
// after - verify length against the declared last block before calling
function safeLZNT1(bytes) {
// walk headers to confirm the buffer covers every declared block
let off = 0;
while (off + 2 <= bytes.length) {
const hdr = bytes[off] | (bytes[off+1] << 8);
const size = hdr & 0x0FFF;
off += 2;
if (size === 0) break;
if (bytes.length < off + size) throw new Error('truncated upstream');
off += size + 1;
}
return LZNT1.decompress(bytes);
} Defensive patterns
Strategy: validation
Validate before calling
function isCompleteLZNT1(bytes) {
let off = 0;
while (off + 2 <= bytes.length) {
const hdr = bytes[off] | (bytes[off + 1] << 8);
off += 2;
const size = hdr & 0x0FFF;
if (size === 0) return true; // legitimate end-of-stream
if (bytes.length < off + size) return false;
off += size + 1;
}
return true; // ran out cleanly
}
if (!isCompleteLZNT1(bytes)) throw new Error('truncated upstream');
LZNT1.decompress(bytes); Try / catch
try {
return LZNT1.decompress(bytes);
} catch (e) {
if (e instanceof OperationError && /truncated/i.test(e.message)) {
return { error: 'truncated', partial: null };
}
throw e;
} Prevention
- Re-extract the compressed attribute from a trusted source.
- Verify the starting offset within the parent NTFS structure.
- Confirm the data is LZNT1 and not XPRESS/LZSS.
- Walk the block headers yourself to confirm completeness before decompressing.
When it happens
Trigger: Calling LZNT1.decompress(byteArray) where the byte array ends before the declared size of the current block. Happens with NTFS-compressed data that was cut off, parsed from the wrong offset, or read past an actual end-of-stream marker.
Common situations: Carving compressed NTFS data from a disk image and slicing at the wrong length; networking or storage layer truncating the buffer; misinterpreting a non-LZNT1 stream as LZNT1 (wrong magic/offset); a previous decompression step stopped early.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Malformed LZNT1 stream: Invalid shift!
- Invalid block type while parsing DEFLATE stream at pos ${str
- Invalid ciphertext length: ${cipherText.length} bytes. Must
- Please provide an input.
- Please provide an input.
AI-assisted analysis of gchq/CyberChef@4290ea7539 (2026-08-13).
Data as JSON: /api/errors/9cda78203d12f6b7.
Report an issue: GitHub.