From f5e8b2cf35bf23f05fba6a58b6d6972e56fb2bda Mon Sep 17 00:00:00 2001
From: "yulong.tang" <yulong.tang@nio.com>
Date: Tue, 20 Feb 2024 15:09:49 +0800
Subject: [PATCH] Adding the zram decompression algorithm "lzo-rle"

Port the improved decompression method for "lzo" in the kernel to
support decompression of "lzorle".

Since Linux 5.1, the default compression algorithm for zram was changed
from "lzo" to "lzo-rle".  The crash-utility only supports decompression
for "lzo", when parsing vmcore files that utilize zram compression, such
as when using the gcore command to detach process core dump files,
parsing cannot be completed successfully.

before:
	crash> gcore -v 0 1
	gcore: WARNING: only the lzo compressor is supported
	gcore: WARNING: only the lzo compressor is supported
	gcore: WARNING: only the lzo compressor is supported
	gcore: WARNING: only the lzo compressor is supported
after:
	crash> gcore -v 0 1
	Saved core.1.init

Signed-off-by: yulong.tang <yulong.tang@nio.com>
Reviewed-by: Tao Liu <ltao@redhat.com>
Signed-off-by: Kazuhito Hagio <k-hagio-ab@nec.com>
---
 Makefile            |  13 +-
 diskdump.c          |   3 +
 lzorle_decompress.c | 295 ++++++++++++++++++++++++++++++++++++++++++++
 lzorle_decompress.h |  75 +++++++++++
 4 files changed, 383 insertions(+), 3 deletions(-)
 create mode 100644 lzorle_decompress.c
 create mode 100644 lzorle_decompress.h

diff --git a/Makefile b/Makefile
index 9e97313..60dad18 100644
--- a/Makefile
+++ b/Makefile
@@ -60,6 +60,7 @@ SADUMP_HFILES=sadump.h
 UNWIND_HFILES=unwind.h unwind_i.h rse.h unwind_x86.h unwind_x86_64.h
 VMWARE_HFILES=vmware_vmss.h
 MAPLE_TREE_HFILES=maple_tree.h
+LZORLE_HFILES=lzorle_decompress.h
 
 CFILES=main.c tools.c global_data.c memory.c filesys.c help.c task.c \
 	kernel.c test.c gdb_interface.c configure.c net.c dev.c bpf.c \
@@ -74,12 +75,14 @@ CFILES=main.c tools.c global_data.c memory.c filesys.c help.c task.c \
 	xen_hyper.c xen_hyper_command.c xen_hyper_global_data.c \
 	xen_hyper_dump_tables.c kvmdump.c qemu.c qemu-load.c sadump.c ipcs.c \
 	ramdump.c vmware_vmss.c vmware_guestdump.c \
-	xen_dom0.c kaslr_helper.c sbitmap.c maple_tree.c
+	xen_dom0.c kaslr_helper.c sbitmap.c maple_tree.c \
+	lzorle_decompress.c
 
 SOURCE_FILES=${CFILES} ${GENERIC_HFILES} ${MCORE_HFILES} \
 	${REDHAT_CFILES} ${REDHAT_HFILES} ${UNWIND_HFILES} \
 	${LKCD_DUMP_HFILES} ${LKCD_TRACE_HFILES} ${LKCD_OBSOLETE_HFILES}\
-	${IBM_HFILES} ${SADUMP_HFILES} ${VMWARE_HFILES} ${MAPLE_TREE_HFILES}
+	${IBM_HFILES} ${SADUMP_HFILES} ${VMWARE_HFILES} ${MAPLE_TREE_HFILES} \
+	${LZORLE_HFILES}
 
 OBJECT_FILES=main.o tools.o global_data.o memory.o filesys.o help.o task.o \
 	build_data.o kernel.o test.o gdb_interface.o net.o dev.o bpf.o \
@@ -94,7 +97,8 @@ OBJECT_FILES=main.o tools.o global_data.o memory.o filesys.o help.o task.o \
 	xen_hyper.o xen_hyper_command.o xen_hyper_global_data.o \
 	xen_hyper_dump_tables.o kvmdump.o qemu.o qemu-load.o sadump.o ipcs.o \
 	ramdump.o vmware_vmss.o vmware_guestdump.o \
-	xen_dom0.o kaslr_helper.o sbitmap.o maple_tree.o
+	xen_dom0.o kaslr_helper.o sbitmap.o maple_tree.o \
+	lzorle_decompress.o
 
 MEMORY_DRIVER_FILES=memory_driver/Makefile memory_driver/crash.c memory_driver/README
 
