Documentation
¶
Overview ¶
Package lzo decodes the LZO1X compressed bytestream and the lzop (.lzo) container, in pure Go, with no cgo and no third-party dependency.
Provenance and licence ¶
This is an independent implementation written from a published prose description of the LZO1X bytestream — principally "LZO stream format as understood by Linux's LZO decompressor" by Willy Tarreau and Dave Rodgman, distributed as Documentation/staging/lzo.rst in the Linux source tree and published at https://docs.kernel.org/staging/lzo.html — cross-checked end-to-end against streams produced by the lzop(1) command-line tool, treated purely as a black box.
No liblzo2 code, and no port or translation of liblzo2 code, was read, consulted or adapted. liblzo2 is GPL-2.0; this package is BSD-3-Clause, and the two must not be mixed. The lzop container layout was likewise derived from a field-by-field description plus empirical inspection of real .lzo files, not from lzop's sources.
Decoder only ¶
There is deliberately no compressor here. Writing .lzo serves nobody: every consumer of the format already reads gzip, zstd or xz, all of which compress better, and a second encoder would double the surface area that has to be proved byte-exact while adding no capability a reader does not already give. Decoding is what lets Go programs open the .lzo files that already exist — Hadoop splits, embedded firmware images, kernel initramfs payloads, old archives — and that is the whole scope.
The two layers ¶
Decompress decodes one LZO1X block. LZO1X blocks are not self-delimiting in their output length, so the caller must supply it; containers such as lzop store it alongside each block.
NewReader decodes a whole lzop stream: it parses and checksum-verifies the file header, then walks the block list, decompressing each block and verifying its checksums.
Methods 1 (LZO1X_1), 2 (LZO1X_1_15) and 3 (LZO1X_999) all decode with the same LZO1X decoder. They look like three formats and they are one: only the compressor differs — how hard it searches for matches — and every one of them emits the same instruction grammar. A decoder that special-cased them would be three copies of one thing.
Untrusted input ¶
An LZO1X length can grow 255-fold (MaxExpansion) and the lengths a caller passes to Decompress, like the block lengths NewReader reads, come from headers an attacker can write. Both are checked against what the input could possibly produce, and against DefaultMaxOutput, before any buffer is sized from them; NewReader also grows its payload buffer only as bytes arrive.
What is not supported ¶
LZO-RLE (bitstream version 1, the zram zero-run extension) is rejected rather than guessed at: no witness for it is available to verify against here, and a decoder that silently mis-decodes is worse than one that says no. lzop's delta filters and its extra-field header block are rejected for the same reason. Each returns a distinguishable error.
Index ¶
Constants ¶
const DefaultMaxOutput = 1 << 30
DefaultMaxOutput is the largest uncompressedLen Decompress will allocate for: 1 GiB. Real containers are far below it (lzop blocks are at most 64 MiB, the kernel filesystems use 4 KiB to 1 MiB).
const MaxExpansion = 255
MaxExpansion is how many output bytes one input byte can produce at most. A length is extended 255 at a time by one extra zero byte each, so the ratio "cannot exceed around 255:1" (Documentation/staging/lzo.rst in the Linux tree); every instruction's output is strictly below 255 times its input.
Variables ¶
var ( // ErrMagic reports a stream that does not begin with the lzop magic. ErrMagic = errors.New("lzo: not an lzop stream") // ErrHeader reports a malformed or unreadable lzop header or block header. ErrHeader = errors.New("lzo: malformed lzop stream") // ErrChecksum reports a header or block checksum that does not match. ErrChecksum = errors.New("lzo: lzop checksum mismatch") // ErrUnsupported reports a well-formed lzop stream this package declines to // decode: an unknown compression method, a delta filter, an extra-field // header block, or a stream that demands a newer lzop than 1.04. ErrUnsupported = errors.New("lzo: unsupported lzop stream") )
Container-level errors. Each is distinguishable with errors.Is so a caller can tell "this is not an lzop file" from "this is one, and I cannot read it".
var ErrBitstreamVersion = errors.New("lzo: unsupported LZO1X bitstream version")
ErrBitstreamVersion reports an LZO1X block carrying a bitstream version marker for a version this package does not decode. Version 1 is LZO-RLE, the zero-run extension used by zram; see the package documentation.
var ErrCorrupt = errors.New("lzo: corrupt LZO1X block")
ErrCorrupt reports an LZO1X block that cannot be decoded: a truncated instruction or operand, a match reaching back before the start of the block, an output overrun, or a missing end-of-stream marker.
var ErrTooLarge = errors.New("lzo: output exceeds the size limit")
ErrTooLarge reports a block that asks for more output than DefaultMaxOutput. It is returned before the output is allocated.
Functions ¶
func Decompress ¶
Decompress decodes one LZO1X block and returns its uncompressedLen bytes.
LZO1X does not record its output length, so uncompressedLen must be supplied by the caller from wherever the container kept it. It is exact, not a hint: a block that ends after a different number of bytes, or that has bytes left over after its end-of-stream marker, is reported as ErrCorrupt rather than truncated or padded to fit.
The same decoder reads blocks from every LZO1X compressor variant — LZO1X_1, LZO1X_1_15 and LZO1X_999 (lzop methods 1, 2 and 3) — because they share one bytestream grammar.
uncompressedLen usually comes from a container header an attacker wrote, so it is checked before the output is allocated: a length above DefaultMaxOutput is ErrTooLarge, and a length that len(src) input bytes cannot possibly produce (see MaxExpansion) is ErrCorrupt.
func NewReader ¶
NewReader returns an io.Reader yielding the uncompressed contents of the lzop (.lzo) stream in r.
The file header is read and verified before NewReader returns, so a stream that is not lzop, or is lzop this package declines to decode, is reported here rather than on the first Read. Block and header checksums are verified whenever the stream carries them; a mismatch is reported as ErrChecksum.
lzop stores its own integers big-endian, and its blocks each carry both lengths, so a block whose compressed length equals its uncompressed length is stored verbatim rather than compressed — which is what lzop does with incompressible input. Such blocks are passed through, not handed to the LZO1X decoder.
Types ¶
This section is empty.
