diff --git a/config/Config-images.in b/config/Config-images.in index 47f3dfc0d9..411dc1c969 100644 --- a/config/Config-images.in +++ b/config/Config-images.in @@ -70,6 +70,25 @@ menu "Target Images" Generate separate initrd.cpio instead of embedding it. This is useful for generating images with a dedicated ramdisk e.g. in U-Boot's uImage and uImage.FIT formats. + menuconfig TARGET_SECURE_INITRAMFS + bool "pivoting initramfs" + help + Build a pivoting initramfs archive the kernel can start. + It will be used to set things up before a rootfs is started, + like authenticate a rootfs image. + + choice + prompt "Compression" + depends on TARGET_SECURE_INITRAMFS + help + Select initramfs compression mode. + + config TARGET_SECURE_INITRAMFS_COMPRESSION_NONE + bool "none" + + config TARGET_SECURE_INITRAMFS_COMPRESSION_GZIP + bool "gzip" + endchoice comment "Root filesystem archives" diff --git a/include/image-commands.mk b/include/image-commands.mk index a627ffc202..b6bb5b5f20 100644 --- a/include/image-commands.mk +++ b/include/image-commands.mk @@ -3,6 +3,10 @@ IMAGE_KERNEL = $(word 1,$^) IMAGE_ROOTFS = $(word 2,$^) +define get_initramfs_compression +$(firstword $(foreach compression,NONE GZIP BZIP2 LZMA LZO XZ LZ4 ZSTD,$(if $($1$(compression)),$(compression) ,))) +endef + define ModelNameLimit16 $(shell printf %.16s "$(word 2, $(subst _, ,$(1)))") endef diff --git a/include/image.mk b/include/image.mk index a42bec7977..800d8f033b 100644 --- a/include/image.mk +++ b/include/image.mk @@ -48,6 +48,7 @@ IMG_PREFIX_VERCODE:=$(if $(CONFIG_VERSION_CODE_FILENAMES),$(call sanitize,$(VERS IMG_PREFIX:=$(VERSION_DIST_SANITIZED)-$(IMG_PREFIX_VERNUM)$(IMG_PREFIX_VERCODE)$(IMG_PREFIX_EXTRA)$(BOARD)-$(SUBTARGET) IMG_ROOTFS:=$(IMG_PREFIX)-rootfs IMG_COMBINED:=$(IMG_PREFIX)-combined +IMG_SECURE_INITRAMFS:=$(IMG_PREFIX)-secure-initramfs$(shell $(TOPDIR)/scripts/gen-secure-initramfs.sh show-extension $(call get_initramfs_compression,CONFIG_TARGET_SECURE_INITRAMFS_COMPRESSION_)) ifeq ($(DUMP),) IMG_PART_SIGNATURE:=$(shell echo $(SOURCE_DATE_EPOCH)$(LINUX_VERMAGIC) | $(MKHASH) md5 | cut -b1-8) IMG_PART_DISKGUID:=$(shell echo $(SOURCE_DATE_EPOCH)$(LINUX_VERMAGIC) | $(MKHASH) md5 | sed -E 's/(.{8})(.{4})(.{4})(.{4})(.{10})../\1-\2-\3-\4-\500/') @@ -156,6 +157,15 @@ define prepare_generic_squashfs $(STAGING_DIR_HOST)/bin/padjffs2 $(1) 4 8 16 64 128 256 endef +define Image/SecureInitramfs + UNSTRIPPED_FOLDER=$(STAGING_DIR_ROOT) \ + BINARIES_PATH=$(CONFIG_TARGET_INIT_PATH) \ + $(TOPDIR)/scripts/gen-secure-initramfs.sh build \ + $(call get_initramfs_compression,CONFIG_TARGET_SECURE_INITRAMFS_COMPRESSION_) \ + $(BIN_DIR)/$(IMG_SECURE_INITRAMFS) \ + $(STAGING_DIR_IMAGE)/initramfs $(TARGET_DIR) +endef + define Image/BuildKernel/Initramfs $(call Image/Build/Initramfs) endef @@ -971,6 +981,7 @@ define BuildImage $(call Image/BuildKernel) $(if $(CONFIG_TARGET_ROOTFS_INITRAMFS),$(if $(IB),,$(call Image/BuildKernel/Initramfs))) $(call Image/InstallKernel) + $(if $(CONFIG_TARGET_SECURE_INITRAMFS),$(call Image/SecureInitramfs)) $(foreach device,$(TARGET_DEVICES),$(call Device,$(device))) diff --git a/include/kernel-defaults.mk b/include/kernel-defaults.mk index 372da1b085..0102be3e8f 100644 --- a/include/kernel-defaults.mk +++ b/include/kernel-defaults.mk @@ -45,6 +45,26 @@ else endef endif + +# Convenience function to only enable selected initramfs compression mode +# $1: compression variable prefix (e.g. "CONFIG_TARGET_INITRAMFS_COMPRESSION_") +# $2: output file +define Kernel/SetInitramfsCompressions + { \ + $(if $(CONFIG_TARGET_INITRAMFS_COMPRESSION_NONE), \ + echo "CONFIG_INITRAMFS_COMPRESSION_NONE=y" >> $(2);, \ + echo "# CONFIG_INITRAMFS_COMPRESSION_NONE is not set" >> $(2); ) \ + $(foreach ALGO,GZIP BZIP2 LZMA LZO XZ LZ4 ZSTD, \ + $(if $(CONFIG_TARGET_INITRAMFS_COMPRESSION_$(ALGO)), \ + echo "CONFIG_INITRAMFS_COMPRESSION_$(ALGO)=y" >> $(2); $\, \ + echo "# CONFIG_INITRAMFS_COMPRESSION_$(ALGO) is not set" >> $(2); $\) \ + $(if $(CONFIG_TARGET_INITRAMFS_COMPRESSION_$(ALGO)), \ + echo "CONFIG_RD_$(ALGO)=y" >> $(2); $\, \ + echo "# CONFIG_RD_$(ALGO) is not set" >> $(2); $\) \ + ) \ + } +endef + ifeq ($(CONFIG_TARGET_ROOTFS_INITRAMFS),y) ifeq ($(CONFIG_TARGET_ROOTFS_INITRAMFS_SEPARATE),y) define Kernel/SetInitramfs/PreConfigure @@ -86,28 +106,31 @@ ifeq ($(CONFIG_TARGET_ROOTFS_INITRAMFS),y) $(if $(CONFIG_TARGET_INITRAMFS_FORCE), \ echo "CONFIG_INITRAMFS_FORCE=y" >> $(2)/.config;, \ echo "# CONFIG_INITRAMFS_FORCE is not set" >> $(2)/.config;)) \ - $(if $(CONFIG_TARGET_INITRAMFS_COMPRESSION_NONE), \ - echo "CONFIG_INITRAMFS_COMPRESSION_NONE=y" >> $(2)/.config;, \ - echo "# CONFIG_INITRAMFS_COMPRESSION_NONE is not set" >> $(2)/.config; ) \ - $(foreach ALGO,GZIP BZIP2 LZMA LZO XZ LZ4 ZSTD, \ - $(if $(CONFIG_TARGET_INITRAMFS_COMPRESSION_$(ALGO)), \ - echo "CONFIG_INITRAMFS_COMPRESSION_$(ALGO)=y" >> $(2)/.config; $\, \ - echo "# CONFIG_INITRAMFS_COMPRESSION_$(ALGO) is not set" >> $(2)/.config; $\) \ - $(if $(CONFIG_TARGET_INITRAMFS_COMPRESSION_$(ALGO)), \ - echo "CONFIG_RD_$(ALGO)=y" >> $(2)/.config; $\, \ - echo "# CONFIG_RD_$(ALGO) is not set" >> $(2)/.config; $\) \ - ) \ + $(call Kernel/SetInitramfsCompressions,CONFIG_TARGET_INITRAMFS_COMPRESSION_,$(LINUX_DIR)/.config); \ } endef else endif +ifeq ($(CONFIG_TARGET_SECURE_INITRAMFS),y) + define Kernel/SetNoInitramfs/CustomSupportInitramfs + echo 'CONFIG_BLK_DEV_INITRD=y' >> $(LINUX_DIR)/.config.set + echo 'CONFIG_DECOMPRESS_GZIP=y' >> $(LINUX_DIR)/.config.set + echo 'CONFIG_RD_GZIP=y' >> $(LINUX_DIR)/.config.set + endef +else + define Kernel/SetNoInitramfs/CustomSupportInitramfs + endef +endif + + define Kernel/SetNoInitramfs mv $(LINUX_DIR)/.config.set $(LINUX_DIR)/.config.old grep -v INITRAMFS $(LINUX_DIR)/.config.old > $(LINUX_DIR)/.config.set echo 'CONFIG_INITRAMFS_SOURCE=""' >> $(LINUX_DIR)/.config.set echo '# CONFIG_INITRAMFS_FORCE is not set' >> $(LINUX_DIR)/.config.set echo "# CONFIG_INITRAMFS_PRESERVE_MTIME is not set" >> $(LINUX_DIR)/.config.set + $(call Kernel/SetNoInitramfs/CustomSupportInitramfs) endef define Kernel/Configure/Default diff --git a/scripts/gen-secure-initramfs.sh b/scripts/gen-secure-initramfs.sh new file mode 100755 index 0000000000..453657923e --- /dev/null +++ b/scripts/gen-secure-initramfs.sh @@ -0,0 +1,208 @@ +#!/bin/bash +# SPDX-License-Identifier: BSD-2-Clause-Patent +# +# SPDX-FileCopyrightText: Copyright (c) 2025 SoftAtHome +# + +TMP_FOLDER="initramfs.tmp" +MODES="help show-extension build" + + +function usage { + echo -e "Usage: +$0 MODE COMPRESSION [OUT_FILE] [IN_FOLDER] [ROOTFS_FOLDER]\n\ +\n\ +MODE should be one of $MODES.\n\ +COMPRESSION should be one of GZIP BZIP2 LZMA LZO XZ LZ4 ZSTD.\n\ +Additional parameters are only needed in 'build' mode + +'build' mode will also expect environment variables: + - UNSTRIPPED_FOLDER: a staging folder containing unstripped binaries for objdump + - BINARIES_PATH: the target PATH variable +" + exit $1 +} + +function help { + usage 0 +} + +function show-extension { + echo ".cpio$EXTENSION" +} + +function build { + OUT_FILE="$1" + IN_FOLDER="$2" + ROOTFS_FOLDER="$3" + [ -e `dirname $OUT_FILE` ] && [ -e "$IN_FOLDER" ] && [ -e "$ROOTFS_FOLDER" ] && [ -n "$UNSTRIPPED_FOLDER" ] && [ -n "$BINARIES_PATH" ] || usage 1 + INITRAMFS_BINARIES_SEARCH_PATH="$BINARIES_PATH:/lib:/lib64:/usr/lib:/usr/lib64" + + rm -rf "$TMP_FOLDER" + mkdir "$TMP_FOLDER" + + import_files_tree + import_files_list + package_final_file +} + +function parse_compression { + case "$1" in + NONE) + EXTENSION="" + COMPRESSOR="tee" + ;; + GZIP) + EXTENSION=".gz" + COMPRESSOR="gzip -c -" + ;; + BZIP2) + EXTENSION=".bz2" + COMPRESSOR="bzip2 -c -" + ;; + LZMA) + EXTENSION=".lzma" + COMPRESSOR="lzma -c -" + ;; + LZO) + EXTENSION=".lzo" + COMPRESSOR="lzop -c -" + ;; + XZ) + EXTENSION=".xz" + COMPRESSOR="xz -c -" + ;; + LZ4) + EXTENSION=".lz4" + COMPRESSOR="lz4 -c -" + ;; + ZSTD) + EXTENSION=".zstd" + COMPRESSOR="zstd -c -" + ;; + *) + usage 1;; + esac +} + +function import_files_tree { + cp -r $IN_FOLDER/initramfs-files/* $TMP_FOLDER/ +} + +# $1 is the file to find, it should be a binary/lib to search in path +# Only first hit is returned +function find_binary { + while read -r search_dir; do + if [ -e "$ROOTFS_FOLDER$search_dir/$1" ] || [ -L "$ROOTFS_FOLDER$search_dir/$1" ]; then + echo "$search_dir/$1" + return 0 + fi + done < <(echo "$INITRAMFS_BINARIES_SEARCH_PATH" | sed -r 's/:/\n/g') + echo "Error: could not find required binary $1" >&2 + exit 1 +} + + +function import_file_symlink { + import_file_other "$1" + target=`readlink "$ROOTFS_FOLDER$1"` + [[ "$target" == /* ]] || target=`dirname "$1"`/$target + import_file "$target" +} + +function import_file_binary { + import_file_other "$1" + deps=`objdump -p "$UNSTRIPPED_FOLDER$1" | grep NEEDED | sed -r 's/\s*NEEDED\s*//'` + [ -z "$deps" ] && return 0 + while read -r dependency; do + import_file `find_binary "$dependency"` + done < <(echo "$deps") +} + +function import_file_other { + mkdir -p `dirname $TMP_FOLDER$1` + cp -pPTr "$ROOTFS_FOLDER$1" "$TMP_FOLDER$1" +} + +function import_file { + [ -e "$TMP_FOLDER$1" ] && return 0 + + # Here it both checks the file exists, and expand any wildcard pattern, so we need the ls command + cd "$ROOTFS_FOLDER"; file=`ls --format=single-column ./$1 | head -n 1 | sed -E 's,\./,,'`; cd - > /dev/null + [ -z "$file" ] && echo "Could not find $1" >&2 && exit 1 + input_file="$ROOTFS_FOLDER$file" + + if [ -L "$input_file" ]; then + file_type=symlink + elif objdump -f "$input_file" > /dev/null; then + file_type=binary + else + file_type=other + fi + import_file_$file_type "$file" +} + +function find_optional_kernel_module { + local name="$1" + local relpath + + # Work under ROOTFS_FOLDER, search entire lib/modules tree + relpath=$(cd "$ROOTFS_FOLDER" && \ + find lib/modules -type f -name "$name" 2>/dev/null | head -n 1) + + if [ -z "$relpath" ]; then + echo "Optional kernel module $name not found, skipping" >&2 + return 1 + fi + + # Prepend leading slash to make it absolute relative to ROOTFS + echo "/$relpath" + return 0 +} + +function import_files_list { + while read -r file; do + # Skip empty lines and comments + [ -z "$file" ] && continue + [[ "$file" =~ ^# ]] && continue + + # Optional kernel module: syntax "(dm-mod.ko)" + if [[ "$file" =~ ^\((.+\.ko)\)$ ]]; then + local modname="${BASH_REMATCH[1]}" + local modpath + + if modpath=$(find_optional_kernel_module "$modname"); then + # Only import if found + import_file "$modpath" + fi + # Always continue: optional, so never fatal + continue + fi + + # Normal behavior for all other entries: + # - absolute path: use as-is + # - otherwise: search via find_binary + if [[ "$file" == /* ]]; then + import_file "$file" + else + file=$(find_binary "$file") + import_file "$file" + fi + done < <(cat "$IN_FOLDER/initramfs-files-list.txt"; \ + find "$ROOTFS_FOLDER/lib" -name 'ld-*' -printf '/lib/%P\n') +} + +function package_final_file { + cd $TMP_FOLDER + find . | cpio -o --quiet -H newc --owner "+0:+0" | $COMPRESSOR > "$OUT_FILE" + cd - > /dev/null +} + + +mode=$1 +echo "$MODES" | grep -wq "$mode" || usage 1 +shift +parse_compression "$1" +shift + +$mode $@