@@ -546,6 +550,9 @@ bpf.o: ${GENERIC_HFILES} bpf.c
 maple_tree.o: ${GENERIC_HFILES} ${MAPLE_TREE_HFILES} maple_tree.c
 	${CC} -c ${CRASH_CFLAGS} maple_tree.c ${WARNING_OPTIONS} ${WARNING_ERROR}
 
+lzorle_decompress.o: lzorle_decompress.c
+	${CC} -c ${CRASH_CFLAGS} lzorle_decompress.c ${WARNING_OPTIONS} ${WARNING_ERROR}
+
 ${PROGRAM}: force
 	@$(MAKE) all
 
diff --git a/diskdump.c b/diskdump.c
index 3ae7bf2..4a473e1 100644
--- a/diskdump.c
+++ b/diskdump.c
@@ -28,6 +28,7 @@
 #include "xen_dom0.h"
 #include "vmcore.h"
 #include "maple_tree.h"
+#include "lzorle_decompress.h"
 
 #define BITMAP_SECT_LEN	4096
 
@@ -3069,6 +3070,8 @@ try_zram_decompress(ulonglong pte_val, unsigned char *buf, ulong len, ulonglong
 		      " with lzo library\n");
 		return 0;
 #endif
+	} else if (STREQ(name, "lzo-rle")) {
+		decompressor = (void *)&lzorle_decompress_safe;
 	} else { /* todo: support more compressor */
 		error(WARNING, "only the lzo compressor is supported\n");
 		return 0;
diff --git a/lzorle_decompress.c b/lzorle_decompress.c
new file mode 100644
index 0000000..98eb9b2
--- /dev/null
+++ b/lzorle_decompress.c
@@ -0,0 +1,295 @@
+/* lzorle_decompress.h
+ *
+ * from kernel lib/lzo/lzo1x_decompress_safe.c
+ *
+ * Copyright (C) 1996-2012 Markus F.X.J. Oberhumer <markus@oberhumer.com>
+ * Copyright (C) 2024 NIO
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include "defs.h"
+#include "lzorle_decompress.h"
+
+/* This MAX_255_COUNT is the maximum number of times we can add 255 to a base
+ * count without overflowing an integer. The multiply will overflow when
+ * multiplying 255 by more than MAXINT/255. The sum will overflow earlier
+ * depending on the base count. Since the base count is taken from a u8
+ * and a few bits, it is safe to assume that it will always be lower than
+ * or equal to 2*255, thus we can always prevent any overflow by accepting
+ * two less 255 steps. See Documentation/lzo.txt for more information.
+ */
+#define MAX_255_COUNT ((((ulong)~0) / 255) - 2)
+
+static inline uint16_t get_unaligned_le16 (const uint8_t *p) {
+	return p[0] | p[1] << 8;
+}
+
+int lzorle_decompress_safe(const unsigned char *in, ulong in_len,
+			  unsigned char *out, ulong *out_len, void *other/* NOT USED */) {
+	unsigned char *op;
+	const unsigned char *ip;
+	ulong t, next;
+	ulong state = 0;
+	const unsigned char *m_pos;
+	const unsigned char * const ip_end = in + in_len;
+	unsigned char * const op_end = out + *out_len;
+
+	unsigned char bitstream_version;
+
+	static int efficient_unaligned_access = -1;
+
+	if (efficient_unaligned_access == -1) {
+#if defined(ARM) || defined(ARM64) || defined(X86) || defined(X86_64) || defined(PPC) || defined(PPC64) || defined(S390)|| defined(S390X)
+		efficient_unaligned_access = TRUE;
+#else
+		efficient_unaligned_access = FALSE;
+#endif
+
+		if ((kt->ikconfig_flags & IKCONFIG_AVAIL) &&
+		    (get_kernel_config("CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS", NULL) == IKCONFIG_Y))
+			efficient_unaligned_access = TRUE;
+	}
+
+	op = out;
+	ip = in;
+
+	if (in_len < 3)
+		goto input_overrun;
+
+	if (in_len >= 5 && *ip == 17) {
+		bitstream_version = ip[1];
+		ip += 2;
+	} else {
+		bitstream_version = 0;
+	}
+
+	if (*ip > 17) {
+		t = *ip++ - 17;
+		if (t < 4) {
+			next = t;
+			goto match_next;
+		}
+		goto copy_literal_run;
+	}
+
+	for (;;) {
+		t = *ip++;
+		if (t < 16) {
+			if (state == 0) {
+				if (t == 0) {
+					ulong offset;
+					const unsigned char *ip_last = ip;
+
+					while (*ip == 0) {
+						ip++;
+						NEED_IP(1);
+					}
+					offset = ip - ip_last;
+					if (offset > MAX_255_COUNT)
+						return LZO_E_ERROR;
+
+					offset = (offset << 8) - offset;
+					t += offset + 15 + *ip++;
+				}
+				t += 3;
+copy_literal_run:
+				if (efficient_unaligned_access &&
+				    (HAVE_IP(t + 15) && HAVE_OP(t + 15))) {
+					const unsigned char *ie = ip + t;
+					unsigned char *oe = op + t;
+					do {
+						COPY8(op, ip);
+						op += 8;
+						ip += 8;
+						COPY8(op, ip);
+						op += 8;
+						ip += 8;
+					} while (ip < ie);
+					ip = ie;
+					op = oe;
+				} else {
+					NEED_OP(t);
+					NEED_IP(t + 3);
+					do {
+						*op++ = *ip++;
+					} while (--t > 0);
+				}
+				state = 4;
+				continue;
+			} else if (state != 4) {
+				next = t & 3;
+				m_pos = op - 1;
+				m_pos -= t >> 2;
+				m_pos -= *ip++ << 2;
+				TEST_LB(m_pos);
+				NEED_OP(2);
+				op[0] = m_pos[0];
+				op[1] = m_pos[1];
+				op += 2;
+				goto match_next;
+			} else {
+				next = t & 3;
+				m_pos = op - (1 + M2_MAX_OFFSET);
+				m_pos -= t >> 2;
+				m_pos -= *ip++ << 2;
+				t = 3;
+			}
+		} else if (t >= 64) {
+			next = t & 3;
+			m_pos = op - 1;
+			m_pos -= (t >> 2) & 7;
+			m_pos -= *ip++ << 3;
+			t = (t >> 5) - 1 + (3 - 1);
+		} else if (t >= 32) {
+			t = (t & 31) + (3 - 1);
+			if (t == 2) {
+				ulong offset;
+				const unsigned char *ip_last = ip;
+
+				while (*ip == 0) {
+					ip++;
+					NEED_IP(1);
+				}
+				offset = ip - ip_last;
+				if (offset > MAX_255_COUNT)
+					return LZO_E_ERROR;
+
+				offset = (offset << 8) - offset;
+				t += offset + 31 + *ip++;
+				NEED_IP(2);
+			}
+			m_pos = op - 1;
+
+			next = get_unaligned_le16(ip);
+			ip += 2;
+			m_pos -= next >> 2;
+			next &= 3;
+		} else {
+			NEED_IP(2);
+			next = get_unaligned_le16(ip);
+			if (((next & 0xfffc) == 0xfffc) &&
+			    ((t & 0xf8) == 0x18) &&
+			    bitstream_version) {
+				NEED_IP(3);
+				t &= 7;
+				t |= ip[2] << 3;
+				t += MIN_ZERO_RUN_LENGTH;
+				NEED_OP(t);
+				memset(op, 0, t);
+				op += t;
+				next &= 3;
+				ip += 3;
+				goto match_next;
+			} else {
+				m_pos = op;
+				m_pos -= (t & 8) << 11;
+				t = (t & 7) + (3 - 1);
+				if (t == 2) {
+					ulong offset;
+					const unsigned char *ip_last = ip;
+
+					while (*ip == 0) {
+						ip++;
+						NEED_IP(1);
+					}
+					offset = ip - ip_last;
+					if (offset > MAX_255_COUNT)
+						return LZO_E_ERROR;
+
+					offset = (offset << 8) - offset;
+					t += offset + 7 + *ip++;
+					NEED_IP(2);
+					next = get_unaligned_le16(ip);
+				}
+				ip += 2;
+				m_pos -= next >> 2;
+				next &= 3;
+				if (m_pos == op)
+					goto eof_found;
+				m_pos -= 0x4000;
+			}
+		}
+		TEST_LB(m_pos);
+
+		if (efficient_unaligned_access &&
+		    (op - m_pos >= 8)) {
+			unsigned char *oe = op + t;
+			if (HAVE_OP(t + 15)) {
+				do {
+					COPY8(op, m_pos);
+					op += 8;
+					m_pos += 8;
+					COPY8(op, m_pos);
+					op += 8;
+					m_pos += 8;
+				} while (op < oe);
+				op = oe;
+				if (HAVE_IP(6)) {
+					state = next;
+					COPY4(op, ip);
+					op += next;
+					ip += next;
+					continue;
+				}
+			} else {
+				NEED_OP(t);
+				do {
+					*op++ = *m_pos++;
+				} while (op < oe);
+			}
+		} else {
+			unsigned char *oe = op + t;
+			NEED_OP(t);
+			op[0] = m_pos[0];
+			op[1] = m_pos[1];
+			op += 2;
+			m_pos += 2;
+			do {
+				*op++ = *m_pos++;
+			} while (op < oe);
+		}
+match_next:
+		state = next;
+		t = next;
+		if (efficient_unaligned_access &&
+		    (HAVE_IP(6) && HAVE_OP(4))) {
+			COPY4(op, ip);
+			op += t;
+			ip += t;
+		} else {
+			NEED_IP(t + 3);
+			NEED_OP(t);
+			while (t > 0) {
+				*op++ = *ip++;
+				t--;
+			}
+		}
+	}
+
+eof_found:
+	*out_len = op - out;
+	return (t != 3       ? LZO_E_ERROR :
+		ip == ip_end ? LZO_E_OK :
+		ip <  ip_end ? LZO_E_INPUT_NOT_CONSUMED : LZO_E_INPUT_OVERRUN);
+
+input_overrun:
+	*out_len = op - out;
+	return LZO_E_INPUT_OVERRUN;
+
+output_overrun:
+	*out_len = op - out;
+	return LZO_E_OUTPUT_OVERRUN;
+
+lookbehind_overrun:
+	*out_len = op - out;
+	return LZO_E_LOOKBEHIND_OVERRUN;
+}
diff --git a/lzorle_decompress.h b/lzorle_decompress.h
new file mode 100644
index 0000000..8cdb3b1
--- /dev/null
+++ b/lzorle_decompress.h
@@ -0,0 +1,75 @@
+/* lzorle_decompress.h
+ *
+ * from kernel lib/lzo/lzodefs.h
+ *
+ * Copyright (C) 1996-2012 Markus F.X.J. Oberhumer <markus@oberhumer.com>
+ * Copyright (C) 2024 NIO
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef LZODEFS_H
+#define LZODEFS_H
+
+#define COPY4(dst, src) memcpy((dst), (src), sizeof(uint32_t))
+#define COPY8(dst, src) memcpy((dst), (src), sizeof(uint64_t))
+
+#define M1_MAX_OFFSET 0x0400
+#define M2_MAX_OFFSET 0x0800
+#define M3_MAX_OFFSET 0x4000
+#define M4_MAX_OFFSET_V0 0xbfff
+#define M4_MAX_OFFSET_V1 0xbffe
+
+#define M1_MIN_LEN 2
+#define M1_MAX_LEN 2
+#define M2_MIN_LEN 3
+#define M2_MAX_LEN 8
+#define M3_MIN_LEN 3
+#define M3_MAX_LEN 33
+#define M4_MIN_LEN 3
+#define M4_MAX_LEN 9
+
+#define M1_MARKER 0
+#define M2_MARKER 64
+#define M3_MARKER 32
+#define M4_MARKER 16
+
+#define MIN_ZERO_RUN_LENGTH 4
+#define MAX_ZERO_RUN_LENGTH (2047 + MIN_ZERO_RUN_LENGTH)
+
+#define lzo_dict_t unsigned short
+#define D_BITS 13
+#define D_SIZE (1u << D_BITS)
+#define D_MASK (D_SIZE - 1)
+#define D_HIGH ((D_MASK >> 1) + 1)
+
+#define LZO_E_OK 0
+#define LZO_E_ERROR (-1)
+#define LZO_E_OUT_OF_MEMORY (-2)
+#define LZO_E_NOT_COMPRESSIBLE (-3)
+#define LZO_E_INPUT_OVERRUN (-4)
+#define LZO_E_OUTPUT_OVERRUN (-5)
+#define LZO_E_LOOKBEHIND_OVERRUN (-6)
+#define LZO_E_EOF_NOT_FOUND (-7)
+#define LZO_E_INPUT_NOT_CONSUMED (-8)
+#define LZO_E_NOT_YET_IMPLEMELZO_HFILESNTED (-9)
+#define LZO_E_INVALID_ARGUMENT (-10)
+
+#define HAVE_IP(x)	((unsigned long)(ip_end - ip) >= (unsigned long)(x))
+#define HAVE_OP(x)	((unsigned long)(op_end - op) >= (unsigned long)(x))
+#define NEED_IP(x)	if (!HAVE_IP(x)) goto input_overrun
+#define NEED_OP(x)	if (!HAVE_OP(x)) goto output_overrun
+#define TEST_LB(m_pos)	if ((m_pos) < out) goto lookbehind_overrun
+
+int lzorle_decompress_safe(const unsigned char *in, unsigned long in_len,
+			  unsigned char *out, unsigned long *out_len, void *other/* NOT USED */);
+
+#endif
-- 
2.25.1

