crypto: lzo - Fix compression buffer overrun

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>
This commit is contained in:
Herbert Xu 2025-02-27 17:04:46 +08:00
parent ef2a68f815
commit cc47f07234
6 changed files with 106 additions and 28 deletions

View 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;

View 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;

View File

@ -24,10 +24,18 @@
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);

View 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

View File

@ -18,11 +18,22 @@
#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

View File

@ -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"