]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
crypto: lzo - Fix compression buffer overrun
authorHerbert Xu <herbert@gondor.apana.org.au>
Thu, 27 Feb 2025 09:04:46 +0000 (17:04 +0800)
committerHerbert Xu <herbert@gondor.apana.org.au>
Sat, 8 Mar 2025 08:23:22 +0000 (16:23 +0800)
Unlike the decompression code, the compression code in LZO never
checked for output overruns.  It instead assumes that the caller
always provides enough buffer space, disregarding the buffer length
provided by the caller.

Add a safe compression interface that checks for the end of buffer
before each write.  Use the safe interface in crypto/lzo.

Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
crypto/lzo-rle.c
crypto/lzo.c
include/linux/lzo.h
lib/lzo/Makefile
lib/lzo/lzo1x_compress.c
lib/lzo/lzo1x_compress_safe.c [new file with mode: 0644]

index 0631d975bfac1129a557c79cb1323827fed098de..0abc2d87f04200c6b0088f65bbb966cc748ace72 100644 (file)
@@ -55,7 +55,7 @@ static int __lzorle_compress(const u8 *src, unsigned int slen,
        size_t tmp_len = *dlen; /* size_t(ulong) <-> uint on 64 bit */
        int err;
 
-       err = lzorle1x_1_compress(src, slen, dst, &tmp_len, ctx);
+       err = lzorle1x_1_compress_safe(src, slen, dst, &tmp_len, ctx);
 
        if (err != LZO_E_OK)
                return -EINVAL;
index ebda132dd22bf5434d232a84d4c6a7cb40f02464..8338851c7406a38414f053048714f2346bd88b12 100644 (file)
@@ -55,7 +55,7 @@ static int __lzo_compress(const u8 *src, unsigned int slen,
        size_t tmp_len = *dlen; /* size_t(ulong) <-> uint on 64 bit */
        int err;
 
-       err = lzo1x_1_compress(src, slen, dst, &tmp_len, ctx);
+       err = lzo1x_1_compress_safe(src, slen, dst, &tmp_len, ctx);
 
        if (err != LZO_E_OK)
                return -EINVAL;
index e95c7d1092b28616bfd4aea617d0ff34434d7e2b..4d30e3624acd237290dbfeaefdb9c710e4f85f56 100644 (file)
 int lzo1x_1_compress(const unsigned char *src, size_t src_len,
                     unsigned char *dst, size_t *dst_len, void *wrkmem);
 
+/* Same as above but does not write more than dst_len to dst. */
+int lzo1x_1_compress_safe(const unsigned char *src, size_t src_len,
+                         unsigned char *dst, size_t *dst_len, void *wrkmem);
+
 /* This requires 'wrkmem' of size LZO1X_1_MEM_COMPRESS */
 int lzorle1x_1_compress(const unsigned char *src, size_t src_len,
                     unsigned char *dst, size_t *dst_len, void *wrkmem);
 
+/* Same as above but does not write more than dst_len to dst. */
+int lzorle1x_1_compress_safe(const unsigned char *src, size_t src_len,
+                            unsigned char *dst, size_t *dst_len, void *wrkmem);
+
 /* safe decompression with overrun testing */
 int lzo1x_decompress_safe(const unsigned char *src, size_t src_len,
                          unsigned char *dst, size_t *dst_len);
index 2f58fafbbdddc09ce803d92c504b580661e53bc6..fc7b2b7ef4b20ee7719a7efc0518387f82f11c3f 100644 (file)
@@ -1,5 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0-only
-lzo_compress-objs := lzo1x_compress.o
+lzo_compress-objs := lzo1x_compress.o lzo1x_compress_safe.o
 lzo_decompress-objs := lzo1x_decompress_safe.o
 
 obj-$(CONFIG_LZO_COMPRESS) += lzo_compress.o
index 47d6d43ea9578c93cfbd44b3420426cab1826638..7b10ca86a89300d8febfc3d2564aa4e407ed2c07 100644 (file)
 #include <linux/lzo.h>
 #include "lzodefs.h"
 
-static noinline size_t
-lzo1x_1_do_compress(const unsigned char *in, size_t in_len,
-                   unsigned char *out, size_t *out_len,
-                   size_t ti, void *wrkmem, signed char *state_offset,
-                   const unsigned char bitstream_version)
+#undef LZO_UNSAFE
+
+#ifndef LZO_SAFE
+#define LZO_UNSAFE 1
+#define LZO_SAFE(name) name
+#define HAVE_OP(x) 1
+#endif
+
+#define NEED_OP(x) if (!HAVE_OP(x)) goto output_overrun
+
+static noinline int
+LZO_SAFE(lzo1x_1_do_compress)(const unsigned char *in, size_t in_len,
+                             unsigned char **out, unsigned char *op_end,
+                             size_t *tp, void *wrkmem,
+                             signed char *state_offset,
+                             const unsigned char bitstream_version)
 {
        const unsigned char *ip;
        unsigned char *op;
@@ -30,8 +41,9 @@ lzo1x_1_do_compress(const unsigned char *in, size_t in_len,
        const unsigned char * const ip_end = in + in_len - 20;
        const unsigned char *ii;
        lzo_dict_t * const dict = (lzo_dict_t *) wrkmem;
+       size_t ti = *tp;
 
-       op = out;
+       op = *out;
        ip = in;
        ii = ip;
        ip += ti < 4 ? 4 - ti : 0;
@@ -116,25 +128,32 @@ next:
                if (t != 0) {
                        if (t <= 3) {
                                op[*state_offset] |= t;
+                               NEED_OP(4);
                                COPY4(op, ii);
                                op += t;
                        } else if (t <= 16) {
+                               NEED_OP(17);
                                *op++ = (t - 3);
                                COPY8(op, ii);
                                COPY8(op + 8, ii + 8);
                                op += t;
                        } else {
                                if (t <= 18) {
+                                       NEED_OP(1);
                                        *op++ = (t - 3);
                                } else {
                                        size_t tt = t - 18;
+                                       NEED_OP(1);
                                        *op++ = 0;
                                        while (unlikely(tt > 255)) {
                                                tt -= 255;
+                                               NEED_OP(1);
                                                *op++ = 0;
                                        }
+                                       NEED_OP(1);
                                        *op++ = tt;
                                }
+                               NEED_OP(t);
                                do {
                                        COPY8(op, ii);
                                        COPY8(op + 8, ii + 8);
@@ -151,6 +170,7 @@ next:
                if (unlikely(run_length)) {
                        ip += run_length;
                        run_length -= MIN_ZERO_RUN_LENGTH;
+                       NEED_OP(4);
                        put_unaligned_le32((run_length << 21) | 0xfffc18
                                           | (run_length & 0x7), op);
                        op += 4;
@@ -243,10 +263,12 @@ m_len_done:
                ip += m_len;
                if (m_len <= M2_MAX_LEN && m_off <= M2_MAX_OFFSET) {
                        m_off -= 1;
+                       NEED_OP(2);
                        *op++ = (((m_len - 1) << 5) | ((m_off & 7) << 2));
                        *op++ = (m_off >> 3);
                } else if (m_off <= M3_MAX_OFFSET) {
                        m_off -= 1;
+                       NEED_OP(1);
                        if (m_len <= M3_MAX_LEN)
                                *op++ = (M3_MARKER | (m_len - 2));
                        else {
@@ -254,14 +276,18 @@ m_len_done:
                                *op++ = M3_MARKER | 0;
                                while (unlikely(m_len > 255)) {
                                        m_len -= 255;
+                                       NEED_OP(1);
                                        *op++ = 0;
                                }
+                               NEED_OP(1);
                                *op++ = (m_len);
                        }
+                       NEED_OP(2);
                        *op++ = (m_off << 2);
                        *op++ = (m_off >> 6);
                } else {
                        m_off -= 0x4000;
+                       NEED_OP(1);
                        if (m_len <= M4_MAX_LEN)
                                *op++ = (M4_MARKER | ((m_off >> 11) & 8)
                                                | (m_len - 2));
@@ -282,11 +308,14 @@ m_len_done:
                                m_len -= M4_MAX_LEN;
                                *op++ = (M4_MARKER | ((m_off >> 11) & 8));
                                while (unlikely(m_len > 255)) {
+                                       NEED_OP(1);
                                        m_len -= 255;
                                        *op++ = 0;
                                }
+                               NEED_OP(1);
                                *op++ = (m_len);
                        }
+                       NEED_OP(2);
                        *op++ = (m_off << 2);
                        *op++ = (m_off >> 6);
                }
@@ -295,14 +324,20 @@ finished_writing_instruction:
                ii = ip;
                goto next;
        }
-       *out_len = op - out;
-       return in_end - (ii - ti);
+       *out = op;
+       *tp = in_end - (ii - ti);
+       return LZO_E_OK;
+
+output_overrun:
+       return LZO_E_OUTPUT_OVERRUN;
 }
 
-static int lzogeneric1x_1_compress(const unsigned char *in, size_t in_len,
-                    unsigned char *out, size_t *out_len,
-                    void *wrkmem, const unsigned char bitstream_version)
+static int LZO_SAFE(lzogeneric1x_1_compress)(
+       const unsigned char *in, size_t in_len,
+       unsigned char *out, size_t *out_len,
+       void *wrkmem, const unsigned char bitstream_version)
 {
+       unsigned char * const op_end = out + *out_len;
        const unsigned char *ip = in;
        unsigned char *op = out;
        unsigned char *data_start;
@@ -326,14 +361,18 @@ static int lzogeneric1x_1_compress(const unsigned char *in, size_t in_len,
        while (l > 20) {
                size_t ll = min_t(size_t, l, m4_max_offset + 1);
                uintptr_t ll_end = (uintptr_t) ip + ll;
+               int err;
+
                if ((ll_end + ((t + ll) >> 5)) <= ll_end)
                        break;
                BUILD_BUG_ON(D_SIZE * sizeof(lzo_dict_t) > LZO1X_1_MEM_COMPRESS);
                memset(wrkmem, 0, D_SIZE * sizeof(lzo_dict_t));
-               t = lzo1x_1_do_compress(ip, ll, op, out_len, t, wrkmem,
-                                       &state_offset, bitstream_version);
+               err = LZO_SAFE(lzo1x_1_do_compress)(
+                       ip, ll, &op, op_end, &t, wrkmem,
+                       &state_offset, bitstream_version);
+               if (err != LZO_E_OK)
+                       return err;
                ip += ll;
-               op += *out_len;
                l  -= ll;
        }
        t += l;
@@ -342,20 +381,26 @@ static int lzogeneric1x_1_compress(const unsigned char *in, size_t in_len,
                const unsigned char *ii = in + in_len - t;
 
                if (op == data_start && t <= 238) {
+                       NEED_OP(1);
                        *op++ = (17 + t);
                } else if (t <= 3) {
                        op[state_offset] |= t;
                } else if (t <= 18) {
+                       NEED_OP(1);
                        *op++ = (t - 3);
                } else {
                        size_t tt = t - 18;
+                       NEED_OP(1);
                        *op++ = 0;
                        while (tt > 255) {
                                tt -= 255;
+                               NEED_OP(1);
                                *op++ = 0;
                        }
+                       NEED_OP(1);
                        *op++ = tt;
                }
+               NEED_OP(t);
                if (t >= 16) do {
                        COPY8(op, ii);
                        COPY8(op + 8, ii + 8);
@@ -368,31 +413,38 @@ static int lzogeneric1x_1_compress(const unsigned char *in, size_t in_len,
                } while (--t > 0);
        }
 
+       NEED_OP(3);
        *op++ = M4_MARKER | 1;
        *op++ = 0;
        *op++ = 0;
 
        *out_len = op - out;
        return LZO_E_OK;
+
+output_overrun:
+       return LZO_E_OUTPUT_OVERRUN;
 }
 
-int lzo1x_1_compress(const unsigned char *in, size_t in_len,
-                    unsigned char *out, size_t *out_len,
-                    void *wrkmem)
+int LZO_SAFE(lzo1x_1_compress)(const unsigned char *in, size_t in_len,
+                              unsigned char *out, size_t *out_len,
+                              void *wrkmem)
 {
-       return lzogeneric1x_1_compress(in, in_len, out, out_len, wrkmem, 0);
+       return LZO_SAFE(lzogeneric1x_1_compress)(
+               in, in_len, out, out_len, wrkmem, 0);
 }
 
-int lzorle1x_1_compress(const unsigned char *in, size_t in_len,
-                    unsigned char *out, size_t *out_len,
-                    void *wrkmem)
+int LZO_SAFE(lzorle1x_1_compress)(const unsigned char *in, size_t in_len,
+                                 unsigned char *out, size_t *out_len,
+                                 void *wrkmem)
 {
-       return lzogeneric1x_1_compress(in, in_len, out, out_len,
-                                      wrkmem, LZO_VERSION);
+       return LZO_SAFE(lzogeneric1x_1_compress)(
+               in, in_len, out, out_len, wrkmem, LZO_VERSION);
 }
 
-EXPORT_SYMBOL_GPL(lzo1x_1_compress);
-EXPORT_SYMBOL_GPL(lzorle1x_1_compress);
+EXPORT_SYMBOL_GPL(LZO_SAFE(lzo1x_1_compress));
+EXPORT_SYMBOL_GPL(LZO_SAFE(lzorle1x_1_compress));
 
+#ifndef LZO_UNSAFE
 MODULE_LICENSE("GPL");
 MODULE_DESCRIPTION("LZO1X-1 Compressor");
+#endif
diff --git a/lib/lzo/lzo1x_compress_safe.c b/lib/lzo/lzo1x_compress_safe.c
new file mode 100644 (file)
index 0000000..371c9f8
--- /dev/null
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ *  LZO1X Compressor from LZO
+ *
+ *  Copyright (C) 1996-2012 Markus F.X.J. Oberhumer <markus@oberhumer.com>
+ *
+ *  The full LZO package can be found at:
+ *  http://www.oberhumer.com/opensource/lzo/
+ *
+ *  Changed for Linux kernel use by:
+ *  Nitin Gupta <nitingupta910@gmail.com>
+ *  Richard Purdie <rpurdie@openedhand.com>
+ */
+
+#define LZO_SAFE(name) name##_safe
+#define HAVE_OP(x) ((size_t)(op_end - op) >= (size_t)(x))
+
+#include "lzo1x_compress.c"