Ctrl K
ComunidadGuías…

scripts/firecracker-menual/extract-arm64-image

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/bin/sh
# ----------------------------------------------------------------------
# extract-arm64-image - Extract a raw Linux/arm64 "Image" from a distro vmlinuz
#
# Firecracker's aarch64 loader (rust-vmm linux-loader) boots exactly one
# kernel format: a raw Linux/arm64 Image. It checks ARM64_IMAGE_MAGIC
# ("ARMd", 4 bytes at offset 0x38, per Documentation/arch/arm64/booting.rst)
# and then hands the guest CPU the start of the file. A distro's
# /boot/vmlinuz on arm64 is usually *not* that file. It comes in one of
# three shapes:
#
#   raw Image   "ARMd" already at 0x38. Passed through untouched.
#
#   EFI zboot   "MZ" at 0x00 and "zimg" at 0x04 (CONFIG_EFI_ZBOOT, Linux
#               6.1+). A PE application wrapping a *compressed* Image plus a
#               decompressor stub that runs under EFI. Header layout, from
#               drivers/firmware/efi/libstub/zboot-header.S: payload offset
#               u32 @0x08, payload size u32 @0x0c, NUL-terminated compression
#               name @0x18, and LINUX_PE_MAGIC (0x818223cd) — not "ARMd" —
#               at 0x38. Shipped by Alpine (gzip) and Rocky (gzip).
#
#   UKI         A PE carrying the kernel in a ".linux" section, next to
#               .dtbauto/.uname/.sbat (systemd-stub layout). Ubuntu 26.04
#               ships its arm64 vmlinuz this way, and the .linux payload is
#               itself an EFI zboot image (zstd), so it unwraps twice.
#
# Only the first shape boots under Firecracker. Writing "ARMd" over offset
# 0x38 of either of the other two makes the loader *accept* the file and then
# jump into an EFI stub that never receives EFI boot services: the guest dies
# before it emits a single console byte, and the only symptom upstack is a
# VM that boots to silence. So unwrap the real Image instead, and verify the
# magic is genuinely present rather than stamping it on.
#
# Usage: extract-arm64-image <vmlinuz>   # raw Image on stdout
# ----------------------------------------------------------------------

set -u

me=${0##*/}

# ARM64_IMAGE_MAGIC ("ARMd") and the "zimg"/"MZ" tags, as hex, so that the
# comparisons never carry NUL or non-printable bytes through a shell variable.
arm64_image_magic=41524d64
zboot_magic=7a696d67
mz_magic=4d5a
pe_magic=50450000

arm64_image_magic_offset=56 # 0x38

fail() {
	echo "${me}: $1" >&2
	exit 1
}

# Hex of $3 bytes at offset $2 of file $1, lowercase and unseparated. Reading
# past the end of a file that is too short to hold the field yields an empty
# string, which never equals a magic the callers look for.
hex_at() {
	od -An -tx1 -j "$2" -N "$3" "$1" 2>/dev/null | tr -d ' \n'
}

# Little-endian u16 at offset $2 of file $1, as a decimal. Assembled from
# individual bytes so the result does not depend on the host's endianness.
# A field that runs past the end of the file reads as 0, which every caller
# already has to treat as a malformed header.
u16_at() {
	_f=$1
	_o=$2
	# shellcheck disable=SC2046 # word splitting is how the bytes arrive
	set -- $(od -An -tu1 -j "$_o" -N 2 "$_f" 2>/dev/null)
	if [ "$#" -lt 2 ]; then
		echo 0
		return
	fi
	echo $(( $2 * 256 + $1 ))
}

# Little-endian u32 at offset $2 of file $1, as a decimal.
u32_at() {
	_f=$1
	_o=$2
	# shellcheck disable=SC2046 # word splitting is how the bytes arrive
	set -- $(od -An -tu1 -j "$_o" -N 4 "$_f" 2>/dev/null)
	if [ "$#" -lt 4 ]; then
		echo 0
		return
	fi
	echo $(( $4 * 16777216 + $3 * 65536 + $2 * 256 + $1 ))
}

# Writes $3 bytes from offset $2 of file $1 to stdout.
slice() {
	tail -c "+$(( $2 + 1 ))" "$1" | head -c "$3"
}

is_arm64_image() {
	[ -s "$1" ] && [ "$(hex_at "$1" "$arm64_image_magic_offset" 4)" = "$arm64_image_magic" ]
}

# Decompresses stdin using the named algorithm. The decompressor's own exit
# status is deliberately ignored by the callers: EFI zboot's declared payload
# size excludes gzip's 4-byte trailer (ZBOOT_SIZE_LEN), so gzip reports
# "unexpected end of file" after having already written every byte of the
# kernel. The extracted Image is validated on its own merits instead, the same
# way extract-vmlinux validates its output rather than trusting exit codes.
decompress_stream() {
	case $1 in
		gzip) gzip -dc 2>/dev/null ;;
		lzma) xz -dc --format=lzma 2>/dev/null ;;
		xz) xz -dc 2>/dev/null ;;
		lz4) lz4 -dc 2>/dev/null ;;
		lzo) lzop -dc 2>/dev/null ;;
		zstd) zstd -dc 2>/dev/null ;;
		*) return 1 ;;
	esac
}

