18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * LZO decompressor for the Linux kernel. Code borrowed from the lzo
48c2ecf20Sopenharmony_ci * implementation by Markus Franz Xaver Johannes Oberhumer.
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci * Linux kernel adaptation:
78c2ecf20Sopenharmony_ci * Copyright (C) 2009
88c2ecf20Sopenharmony_ci * Albin Tonnerre, Free Electrons <albin.tonnerre@free-electrons.com>
98c2ecf20Sopenharmony_ci *
108c2ecf20Sopenharmony_ci * Original code:
118c2ecf20Sopenharmony_ci * Copyright (C) 1996-2005 Markus Franz Xaver Johannes Oberhumer
128c2ecf20Sopenharmony_ci * All Rights Reserved.
138c2ecf20Sopenharmony_ci *
148c2ecf20Sopenharmony_ci * Markus F.X.J. Oberhumer
158c2ecf20Sopenharmony_ci * <markus@oberhumer.com>
168c2ecf20Sopenharmony_ci * http://www.oberhumer.com/opensource/lzop/
178c2ecf20Sopenharmony_ci */
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#ifdef STATIC
208c2ecf20Sopenharmony_ci#define PREBOOT
218c2ecf20Sopenharmony_ci#include "lzo/lzo1x_decompress_safe.c"
228c2ecf20Sopenharmony_ci#else
238c2ecf20Sopenharmony_ci#include <linux/decompress/unlzo.h>
248c2ecf20Sopenharmony_ci#endif
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_ci#include <linux/types.h>
278c2ecf20Sopenharmony_ci#include <linux/lzo.h>
288c2ecf20Sopenharmony_ci#include <linux/decompress/mm.h>
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci#include <linux/compiler.h>
318c2ecf20Sopenharmony_ci#include <asm/unaligned.h>
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_cistatic const unsigned char lzop_magic[] = {
348c2ecf20Sopenharmony_ci	0x89, 0x4c, 0x5a, 0x4f, 0x00, 0x0d, 0x0a, 0x1a, 0x0a };
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci#define LZO_BLOCK_SIZE        (256*1024l)
378c2ecf20Sopenharmony_ci#define HEADER_HAS_FILTER      0x00000800L
388c2ecf20Sopenharmony_ci#define HEADER_SIZE_MIN       (9 + 7     + 4 + 8     + 1       + 4)
398c2ecf20Sopenharmony_ci#define HEADER_SIZE_MAX       (9 + 7 + 1 + 8 + 8 + 4 + 1 + 255 + 4)
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_ciSTATIC inline long INIT parse_header(u8 *input, long *skip, long in_len)
428c2ecf20Sopenharmony_ci{
438c2ecf20Sopenharmony_ci	int l;
448c2ecf20Sopenharmony_ci	u8 *parse = input;
458c2ecf20Sopenharmony_ci	u8 *end = input + in_len;
468c2ecf20Sopenharmony_ci	u8 level = 0;
478c2ecf20Sopenharmony_ci	u16 version;
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_ci	/*
508c2ecf20Sopenharmony_ci	 * Check that there's enough input to possibly have a valid header.
518c2ecf20Sopenharmony_ci	 * Then it is possible to parse several fields until the minimum
528c2ecf20Sopenharmony_ci	 * size may have been used.
538c2ecf20Sopenharmony_ci	 */
548c2ecf20Sopenharmony_ci	if (in_len < HEADER_SIZE_MIN)
558c2ecf20Sopenharmony_ci		return 0;
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci	/* read magic: 9 first bits */
588c2ecf20Sopenharmony_ci	for (l = 0; l < 9; l++) {
598c2ecf20Sopenharmony_ci		if (*parse++ != lzop_magic[l])
608c2ecf20Sopenharmony_ci			return 0;
618c2ecf20Sopenharmony_ci	}
628c2ecf20Sopenharmony_ci	/* get version (2bytes), skip library version (2),
638c2ecf20Sopenharmony_ci	 * 'need to be extracted' version (2) and
648c2ecf20Sopenharmony_ci	 * method (1) */
658c2ecf20Sopenharmony_ci	version = get_unaligned_be16(parse);
668c2ecf20Sopenharmony_ci	parse += 7;
678c2ecf20Sopenharmony_ci	if (version >= 0x0940)
688c2ecf20Sopenharmony_ci		level = *parse++;
698c2ecf20Sopenharmony_ci	if (get_unaligned_be32(parse) & HEADER_HAS_FILTER)
708c2ecf20Sopenharmony_ci		parse += 8; /* flags + filter info */
718c2ecf20Sopenharmony_ci	else
728c2ecf20Sopenharmony_ci		parse += 4; /* flags */
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_ci	/*
758c2ecf20Sopenharmony_ci	 * At least mode, mtime_low, filename length, and checksum must
768c2ecf20Sopenharmony_ci	 * be left to be parsed. If also mtime_high is present, it's OK
778c2ecf20Sopenharmony_ci	 * because the next input buffer check is after reading the
788c2ecf20Sopenharmony_ci	 * filename length.
798c2ecf20Sopenharmony_ci	 */
808c2ecf20Sopenharmony_ci	if (end - parse < 8 + 1 + 4)
818c2ecf20Sopenharmony_ci		return 0;
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci	/* skip mode and mtime_low */
848c2ecf20Sopenharmony_ci	parse += 8;
858c2ecf20Sopenharmony_ci	if (version >= 0x0940)
868c2ecf20Sopenharmony_ci		parse += 4;	/* skip mtime_high */
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci	l = *parse++;
898c2ecf20Sopenharmony_ci	/* don't care about the file name, and skip checksum */
908c2ecf20Sopenharmony_ci	if (end - parse < l + 4)
918c2ecf20Sopenharmony_ci		return 0;
928c2ecf20Sopenharmony_ci	parse += l + 4;
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci	*skip = parse - input;
958c2ecf20Sopenharmony_ci	return 1;
968c2ecf20Sopenharmony_ci}
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ciSTATIC int INIT unlzo(u8 *input, long in_len,
998c2ecf20Sopenharmony_ci				long (*fill)(void *, unsigned long),
1008c2ecf20Sopenharmony_ci				long (*flush)(void *, unsigned long),
1018c2ecf20Sopenharmony_ci				u8 *output, long *posp,
1028c2ecf20Sopenharmony_ci				void (*error) (char *x))
1038c2ecf20Sopenharmony_ci{
1048c2ecf20Sopenharmony_ci	u8 r = 0;
1058c2ecf20Sopenharmony_ci	long skip = 0;
1068c2ecf20Sopenharmony_ci	u32 src_len, dst_len;
1078c2ecf20Sopenharmony_ci	size_t tmp;
1088c2ecf20Sopenharmony_ci	u8 *in_buf, *in_buf_save, *out_buf;
1098c2ecf20Sopenharmony_ci	int ret = -1;
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci	if (output) {
1128c2ecf20Sopenharmony_ci		out_buf = output;
1138c2ecf20Sopenharmony_ci	} else if (!flush) {
1148c2ecf20Sopenharmony_ci		error("NULL output pointer and no flush function provided");
1158c2ecf20Sopenharmony_ci		goto exit;
1168c2ecf20Sopenharmony_ci	} else {
1178c2ecf20Sopenharmony_ci		out_buf = malloc(LZO_BLOCK_SIZE);
1188c2ecf20Sopenharmony_ci		if (!out_buf) {
1198c2ecf20Sopenharmony_ci			error("Could not allocate output buffer");
1208c2ecf20Sopenharmony_ci			goto exit;
1218c2ecf20Sopenharmony_ci		}
1228c2ecf20Sopenharmony_ci	}
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	if (input && fill) {
1258c2ecf20Sopenharmony_ci		error("Both input pointer and fill function provided, don't know what to do");
1268c2ecf20Sopenharmony_ci		goto exit_1;
1278c2ecf20Sopenharmony_ci	} else if (input) {
1288c2ecf20Sopenharmony_ci		in_buf = input;
1298c2ecf20Sopenharmony_ci	} else if (!fill) {
1308c2ecf20Sopenharmony_ci		error("NULL input pointer and missing fill function");
1318c2ecf20Sopenharmony_ci		goto exit_1;
1328c2ecf20Sopenharmony_ci	} else {
1338c2ecf20Sopenharmony_ci		in_buf = malloc(lzo1x_worst_compress(LZO_BLOCK_SIZE));
1348c2ecf20Sopenharmony_ci		if (!in_buf) {
1358c2ecf20Sopenharmony_ci			error("Could not allocate input buffer");
1368c2ecf20Sopenharmony_ci			goto exit_1;
1378c2ecf20Sopenharmony_ci		}
1388c2ecf20Sopenharmony_ci	}
1398c2ecf20Sopenharmony_ci	in_buf_save = in_buf;
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ci	if (posp)
1428c2ecf20Sopenharmony_ci		*posp = 0;
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci	if (fill) {
1458c2ecf20Sopenharmony_ci		/*
1468c2ecf20Sopenharmony_ci		 * Start from in_buf + HEADER_SIZE_MAX to make it possible
1478c2ecf20Sopenharmony_ci		 * to use memcpy() to copy the unused data to the beginning
1488c2ecf20Sopenharmony_ci		 * of the buffer. This way memmove() isn't needed which
1498c2ecf20Sopenharmony_ci		 * is missing from pre-boot environments of most archs.
1508c2ecf20Sopenharmony_ci		 */
1518c2ecf20Sopenharmony_ci		in_buf += HEADER_SIZE_MAX;
1528c2ecf20Sopenharmony_ci		in_len = fill(in_buf, HEADER_SIZE_MAX);
1538c2ecf20Sopenharmony_ci	}
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci	if (!parse_header(in_buf, &skip, in_len)) {
1568c2ecf20Sopenharmony_ci		error("invalid header");
1578c2ecf20Sopenharmony_ci		goto exit_2;
1588c2ecf20Sopenharmony_ci	}
1598c2ecf20Sopenharmony_ci	in_buf += skip;
1608c2ecf20Sopenharmony_ci	in_len -= skip;
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci	if (fill) {
1638c2ecf20Sopenharmony_ci		/* Move the unused data to the beginning of the buffer. */
1648c2ecf20Sopenharmony_ci		memcpy(in_buf_save, in_buf, in_len);
1658c2ecf20Sopenharmony_ci		in_buf = in_buf_save;
1668c2ecf20Sopenharmony_ci	}
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci	if (posp)
1698c2ecf20Sopenharmony_ci		*posp = skip;
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_ci	for (;;) {
1728c2ecf20Sopenharmony_ci		/* read uncompressed block size */
1738c2ecf20Sopenharmony_ci		if (fill && in_len < 4) {
1748c2ecf20Sopenharmony_ci			skip = fill(in_buf + in_len, 4 - in_len);
1758c2ecf20Sopenharmony_ci			if (skip > 0)
1768c2ecf20Sopenharmony_ci				in_len += skip;
1778c2ecf20Sopenharmony_ci		}
1788c2ecf20Sopenharmony_ci		if (in_len < 4) {
1798c2ecf20Sopenharmony_ci			error("file corrupted");
1808c2ecf20Sopenharmony_ci			goto exit_2;
1818c2ecf20Sopenharmony_ci		}
1828c2ecf20Sopenharmony_ci		dst_len = get_unaligned_be32(in_buf);
1838c2ecf20Sopenharmony_ci		in_buf += 4;
1848c2ecf20Sopenharmony_ci		in_len -= 4;
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci		/* exit if last block */
1878c2ecf20Sopenharmony_ci		if (dst_len == 0) {
1888c2ecf20Sopenharmony_ci			if (posp)
1898c2ecf20Sopenharmony_ci				*posp += 4;
1908c2ecf20Sopenharmony_ci			break;
1918c2ecf20Sopenharmony_ci		}
1928c2ecf20Sopenharmony_ci
1938c2ecf20Sopenharmony_ci		if (dst_len > LZO_BLOCK_SIZE) {
1948c2ecf20Sopenharmony_ci			error("dest len longer than block size");
1958c2ecf20Sopenharmony_ci			goto exit_2;
1968c2ecf20Sopenharmony_ci		}
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_ci		/* read compressed block size, and skip block checksum info */
1998c2ecf20Sopenharmony_ci		if (fill && in_len < 8) {
2008c2ecf20Sopenharmony_ci			skip = fill(in_buf + in_len, 8 - in_len);
2018c2ecf20Sopenharmony_ci			if (skip > 0)
2028c2ecf20Sopenharmony_ci				in_len += skip;
2038c2ecf20Sopenharmony_ci		}
2048c2ecf20Sopenharmony_ci		if (in_len < 8) {
2058c2ecf20Sopenharmony_ci			error("file corrupted");
2068c2ecf20Sopenharmony_ci			goto exit_2;
2078c2ecf20Sopenharmony_ci		}
2088c2ecf20Sopenharmony_ci		src_len = get_unaligned_be32(in_buf);
2098c2ecf20Sopenharmony_ci		in_buf += 8;
2108c2ecf20Sopenharmony_ci		in_len -= 8;
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ci		if (src_len <= 0 || src_len > dst_len) {
2138c2ecf20Sopenharmony_ci			error("file corrupted");
2148c2ecf20Sopenharmony_ci			goto exit_2;
2158c2ecf20Sopenharmony_ci		}
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci		/* decompress */
2188c2ecf20Sopenharmony_ci		if (fill && in_len < src_len) {
2198c2ecf20Sopenharmony_ci			skip = fill(in_buf + in_len, src_len - in_len);
2208c2ecf20Sopenharmony_ci			if (skip > 0)
2218c2ecf20Sopenharmony_ci				in_len += skip;
2228c2ecf20Sopenharmony_ci		}
2238c2ecf20Sopenharmony_ci		if (in_len < src_len) {
2248c2ecf20Sopenharmony_ci			error("file corrupted");
2258c2ecf20Sopenharmony_ci			goto exit_2;
2268c2ecf20Sopenharmony_ci		}
2278c2ecf20Sopenharmony_ci		tmp = dst_len;
2288c2ecf20Sopenharmony_ci
2298c2ecf20Sopenharmony_ci		/* When the input data is not compressed at all,
2308c2ecf20Sopenharmony_ci		 * lzo1x_decompress_safe will fail, so call memcpy()
2318c2ecf20Sopenharmony_ci		 * instead */
2328c2ecf20Sopenharmony_ci		if (unlikely(dst_len == src_len))
2338c2ecf20Sopenharmony_ci			memcpy(out_buf, in_buf, src_len);
2348c2ecf20Sopenharmony_ci		else {
2358c2ecf20Sopenharmony_ci			r = lzo1x_decompress_safe((u8 *) in_buf, src_len,
2368c2ecf20Sopenharmony_ci						out_buf, &tmp);
2378c2ecf20Sopenharmony_ci
2388c2ecf20Sopenharmony_ci			if (r != LZO_E_OK || dst_len != tmp) {
2398c2ecf20Sopenharmony_ci				error("Compressed data violation");
2408c2ecf20Sopenharmony_ci				goto exit_2;
2418c2ecf20Sopenharmony_ci			}
2428c2ecf20Sopenharmony_ci		}
2438c2ecf20Sopenharmony_ci
2448c2ecf20Sopenharmony_ci		if (flush && flush(out_buf, dst_len) != dst_len)
2458c2ecf20Sopenharmony_ci			goto exit_2;
2468c2ecf20Sopenharmony_ci		if (output)
2478c2ecf20Sopenharmony_ci			out_buf += dst_len;
2488c2ecf20Sopenharmony_ci		if (posp)
2498c2ecf20Sopenharmony_ci			*posp += src_len + 12;
2508c2ecf20Sopenharmony_ci
2518c2ecf20Sopenharmony_ci		in_buf += src_len;
2528c2ecf20Sopenharmony_ci		in_len -= src_len;
2538c2ecf20Sopenharmony_ci		if (fill) {
2548c2ecf20Sopenharmony_ci			/*
2558c2ecf20Sopenharmony_ci			 * If there happens to still be unused data left in
2568c2ecf20Sopenharmony_ci			 * in_buf, move it to the beginning of the buffer.
2578c2ecf20Sopenharmony_ci			 * Use a loop to avoid memmove() dependency.
2588c2ecf20Sopenharmony_ci			 */
2598c2ecf20Sopenharmony_ci			if (in_len > 0)
2608c2ecf20Sopenharmony_ci				for (skip = 0; skip < in_len; ++skip)
2618c2ecf20Sopenharmony_ci					in_buf_save[skip] = in_buf[skip];
2628c2ecf20Sopenharmony_ci			in_buf = in_buf_save;
2638c2ecf20Sopenharmony_ci		}
2648c2ecf20Sopenharmony_ci	}
2658c2ecf20Sopenharmony_ci
2668c2ecf20Sopenharmony_ci	ret = 0;
2678c2ecf20Sopenharmony_ciexit_2:
2688c2ecf20Sopenharmony_ci	if (!input)
2698c2ecf20Sopenharmony_ci		free(in_buf_save);
2708c2ecf20Sopenharmony_ciexit_1:
2718c2ecf20Sopenharmony_ci	if (!output)
2728c2ecf20Sopenharmony_ci		free(out_buf);
2738c2ecf20Sopenharmony_ciexit:
2748c2ecf20Sopenharmony_ci	return ret;
2758c2ecf20Sopenharmony_ci}
2768c2ecf20Sopenharmony_ci
2778c2ecf20Sopenharmony_ci#ifdef PREBOOT
2788c2ecf20Sopenharmony_ciSTATIC int INIT __decompress(unsigned char *buf, long len,
2798c2ecf20Sopenharmony_ci			   long (*fill)(void*, unsigned long),
2808c2ecf20Sopenharmony_ci			   long (*flush)(void*, unsigned long),
2818c2ecf20Sopenharmony_ci			   unsigned char *out_buf, long olen,
2828c2ecf20Sopenharmony_ci			   long *pos,
2838c2ecf20Sopenharmony_ci			   void (*error)(char *x))
2848c2ecf20Sopenharmony_ci{
2858c2ecf20Sopenharmony_ci	return unlzo(buf, len, fill, flush, out_buf, pos, error);
2868c2ecf20Sopenharmony_ci}
2878c2ecf20Sopenharmony_ci#endif
288