mirror of
https://github.com/linuxboot/heads.git
synced 2024-12-18 20:47:55 +00:00
Blob jail: Add zstd-decompress, decompress more complex archives
Debian 12's initrd by default now consists of an uncompressed cpio archive containing microcode, followed by a zstd-compressed cpio archive. inject_firmware.sh only supported gzip-compressed cpio, so it could not extract /init from this archive. Add zstd-decompress to decompress zstd streams (uncompressed size is about 180 KB). Add unpack_initramfs.sh which is able to decompress uncompressed, gzip, or zstd archives, with multiple segments, much like the Linux kernel itself does. Use unpack_initramfs.sh to extract /init for blob jail. Don't compress the new archive segment containing firmware and the updated /init. Signed-off-by: Jonathon Hall <jonathon.hall@puri.sm>
This commit is contained in:
parent
3e6eac9ffd
commit
1bf8331ffb
1
Makefile
1
Makefile
@ -500,6 +500,7 @@ bin_modules-$(CONFIG_TPM2_TOOLS) += tpm2-tools
|
||||
bin_modules-$(CONFIG_BASH) += bash
|
||||
bin_modules-$(CONFIG_POWERPC_UTILS) += powerpc-utils
|
||||
bin_modules-$(CONFIG_IOPORT) += ioport
|
||||
bin_modules-$(CONFIG_ZSTD) += zstd
|
||||
|
||||
$(foreach m, $(bin_modules-y), \
|
||||
$(call map,initrd_bin_add,$(call bins,$m)) \
|
||||
|
@ -20,6 +20,8 @@ CONFIG_PCIUTILS=y
|
||||
CONFIG_POPT=y
|
||||
CONFIG_QRENCODE=y
|
||||
CONFIG_TPMTOTP=y
|
||||
# zstd-decompress - for blob jail, needed to extract /init from zstd cpio archive
|
||||
CONFIG_ZSTD=y
|
||||
|
||||
CONFIG_CAIRO=y
|
||||
CONFIG_FBWHIPTAIL=y
|
||||
|
@ -36,7 +36,8 @@ ORIG_INITRD="$1"
|
||||
INITRD_ROOT="/tmp/inject_firmware_initrd_root"
|
||||
rm -rf "$INITRD_ROOT" || true
|
||||
mkdir "$INITRD_ROOT"
|
||||
gunzip <"$ORIG_INITRD" | (cd "$INITRD_ROOT"; cpio -i init 2>/dev/null)
|
||||
# Unpack just 'init' from the original initrd
|
||||
unpack_initramfs.sh "$ORIG_INITRD" "$INITRD_ROOT" init
|
||||
|
||||
# Copy the firmware into the initrd
|
||||
for f in $(cbfs -l | grep firmware); do
|
||||
@ -50,6 +51,7 @@ done
|
||||
# awk will happily pass through a binary file, so look for the match we want
|
||||
# before modifying init to ensure it's a shell script and not an ELF, etc.
|
||||
if ! grep -E -q '^exec run-init .*\$\{rootmnt\}' "$INITRD_ROOT/init"; then
|
||||
WARN "Can't apply firmware blob jail, unknown init script"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@ -79,11 +81,14 @@ awk -e "$AWK_INSERT_CP" "$INITRD_ROOT/init" >"$INITRD_ROOT/init_fw"
|
||||
mv "$INITRD_ROOT/init_fw" "$INITRD_ROOT/init"
|
||||
chmod a+x "$INITRD_ROOT/init"
|
||||
|
||||
# Pad the original initrd to 512 byte blocks, the last gzip blob is often not
|
||||
# padded. (If it is not gzip-compressed, we would already have failed above.)
|
||||
# Pad the original initrd to 512 byte blocks. Uncompressed cpio contents must
|
||||
# be 4-byte aligned, and anecdotally gzip frames might not be padded by dracut.
|
||||
# Linux ignores zeros between archive segments, so any extra padding is not
|
||||
# harmful.
|
||||
FW_INITRD="/tmp/inject_firmware_initrd.cpio.gz"
|
||||
dd if="$ORIG_INITRD" of="$FW_INITRD" bs=512 conv=sync status=none
|
||||
# Pack up the new contents and append to the initrd
|
||||
(cd "$INITRD_ROOT"; find . | cpio -o -H newc) | gzip >>"$FW_INITRD"
|
||||
# Pack up the new contents and append to the initrd. Don't spend time
|
||||
# compressing this.
|
||||
(cd "$INITRD_ROOT"; find . | cpio -o -H newc) >>"$FW_INITRD"
|
||||
# Use this initrd
|
||||
echo "$FW_INITRD"
|
||||
|
110
initrd/bin/unpack_initramfs.sh
Executable file
110
initrd/bin/unpack_initramfs.sh
Executable file
@ -0,0 +1,110 @@
|
||||
#! /bin/bash
|
||||
set -e -o pipefail
|
||||
|
||||
. /etc/functions
|
||||
|
||||
# Unpack a Linux initramfs archive.
|
||||
#
|
||||
# In general, the initramfs archive is one or more cpio archives, optionally
|
||||
# compressed, concatenated together. Uncompressed and compressed segments can
|
||||
# exist in the same file. Zero bytes between segments are skipped. To properly
|
||||
# unpack such an archive, all segments must be unpacked.
|
||||
#
|
||||
# This script unpacks such an archive, but with a limitation that once a
|
||||
# compressed segment is reached, no more segments can be read. This works for
|
||||
# common initrds on x86, where the microcode must be stored in an initial
|
||||
# uncompressed segment, followed by the "real" initramfs content which is
|
||||
# usually in one compressed segment.
|
||||
#
|
||||
# The limitation comes from gunzip/unzstd, there's no way to prevent them from
|
||||
# consuming trailing data or tell us the member/frame length. The script
|
||||
# succeeds with whatever was extracted, since this is used to extract particular
|
||||
# files and boot can proceed as long as those files were found.
|
||||
|
||||
INITRAMFS_ARCHIVE="$1"
|
||||
DEST_DIR="$2"
|
||||
shift
|
||||
shift
|
||||
# rest of args go to cpio, can specify filename patterns
|
||||
CPIO_ARGS=("$@")
|
||||
|
||||
# Consume zero bytes, the first nonzero byte read (if any) is repeated on stdout
|
||||
consume_zeros() {
|
||||
next_byte='00'
|
||||
while [ "$next_byte" = "00" ]; do
|
||||
# if we reach EOF, next_byte becomes empty (dd does not fail)
|
||||
next_byte="$(dd bs=1 count=1 status=none | xxd -p | tr -d ' ')"
|
||||
done
|
||||
# if we finished due to nonzero byte (not EOF), then carry that byte
|
||||
if [ -n "$next_byte" ]; then
|
||||
echo -n "$next_byte" | xxd -p -r
|
||||
fi
|
||||
}
|
||||
|
||||
unpack_cpio() {
|
||||
(cd "$dest_dir"; cpio -i "${CPIO_ARGS[@]}" 2>/dev/null)
|
||||
}
|
||||
|
||||
# unpack the first segment of an archive, then write the rest to another file
|
||||
unpack_first_segment() {
|
||||
unpack_archive="$1"
|
||||
dest_dir="$2"
|
||||
rest_archive="$3"
|
||||
|
||||
mkdir -p "$dest_dir"
|
||||
|
||||
# peek the beginning of the file to determine what type of content is next
|
||||
magic="$(dd if="$unpack_archive" bs=6 count=1 status=none | xxd -p)"
|
||||
|
||||
# read this segment of the archive, then write the rest to the next file
|
||||
(
|
||||
# Magic values correspond to Linux init/initramfs.c (zero, cpio) and
|
||||
# lib/decompress.c (gzip)
|
||||
case "$magic" in
|
||||
00*)
|
||||
# Skip zero bytes and copy the first nonzero byte
|
||||
consume_zeros
|
||||
# Copy the remaining data
|
||||
cat
|
||||
;;
|
||||
303730373031*|303730373032*) # plain cpio
|
||||
# Unpack the plain cpio, this stops reading after the trailer
|
||||
unpack_cpio
|
||||
# Copy the remaining data
|
||||
cat
|
||||
;;
|
||||
1f8b*|1f9e*) # gzip
|
||||
# gunzip won't stop when reaching the end of the gzipped member,
|
||||
# so we can't read another segment after this. We can't
|
||||
# reasonably determine the member length either, this requires
|
||||
# walking all the compressed blocks.
|
||||
gunzip | unpack_cpio
|
||||
;;
|
||||
28b5*) # zstd
|
||||
# Like gunzip, this will not stop when reaching the end of the
|
||||
# frame, and determining the frame length requires walking all
|
||||
# of its blocks.
|
||||
(zstd-decompress -d || true) | unpack_cpio
|
||||
;;
|
||||
*) # unknown
|
||||
die "Can't decompress initramfs archive, unknown type: $magic"
|
||||
;;
|
||||
esac
|
||||
) <"$unpack_archive" >"$rest_archive"
|
||||
|
||||
orig_size="$(stat -c %s "$unpack_archive")"
|
||||
rest_size="$(stat -c %s "$rest_archive")"
|
||||
DEBUG "archive segment $magic: $((orig_size - rest_size)) bytes"
|
||||
}
|
||||
|
||||
DEBUG "Unpacking $INITRAMFS_ARCHIVE to $DEST_DIR"
|
||||
|
||||
next_archive="$INITRAMFS_ARCHIVE"
|
||||
rest_archive="/tmp/unpack_initramfs_rest"
|
||||
|
||||
# Break when there is no remaining data
|
||||
while [ -s "$next_archive" ]; do
|
||||
unpack_first_segment "$next_archive" "$DEST_DIR" "$rest_archive"
|
||||
next_archive="/tmp/unpack_initramfs_next"
|
||||
mv "$rest_archive" "$next_archive"
|
||||
done
|
20
modules/zstd
Normal file
20
modules/zstd
Normal file
@ -0,0 +1,20 @@
|
||||
modules-$(CONFIG_ZSTD) += zstd
|
||||
|
||||
zstd_version := 1.5.5
|
||||
zstd_dir := zstd-$(zstd_version)
|
||||
zstd_tar := zstd-$(zstd_version).tar.gz
|
||||
zstd_url := https://github.com/facebook/zstd/releases/download/v$(zstd_version)/$(zstd_tar)
|
||||
zstd_hash := 9c4396cc829cfae319a6e2615202e82aad41372073482fce286fac78646d3ee4
|
||||
|
||||
zstd_configure := true
|
||||
|
||||
# Only the decompressor is built and installed, to be able to read zstd-compressed
|
||||
# initramfs archives.
|
||||
zstd_target := \
|
||||
$(MAKE_JOBS) $(CROSS_TOOLS) -C programs CFLAGS="-g0 -Os" \
|
||||
HAVE_ZLIB=0 \
|
||||
HAVE_LZMA=0 \
|
||||
HAVE_LZ4=0 \
|
||||
zstd-decompress
|
||||
|
||||
zstd_output := programs/zstd-decompress
|
Loading…
Reference in New Issue
Block a user