| From faf15753e1e0256b9e38f44236c48906f3c94b90 Mon Sep 17 00:00:00 2001 |
| From: Sasha Levin <sashal@kernel.org> |
| Date: Wed, 30 Oct 2024 03:55:10 +0000 |
| Subject: initramfs: avoid filename buffer overrun |
| |
| From: David Disseldorp <ddiss@suse.de> |
| |
| [ Upstream commit e017671f534dd3f568db9e47b0583e853d2da9b5 ] |
| |
| The initramfs filename field is defined in |
| Documentation/driver-api/early-userspace/buffer-format.rst as: |
| |
| 37 cpio_file := ALGN(4) + cpio_header + filename + "\0" + ALGN(4) + data |
| ... |
| 55 ============= ================== ========================= |
| 56 Field name Field size Meaning |
| 57 ============= ================== ========================= |
| ... |
| 70 c_namesize 8 bytes Length of filename, including final \0 |
| |
| When extracting an initramfs cpio archive, the kernel's do_name() path |
| handler assumes a zero-terminated path at @collected, passing it |
| directly to filp_open() / init_mkdir() / init_mknod(). |
| |
| If a specially crafted cpio entry carries a non-zero-terminated filename |
| and is followed by uninitialized memory, then a file may be created with |
| trailing characters that represent the uninitialized memory. The ability |
| to create an initramfs entry would imply already having full control of |
| the system, so the buffer overrun shouldn't be considered a security |
| vulnerability. |
| |
| Append the output of the following bash script to an existing initramfs |
| and observe any created /initramfs_test_fname_overrunAA* path. E.g. |
| ./reproducer.sh | gzip >> /myinitramfs |
| |
| It's easiest to observe non-zero uninitialized memory when the output is |
| gzipped, as it'll overflow the heap allocated @out_buf in __gunzip(), |
| rather than the initrd_start+initrd_size block. |
| |
| ---- reproducer.sh ---- |
| nilchar="A" # change to "\0" to properly zero terminate / pad |
| magic="070701" |
| ino=1 |
| mode=$(( 0100777 )) |
| uid=0 |
| gid=0 |
| nlink=1 |
| mtime=1 |
| filesize=0 |
| devmajor=0 |
| devminor=1 |
| rdevmajor=0 |
| rdevminor=0 |
| csum=0 |
| fname="initramfs_test_fname_overrun" |
| namelen=$(( ${#fname} + 1 )) # plus one to account for terminator |
| |
| printf "%s%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%s" \ |
| $magic $ino $mode $uid $gid $nlink $mtime $filesize \ |
| $devmajor $devminor $rdevmajor $rdevminor $namelen $csum $fname |
| |
| termpadlen=$(( 1 + ((4 - ((110 + $namelen) & 3)) % 4) )) |
| printf "%.s${nilchar}" $(seq 1 $termpadlen) |
| ---- reproducer.sh ---- |
| |
| Symlink filename fields handled in do_symlink() won't overrun past the |
| data segment, due to the explicit zero-termination of the symlink |
| target. |
| |
| Fix filename buffer overrun by aborting the initramfs FSM if any cpio |
| entry doesn't carry a zero-terminator at the expected (name_len - 1) |
| offset. |
| |
| Fixes: 1da177e4c3f41 ("Linux-2.6.12-rc2") |
| Signed-off-by: David Disseldorp <ddiss@suse.de> |
| Link: https://lore.kernel.org/r/20241030035509.20194-2-ddiss@suse.de |
| Signed-off-by: Christian Brauner <brauner@kernel.org> |
| Signed-off-by: Sasha Levin <sashal@kernel.org> |
| --- |
| init/initramfs.c | 15 +++++++++++++++ |
| 1 file changed, 15 insertions(+) |
| |
| diff --git a/init/initramfs.c b/init/initramfs.c |
| index bc911e466d5bb..b2f7583bb1f5c 100644 |
| --- a/init/initramfs.c |
| +++ b/init/initramfs.c |
| @@ -360,6 +360,15 @@ static int __init do_name(void) |
| { |
| state = SkipIt; |
| next_state = Reset; |
| + |
| + /* name_len > 0 && name_len <= PATH_MAX checked in do_header */ |
| + if (collected[name_len - 1] != '\0') { |
| + pr_err("initramfs name without nulterm: %.*s\n", |
| + (int)name_len, collected); |
| + error("malformed archive"); |
| + return 1; |
| + } |
| + |
| if (strcmp(collected, "TRAILER!!!") == 0) { |
| free_hash(); |
| return 0; |
| @@ -424,6 +433,12 @@ static int __init do_copy(void) |
| |
| static int __init do_symlink(void) |
| { |
| + if (collected[name_len - 1] != '\0') { |
| + pr_err("initramfs symlink without nulterm: %.*s\n", |
| + (int)name_len, collected); |
| + error("malformed archive"); |
| + return 1; |
| + } |
| collected[N_ALIGN(name_len) + body_len] = '\0'; |
| clean_path(collected, 0); |
| init_symlink(collected + N_ALIGN(name_len), collected); |
| -- |
| 2.43.0 |
| |