gchq/CyberChef · error · OperationError

Malformed LZNT1 stream: Invalid shift!

Error message

Malformed LZNT1 stream: Invalid shift!

What it means

Inside an LZNT1 compressed block, flag bits introduce 2-byte back-reference pointers. The decoder computes a source offset into the already-decompressed buffer and copies bytes from there. If the computed shift is below 0 or beyond the current decompressed length, the back-reference points outside valid data and the stream is malformed.

Source

Thrown at src/core/lib/LZNT1.mjs:73

            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;

                        for (let shiftDelta = 0; shiftDelta < symbolLength + 1; shiftDelta++) {
                            const shift = shiftOffset + shiftDelta;
                            if (shift < 0 || decompressed.length <= shift) {
                                throw new OperationError("Malformed LZNT1 stream: Invalid shift!");
                            }
                            decompressed.push(decompressed[shift]);
                        }
                    }
                    header >>= 1;
                }
            }
        } else {
            decompressed.push(...compressed.slice(coffset, coffset + size + 1));
            coffset += size + 1;
        }
    }

    return decompressed;
}

View on GitHub (pinned to 4290ea7539)

Solutions

  1. Re-acquire the compressed bytes from the authoritative source to rule out corruption.
  2. Confirm the algorithm is actually LZNT1 (used by NTFS) and not a sibling LZ format.
  3. Compare the byte sequence against a known-good reference decoder output to localize the bad pointer.
  4. If you only need best-effort recovery, copy bytes up to the failing offset and accept partial output (wrap in try/catch and keep the partial `decompressed` array).

Example fix

// before
const out = LZNT1.decompress(bytes); // throws mid-stream, loses all output

// after - capture partial output for forensic/recovery use
let partial;
try {
  partial = LZNT1.decompress(bytes);
} catch (e) {
  if (/Invalid shift/.test(e.message)) {
    // re-run a tolerant decoder that stops at the bad block
    partial = tolerantLZNT1(bytes);
  } else throw e;
}
Defensive patterns

Strategy: try-catch

Try / catch

let partial;
try {
  partial = LZNT1.decompress(bytes);
} catch (e) {
  if (!(e instanceof OperationError) || !/Invalid shift/.test(e.message)) throw e;
  partial = tolerantLZNT1Until(bytes, failOffset); // custom partial decoder
}
return partial;

Prevention

When it happens

Trigger: Calling LZNT1.decompress on a stream whose compressed back-reference pointer resolves to an offset not yet written (negative shift or past end). Indicates the pointer/displacement bits do not correspond to a legitimate LZ77-style reference.

Common situations: Corrupted compressed attribute (bit-flip in storage); data tampering; feeding a different LZ variant (e.g. XPRESS/LZXPRESS) into the LZNT1 decoder; partial overwrite of the compressed buffer; wrong endianness when slicing the input.

Understand the failure class

Related errors


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