From 025ce79bed1100e47e63d2577bd3b1d394cde58c Mon Sep 17 00:00:00 2001 From: Ben Hutchings Date: Sun, 16 Mar 2025 15:09:58 +0100 Subject: [PATCH 1/4] unmkinitramfs: Create temporary directory for initramfs parts Currently we extract the compressed archive from the initramfs to a temporary file, but in future we will need to extract multiple archives. Create a temporary directory at the top level of the script, and put the compressed archive inside that. Signed-off-by: Ben Hutchings --- unmkinitramfs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/unmkinitramfs b/unmkinitramfs index 2258e69..acc20d6 100755 --- a/unmkinitramfs +++ b/unmkinitramfs @@ -119,8 +119,7 @@ splitinitramfs() if [ "$end" -gt 0 ]; then # Extract to main subdirectory - subarchive=$(mktemp "${TMPDIR:-/var/tmp}/unmkinitramfs_XXXXXX") - trap 'rm -f "$subarchive"' EXIT + subarchive="$tempdir/main-comp.cpio" dd < "$initramfs" skip="$end" iflag=skip_bytes 2> /dev/null \ > "$subarchive" count=$((count + 1)) @@ -168,5 +167,8 @@ if [ $# -ne $expected_args ]; then usage_error fi +tempdir="$(mktemp -d "${TMPDIR:-/var/tmp}/unmkinitramfs_XXXXXX")" +trap 'rm -rf "$tempdir"' EXIT + # shellcheck disable=SC2086 splitinitramfs "$1" "${2:-}" $cpio_opts From 25b0c6164b1499ebe641b5dc5640012c3142a833 Mon Sep 17 00:00:00 2001 From: Ben Hutchings Date: Sun, 16 Mar 2025 16:55:25 +0100 Subject: [PATCH 2/4] unmkinitramfs: Fix detection of EOF marker We currently split the initramfs when we see zero padding after an file rather than a new file header. However, the real EOF marker in a cpio archive is a file header with the name "TRAILER!!!". Look for that first, then skip zero padding after it. Signed-off-by: Ben Hutchings --- unmkinitramfs | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/unmkinitramfs b/unmkinitramfs index acc20d6..a52b503 100755 --- a/unmkinitramfs +++ b/unmkinitramfs @@ -81,23 +81,27 @@ splitinitramfs() # very roughly based on linux/lib/earlycpio.c end=$start while true; do - if checkzero "$initramfs" $end; then - # This is the EOF marker. There might - # be more zero padding before the next - # archive, so read through all of it. - end=$((end + 4)) + magic="$(readhex "$initramfs" $end 6)" || break + test "$magic" = 070701 || test "$magic" = 070702 || break + namesize=$((0x$(readhex "$initramfs" $((end + 94)) 8))) + filesize=$((0x$(readhex "$initramfs" $((end + 54)) 8))) + nameoff=$((end + 110)) + end=$(((nameoff + namesize + 3) & ~3)) + end=$(((end + filesize + 3) & ~3)) + + # Check for EOF marker. Note that namesize + # includes a null terminator. + if [ $namesize = 11 ] \ + && name="$(dd if="$initramfs" bs=1 skip=$nameoff count=$((namesize - 1)) 2> /dev/null)" \ + && [ "$name" = 'TRAILER!!!' ]; then + # There might be more zero padding + # before the next archive, so read + # through all of it. while checkzero "$initramfs" $end; do end=$((end + 4)) done break fi - magic="$(readhex "$initramfs" $end 6)" || break - test "$magic" = 070701 || test "$magic" = 070702 || break - namesize=0x$(readhex "$initramfs" $((end + 94)) 8) - filesize=0x$(readhex "$initramfs" $((end + 54)) 8) - end=$((end + 110)) - end=$(((end + namesize + 3) & ~3)) - end=$(((end + filesize + 3) & ~3)) done if [ $end -eq $start ]; then break From ca1a68b2c97a4eb9f6c732f334218a0d08acf65a Mon Sep 17 00:00:00 2001 From: Ben Hutchings Date: Sun, 16 Mar 2025 17:39:06 +0100 Subject: [PATCH 3/4] unmkinitramfs: Restore split to "early" and "main" subdirectories unmkinitramfs used to assume that any uncompressed cpio archives at the beginning of an initramfs image belonged to the early initramfs and only a final compressed archive belonged to the the main initramfs. If it found any uncompressed archives it extracted them into "early", "early2", etc. subdirectories and the compressed archive into a "main" subdirectory. The reason for using a separate subdirectory for each archive is to guard against a symlink traversal attack from an untrusted initramfs, e.g. the extraction of "link" as a symlink to "/etc" followed by "link/shadow" which overwrites "/etc/shadow". cpio itself protects against this if we extract a single archive into an empty directory, but not if we extract multiple archives successively into the same directory. mkinitramfs now splits the main initramfs files between uncompressed and compressed archives. unmkinitramfs was changed to use subdirectory names "cpio1", "cpio2", etc. since the previous distinction was no longer valid. Several packages that integrate with initramfs-tools have autopkgtests that run unmkinitramfs and were broken by this new behaviour. It's also quite possible that there are also user scripts that would also be broken. Therefore, try to restore the old behaviour in unmkinitramfs: 1. Distinguish whether uncompressed archives are "early" or "main" by checking for a kernel/ subdirectory. Currently all filenames the kernel looks for in an early initramfs are in this subdirectory, but we should never create this in the main initramfs. 2. Extract early archives as before, but concatenate any "main" uncompressed archives to a temporary file. Exclude the trailer from them so that cpio won't stop early when reading them. 3. Pass both the "main" uncompressed archives and the compressed archive to xcpio, and make it concatenate the uncompressed and decompressed archives as input to cpio. The concatenation in steps 2 and 3 is done to preserve the protection against symlink traversal. Fixes: 81fd41f72dd8 ("Put compressed kernel modules and firmware in an uncompressed cpio") Fixes: cb0618177b26 ("unmkinitramfs: use directory names 'cpio1', 'cpio2', etc.") Closes: #1100008 Signed-off-by: Ben Hutchings --- unmkinitramfs | 109 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 75 insertions(+), 34 deletions(-) diff --git a/unmkinitramfs b/unmkinitramfs index a52b503..9bce38a 100755 --- a/unmkinitramfs +++ b/unmkinitramfs @@ -25,24 +25,28 @@ usage_error() # Extract a compressed cpio archive xcpio() { - archive="$1" - dir="$2" - shift 2 + archive_uncomp="$1" + archive="$2" + dir="$3" + shift 3 - if gzip -t "$archive" >/dev/null 2>&1 ; then - gzip -c -d "$archive" - elif zstd -q -c -t "$archive" >/dev/null 2>&1 ; then - zstd -q -c -d "$archive" - elif xzcat -t "$archive" >/dev/null 2>&1 ; then - xzcat "$archive" - elif lz4cat -t < "$archive" >/dev/null 2>&1 ; then - lz4cat "$archive" - elif bzip2 -t "$archive" >/dev/null 2>&1 ; then - bzip2 -c -d "$archive" - elif lzop -t "$archive" >/dev/null 2>&1 ; then - lzop -c -d "$archive" - # Ignoring other data, which may be garbage at the end of the file - fi | ( + { + cat "$archive_uncomp" + if gzip -t "$archive" >/dev/null 2>&1 ; then + gzip -c -d "$archive" + elif zstd -q -c -t "$archive" >/dev/null 2>&1 ; then + zstd -q -c -d "$archive" + elif xzcat -t "$archive" >/dev/null 2>&1 ; then + xzcat "$archive" + elif lz4cat -t < "$archive" >/dev/null 2>&1 ; then + lz4cat "$archive" + elif bzip2 -t "$archive" >/dev/null 2>&1 ; then + bzip2 -c -d "$archive" + elif lzop -t "$archive" >/dev/null 2>&1 ; then + lzop -c -d "$archive" + # Ignoring other data, which may be garbage at the end of the file + fi + } | ( if [ -n "$dir" ]; then mkdir -p -- "$dir" cd -- "$dir" @@ -65,13 +69,16 @@ checkzero() LANG=C grep -q -z '^$' } -# Split an initramfs into archives and call xcpio on each +# Split an initramfs into archives and run cpio/xcpio to extract them splitinitramfs() { initramfs="$1" dir="$2" shift 2 + # Ensure this exists so we can use it unconditionally later + touch "$tempdir/main-uncomp.cpio" + count=0 start=0 while true; do @@ -81,6 +88,7 @@ splitinitramfs() # very roughly based on linux/lib/earlycpio.c end=$start while true; do + headoff=$end magic="$(readhex "$initramfs" $end 6)" || break test "$magic" = 070701 || test "$magic" = 070702 || break namesize=$((0x$(readhex "$initramfs" $((end + 94)) 8))) @@ -103,36 +111,69 @@ splitinitramfs() break fi done + if [ $end -eq $start ]; then break fi - # Extract to cpio1, cpio2, ... subdirectories - count=$((count + 1)) - subdir=cpio$count - dd < "$initramfs" skip=$start count=$((end - start)) iflag=skip_bytes,count_bytes 2> /dev/null | - ( - if [ -n "$dir" ]; then - mkdir -p -- "$dir/$subdir" - cd -- "$dir/$subdir" + # Check whether this should be treated as an "early" + # or "main" initramfs. Currently all filenames the + # kernel looks for in an early initramfs begin with + # kernel/ subdirectory, but we should never create + # this in the main initramfs. + if dd < "$initramfs" skip=$start count=$((end - start)) \ + iflag=skip_bytes,count_bytes 2> /dev/null | + cpio -i --list 2> /dev/null | + grep -q ^kernel/; then + # Extract to early, early2, ... subdirectories + count=$((count + 1)) + if [ $count -eq 1 ]; then + subdir=early + else + subdir=early$count fi - cpio -i "$@" - ) + dd < "$initramfs" skip=$start count=$((end - start)) \ + iflag=skip_bytes,count_bytes 2> /dev/null | + ( + if [ -n "$dir" ]; then + mkdir -p -- "$dir/$subdir" + cd -- "$dir/$subdir" + fi + cpio -i "$@" + ) + else + # Append to main-uncomp.cpio, excluding the + # trailer so cpio won't stop before the + # (de)compressed part. + dd < "$initramfs" skip=$start \ + count=$((headoff - start)) \ + iflag=skip_bytes,count_bytes \ + >> "$tempdir/main-uncomp.cpio" 2> /dev/null + fi + start=$end done + # Split out final archive if necessary if [ "$end" -gt 0 ]; then - # Extract to main subdirectory subarchive="$tempdir/main-comp.cpio" dd < "$initramfs" skip="$end" iflag=skip_bytes 2> /dev/null \ > "$subarchive" - count=$((count + 1)) - subdir=cpio$count - xcpio "$subarchive" "${dir:+$dir/$subdir}" -i "$@" else - # Don't use subdirectories (for backward compatibility) - xcpio "$initramfs" "$dir" -i "$@" + subarchive="$initramfs" fi + + # If we found an early initramfs, extract main initramfs to + # main subdirectory. Otherwise don't use a subdirectory (for + # backward compatibility). + if [ "$count" -gt 0 ]; then + subdir=main + else + subdir=. + fi + + xcpio "$tempdir/main-uncomp.cpio" "$subarchive" \ + "${dir:+$dir/$subdir}" -i "$@" } OPTIONS=$(getopt -o hv --long help,list,verbose -n "$0" -- "$@") || usage_error From bbbe1d37b337387fe8e0d2e62ffb64a4e45a6d48 Mon Sep 17 00:00:00 2001 From: Ben Hutchings Date: Thu, 20 Mar 2025 02:40:42 +0100 Subject: [PATCH 4/4] unmkinitramfs.8: Update and expand description of multi-archive handling The current behaviour of creating sub-directories for multi-archive initramfs images was never documented. Document what we do now, with a note that we may stop creating sub-directories in future. Signed-off-by: Ben Hutchings --- unmkinitramfs.8 | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/unmkinitramfs.8 b/unmkinitramfs.8 index ae9252d..d0965af 100644 --- a/unmkinitramfs.8 +++ b/unmkinitramfs.8 @@ -1,4 +1,4 @@ -.TH UNMKINITRAMFS 8 "2016/12/15" "initramfs\-tools" "System Administration" +.TH UNMKINITRAMFS 8 "2025/03/20" "initramfs\-tools" "System Administration" .SH NAME unmkinitramfs \- extract content from an initramfs image @@ -14,10 +14,21 @@ unmkinitramfs \- extract content from an initramfs image The .B unmkinitramfs command extracts the content of a given initramfs image using -.BR cpio . -If the image contains multiple segments, each are passed to .B cpio -in order. +and the appropriate decompressor command. + +The initramfs image may be a single compressed cpio archive, or the +concatenation of any number of uncompressed cpio archives followed by +a compressed cpio archive. + +When the initramfs image includes one or more "early initramfs" +archives, that is uncompressed archives with microcode or other files +that the kernel uses directly, these are extracted to sub\-directories +named "\fBearly\fR", "\fBearly2\fR", etc., and the other archives are +extracted to a sub\-directory named "\fBmain\fR". + +In a future version of initramfs\-tools this behaviour may change so +that all archives are extracted directly into the given directory. .SH OPTIONS @@ -40,9 +51,8 @@ Extract initramfs content of current running kernel: .SH BUGS .BR unmkinitramfs -cannot deal with multiple-segmented initramfs images, except where an -early (uncompressed) initramfs with system firmware is prepended to -the regular compressed initramfs. +does not support initramfs images with more or less than one +compressed cpio archive. .SH AUTHOR The initramfs-tools are written by Maximilian Attems