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

  1. Re-extract the source buffer with extra trailing bytes to confirm whether it is genuinely truncated.
  2. Verify the input was produced by NTFS LZNT1 compression and not by another algorithm (LZSS, XPRESS).
  3. Check the offset where the LZNT1 stream starts in the parent structure (e.g. NTFS $DATA attribute) and re-slice.
  4. 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

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

Related errors


AI-assisted analysis of gchq/CyberChef@4290ea7539 (2026-08-13). Data as JSON: /api/errors/9cda78203d12f6b7. Report an issue: GitHub.