# EFI zboot: decompress the payload the header points at.
unwrap_zboot() {
	src=$1
	dest=$2
	payload_offset=$(u32_at "$src" 8)
	compression=$(od -An -c -j 24 -N 8 "$src" | tr -d ' \n' | sed 's/\\0.*$//')
	if [ -z "$payload_offset" ] || [ "$payload_offset" -le 0 ]; then
		fail "EFI zboot header in ${source_image} declares no payload offset"
	fi
	case $compression in
		gzip | lzma | xz | lz4 | lzo | zstd) ;;
		*) fail "unsupported EFI zboot compression '${compression}' in ${source_image}" ;;
	esac
	echo "${me}: EFI zboot payload at offset ${payload_offset}, ${compression}-compressed" >&2
	# Fed to the end of the file rather than the declared payload size: the
	# decompressor stops at the end of its own stream, and the declared size
	# is short by the trailer for some algorithms (see decompress_stream).
	tail -c "+$(( payload_offset + 1 ))" "$src" | decompress_stream "$compression" >"$dest"
}

# UKI: lift out the ".linux" PE section holding the kernel.
unwrap_uki() {
	src=$1
	dest=$2
	pe_offset=$(u32_at "$src" 60) # 0x3c
	if [ "$(hex_at "$src" "$pe_offset" 4)" != "$pe_magic" ]; then
		fail "${source_image} is an MZ file with no PE header and no EFI zboot payload"
	fi
	section_count=$(u16_at "$src" $(( pe_offset + 6 )))
	optional_header_size=$(u16_at "$src" $(( pe_offset + 20 )))
	section_table=$(( pe_offset + 24 + optional_header_size ))
	index=0
	while [ "$index" -lt "$section_count" ]; do
		entry=$(( section_table + index * 40 ))
		index=$(( index + 1 ))
		# ".linux", NUL-padded to the 8-byte PE section name field.
		[ "$(hex_at "$src" "$entry" 8)" = 2e6c696e75780000 ] || continue
		virtual_size=$(u32_at "$src" $(( entry + 8 )))
		raw_size=$(u32_at "$src" $(( entry + 16 )))
		raw_offset=$(u32_at "$src" $(( entry + 20 )))
		# SizeOfRawData is padded up to the file alignment; VirtualSize is
		# the payload's real length, but never trust it past the data that
		# is actually in the file.
		size=$raw_size
		if [ "$virtual_size" -gt 0 ] && [ "$virtual_size" -lt "$raw_size" ]; then
			size=$virtual_size
		fi
		if [ "$size" -le 0 ]; then
			fail "${source_image} has an empty .linux section"
		fi
		echo "${me}: UKI .linux section at offset ${raw_offset}, ${size} bytes" >&2
		slice "$src" "$raw_offset" "$size" >"$dest"
		return 0
	done
	fail "${source_image} is a PE image with neither an EFI zboot payload nor a .linux section"
}

# A bare Image.gz and friends: no PE wrapper, just a compressed stream.
unwrap_compressed_image() {
	src=$1
	dest=$2
	for compression in gzip zstd xz lzma lz4 lzo; do
		# A missing decompressor just yields no output, same as a stream that
		# was not compressed that way — both mean "try the next one".
		decompress_stream "$compression" <"$src" >"$dest"
		if is_arm64_image "$dest"; then
			echo "${me}: bare ${compression}-compressed Image" >&2
			return 0
		fi
	done
	fail "${source_image} is neither a raw ARM64 Image, an EFI zboot image, a UKI, nor a compressed Image"
}

if [ "$#" -ne 1 ] || [ ! -s "$1" ]; then
	echo "Usage: ${me} <vmlinuz>" >&2
	exit 2
fi

source_image=$1
work_dir=$(mktemp -d "${TMPDIR:-/tmp}/extract-arm64-image-XXXXXX") || exit 1
trap 'rm -rf "$work_dir"' 0 INT HUP TERM

current=$source_image
# Ubuntu needs two (UKI then zboot); the bound just stops a malformed image
# from looping. No real kernel nests deeper than that.
depth=0
while [ "$depth" -lt 4 ]; do
	# Every shape below carries a 64-byte header, so anything shorter cannot
	# be a kernel in any of them — say so rather than reading past the end.
	if [ "$(wc -c <"$current")" -lt 64 ]; then
		fail "${source_image} is too small to hold an ARM64 kernel header"
	fi
	if is_arm64_image "$current"; then
		cat "$current"
		[ "$depth" -eq 0 ] && echo "${me}: already a raw ARM64 Image" >&2
		exit 0
	fi
	next="${work_dir}/stage${depth}"
	depth=$(( depth + 1 ))
	if [ "$(hex_at "$current" 0 2)" = "$mz_magic" ]; then
		if [ "$(hex_at "$current" 4 4)" = "$zboot_magic" ]; then
			unwrap_zboot "$current" "$next"
		else
			unwrap_uki "$current" "$next"
		fi
	else
		unwrap_compressed_image "$current" "$next"
	fi
	[ -s "$next" ] || fail "extracting the kernel from ${source_image} produced no data"
	current=$next
done

fail "could not reach a raw ARM64 Image in ${source_image} after ${depth} unwrapping passes"