lib/lzo: fix ambiguous encoding bug in lzo-rle
authorDave Rodgman <dave.rodgman@arm.com>
Fri, 12 Jun 2020 00:34:54 +0000 (17:34 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Fri, 12 Jun 2020 01:17:47 +0000 (18:17 -0700)
In some rare cases, for input data over 32 KB, lzo-rle could encode two
different inputs to the same compressed representation, so that
decompression is then ambiguous (i.e.  data may be corrupted - although
zram is not affected because it operates over 4 KB pages).

This modifies the compressor without changing the decompressor or the
bitstream format, such that:

 - there is no change to how data produced by the old compressor is
   decompressed

 - an old decompressor will correctly decode data from the updated
   compressor

 - performance and compression ratio are not affected

 - we avoid introducing a new bitstream format

In testing over 12.8M real-world files totalling 903 GB, three files
were affected by this bug.  I also constructed 37M semi-random 64 KB
files totalling 2.27 TB, and saw no affected files.  Finally I tested
over files constructed to contain each of the ~1024 possible bad input
sequences; for all of these cases, updated lzo-rle worked correctly.

There is no significant impact to performance or compression ratio.

Signed-off-by: Dave Rodgman <dave.rodgman@arm.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Dave Rodgman <dave.rodgman@arm.com>
Cc: Willy Tarreau <w@1wt.eu>
Cc: Sergey Senozhatsky <sergey.senozhatsky.work@gmail.com>
Cc: Markus F.X.J. Oberhumer <markus@oberhumer.com>
Cc: Minchan Kim <minchan@kernel.org>
Cc: Nitin Gupta <ngupta@vflare.org>
Cc: Chao Yu <yuchao0@huawei.com>
Cc: <stable@vger.kernel.org>
Link: http://lkml.kernel.org/r/20200507100203.29785-1-dave.rodgman@arm.com
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Documentation/lzo.txt
lib/lzo/lzo1x_compress.c

index ca98332..f65b515 100644 (file)
@@ -159,11 +159,15 @@ Byte sequences
            distance = 16384 + (H << 14) + D
            state = S (copy S literals after this block)
            End of stream is reached if distance == 16384
+           In version 1 only, to prevent ambiguity with the RLE case when
+           ((distance & 0x803f) == 0x803f) && (261 <= length <= 264), the
+           compressor must not emit block copies where distance and length
+           meet these conditions.
 
         In version 1 only, this instruction is also used to encode a run of
-        zeros if distance = 0xbfff, i.e. H = 1 and the D bits are all 1.
+           zeros if distance = 0xbfff, i.e. H = 1 and the D bits are all 1.
            In this case, it is followed by a fourth byte, X.
-           run length = ((X << 3) | (0 0 0 0 0 L L L)) + 4.
+           run length = ((X << 3) | (0 0 0 0 0 L L L)) + 4
 
       0 0 1 L L L L L  (32..63)
            Copy of small block within 16kB distance (preferably less than 34B)
index 717c940..8ad5ba2 100644 (file)
@@ -268,6 +268,19 @@ m_len_done:
                                *op++ = (M4_MARKER | ((m_off >> 11) & 8)
                                                | (m_len - 2));
                        else {
+                               if (unlikely(((m_off & 0x403f) == 0x403f)
+                                               && (m_len >= 261)
+                                               && (m_len <= 264))
+                                               && likely(bitstream_version)) {
+                                       // Under lzo-rle, block copies
+                                       // for 261 <= length <= 264 and
+                                       // (distance & 0x80f3) == 0x80f3
+                                       // can result in ambiguous
+                                       // output. Adjust length
+                                       // to 260 to prevent ambiguity.
+                                       ip -= m_len - 260;
+                                       m_len = 260;
+                               }
                                m_len -= M4_MAX_LEN;
                                *op++ = (M4_MARKER | ((m_off >> 11) & 8));
                                while (unlikely(m_len > 255)) {