Commit cc47f072 authored by Herbert Xu's avatar Herbert Xu
Browse files

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: default avatarHerbert Xu <herbert@gondor.apana.org.au>
Reviewed-by: default avatarDavid Sterba <dsterba@suse.com>
Signed-off-by: default avatarHerbert Xu <herbert@gondor.apana.org.au>
parent ef2a68f8
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -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;
+1 −1
Original line number Diff line number Diff line
@@ -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;
+8 −0
Original line number Diff line number Diff line
@@ -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);
+1 −1
Original line number Diff line number Diff line
# 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
+77 −25
Original line number Diff line number Diff line
@@ -18,10 +18,21 @@
#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,
#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;
@@ -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 @@ lzo1x_1_do_compress(const unsigned char *in, size_t in_len,
		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 @@ lzo1x_1_do_compress(const unsigned char *in, size_t in_len,
		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 @@ lzo1x_1_do_compress(const unsigned char *in, size_t in_len,
		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 @@ lzo1x_1_do_compress(const unsigned char *in, size_t in_len,
				*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 @@ lzo1x_1_do_compress(const unsigned char *in, size_t in_len,
				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 @@ lzo1x_1_do_compress(const unsigned char *in, size_t in_len,
		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,
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,
		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,
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,
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
Loading