From b8ba3aecc3183cf41b9a7da6510e4488c5ce0ed4 Mon Sep 17 00:00:00 2001 From: mohamed-nour Toumi Date: Mon, 3 Feb 2025 10:15:27 +0100 Subject: [PATCH] image: add the implementation to support the secure initramfs The secure initramfs is used to load the rootfs based on information from the kernel command line, parse the FIT image encapsulating the rootfs and perform other operations such as authenticating the rootfs. Tracked-by: FEAT-29 Closes: PCF-1893 Signed-off-by: Mohamed-nour Toumi --- config/Config-images.in | 19 +++ include/image-commands.mk | 4 + include/image.mk | 11 ++ include/kernel-defaults.mk | 45 +++++-- scripts/gen-secure-initramfs.sh | 208 ++++++++++++++++++++++++++++++++ 5 files changed, 276 insertions(+), 11 deletions(-) create mode 100755 scripts/gen-secure-initramfs.sh 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 $@