From 3aa5c38714e6a690c0c2f549558a5b40506a7f83 Mon Sep 17 00:00:00 2001 From: Finn Callies Date: Tue, 17 Mar 2026 14:47:58 +0100 Subject: [PATCH] ebc: Add ibm-sel-ebc dracut module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add 95ibm-sel-ebc dracut module for secure boot-time customization of SEL guests. Introduce the IBM SEL EBC dracut module (95ibm-sel-ebc) that enables Early Boot Customization for SEL guests during the initramfs phase. The module implements a critical security architecture to prevent injection attacks: all EBC resources (.asr and .pol files) are copied from /boot/sics (which resides in the qcow2 image on the host filesystem) to /run/ibm-sel-ebc (a tmpfs/RAM-backed directory). Since guest RAM is protected by the Ultravisor, this prevents malicious hosts from modifying EBC resources during boot. Systemd units and their purposes: - ibm-sel-ebc.target: Groups all EBC-related units - boot.mount: Mounts /dev/disk/by-label/boot to /boot - ibm-sel-ebc-ensure-sics.service: Fallback to create /boot/sics/ if boot partition mount fails (supports Kata VM scenarios) - ibm-sel-ebc-pvebc.service: Main unit that copies EBC resources to RAM, invokes pvebc tool to verify integrity and add ASRs to UV, retrieves LUKS passphrase from UV secret store - ibm-sel-ebc-override-crypttab.service: Replaces /etc/crypttab with prepared IBM SEL EBC crypttab, reloads systemd daemon, starts cryptsetup service - ibm-sel-ebc-paes-enforce.service: Verifies root filesystem uses PAES encryption to prevent root filesystem substitution attacks All units write logs to /boot/sics/log for debugging, accessible even if root filesystem fails to mount. Units are triggered by rd.ibm-sel-ebc kernel parameter and only execute in initramfs (ConditionPathExists=/etc/initrd-release). Assisted-by: IBM Bob:1.0.1 Reviewed-by: Holger Dengler Signed-off-by: Finn Callies Signed-off-by: Jan Höppner --- rust/Makefile | 20 +++++ rust/pvebc/95sel-ebc/boot.mount | 28 +++++++ rust/pvebc/95sel-ebc/module-setup.sh | 76 +++++++++++++++++++ rust/pvebc/95sel-ebc/override-crypttab.sh | 20 +++++ rust/pvebc/95sel-ebc/pvebc-wrapper.sh | 63 +++++++++++++++ .../sel-ebc-override-crypttab.service | 37 +++++++++ .../95sel-ebc/sel-ebc-paes-enforce.service | 27 +++++++ rust/pvebc/95sel-ebc/sel-ebc-pvebc.service | 36 +++++++++ rust/pvebc/95sel-ebc/sel-ebc.crypttab | 1 + rust/pvebc/95sel-ebc/sel-ebc.target | 27 +++++++ 10 files changed, 335 insertions(+) create mode 100644 rust/pvebc/95sel-ebc/boot.mount create mode 100644 rust/pvebc/95sel-ebc/module-setup.sh create mode 100644 rust/pvebc/95sel-ebc/override-crypttab.sh create mode 100644 rust/pvebc/95sel-ebc/pvebc-wrapper.sh create mode 100644 rust/pvebc/95sel-ebc/sel-ebc-override-crypttab.service create mode 100644 rust/pvebc/95sel-ebc/sel-ebc-paes-enforce.service create mode 100644 rust/pvebc/95sel-ebc/sel-ebc-pvebc.service create mode 100644 rust/pvebc/95sel-ebc/sel-ebc.crypttab create mode 100644 rust/pvebc/95sel-ebc/sel-ebc.target diff --git a/rust/Makefile b/rust/Makefile index a399df4f..87172eae 100644 --- a/rust/Makefile +++ b/rust/Makefile @@ -10,6 +10,8 @@ CARGO_TARGETS := PV_TARGETS := CARGO_TEST_TARGETS := +SEL_EBC_MODDIR := 95sel-ebc + ifneq (${HAVE_CARGO},0) CARGO_TARGETS := pvebc @@ -29,6 +31,7 @@ ifneq (${HAVE_LIBCURL},0) ifeq ($(HOST_ARCH),s390x) PV_TARGETS += pvapconfig pvinfo + INSTALL_TARGETS += install-ebc-dracut-module else BUILD_TARGETS += skip-pvapconfig skip-pvinfo endif #HOSTARCH @@ -104,6 +107,23 @@ endif # CARGO rust-test: $(CARGO_TEST_TARGETS) +install-ebc-dracut-module: +ifneq ($(HAVE_DRACUT),0) + $(INSTALL) -m 755 -d $(DESTDIR)$(DRACUTMODDIR)/ + $(INSTALL) -m 755 -d $(DESTDIR)$(DRACUTMODDIR)/$(SEL_EBC_MODDIR) + $(INSTALL) -m 755 pvebc/$(SEL_EBC_MODDIR)/module-setup.sh \ + pvebc/$(SEL_EBC_MODDIR)/override-crypttab.sh \ + pvebc/$(SEL_EBC_MODDIR)/pvebc-wrapper.sh \ + $(DESTDIR)$(DRACUTMODDIR)/$(SEL_EBC_MODDIR) + $(INSTALL) -m 644 pvebc/$(SEL_EBC_MODDIR)/boot.mount \ + pvebc/$(SEL_EBC_MODDIR)/sel-ebc-override-crypttab.service \ + pvebc/$(SEL_EBC_MODDIR)/sel-ebc-paes-enforce.service \ + pvebc/$(SEL_EBC_MODDIR)/sel-ebc-pvebc.service \ + pvebc/$(SEL_EBC_MODDIR)/sel-ebc.crypttab \ + pvebc/$(SEL_EBC_MODDIR)/sel-ebc.target \ + $(DESTDIR)$(DRACUTMODDIR)/$(SEL_EBC_MODDIR) +endif + install-rust-tools: $(BUILD_TARGETS) $(INSTALL) -d -m 755 $(DESTDIR)$(USRBINDIR) $(foreach target,$(CARGO_TARGETS),\ diff --git a/rust/pvebc/95sel-ebc/boot.mount b/rust/pvebc/95sel-ebc/boot.mount new file mode 100644 index 00000000..b672bcd6 --- /dev/null +++ b/rust/pvebc/95sel-ebc/boot.mount @@ -0,0 +1,28 @@ +[Unit] +Description=Mount /boot early in initramfs + +# Initramfs requirement +DefaultDependencies=no +# Make absolutely sure this only runs in initramfs (and not post-pivot if the +# unit ever appears there) +ConditionPathExists=/etc/initrd-release +ConditionKernelCommandLine=root + +# we use /dev/disk/by-label because it identifies the boot partition system +# independently IF set up correctly +Requires=dev-disk-by\x2dlabel-boot.device + +# Ordering dependencies +After=dev-disk-by\x2dlabel-boot.device +Before=sel-ebc-pvebc.service + +[Mount] +# system independent identification of boot partition requires that the label +# boot is set for the boot partition +What=/dev/disk/by-label/boot +Where=/boot +Type=auto +Options=defaults + +[Install] +WantedBy=sel-ebc-pvebc.service diff --git a/rust/pvebc/95sel-ebc/module-setup.sh b/rust/pvebc/95sel-ebc/module-setup.sh new file mode 100644 index 00000000..52673112 --- /dev/null +++ b/rust/pvebc/95sel-ebc/module-setup.sh @@ -0,0 +1,76 @@ +#!/bin/bash +# SPDX-License-Identifier: MIT +# +# Copyright IBM Corp. + + +# Called by dracut +check() { + # always include + return 0 +} + +# Called by dracut +depends() { + # We need systemd in the initramfs + echo systemd + echo systemd-udevd + echo crypt + echo dm + return 0 +} + +# Called by dracut +installkernel() { + # kernel modules needed for opening an encrypted rfs + instmods -c uvdevice + instmods -c paes_s390 + instmods -c pkey_uv + instmods -c pkey_pckmo + instmods -c pkey +} + +# Called by dracut +install() { + # shellcheck disable=SC2154 + # moddir, systemdsystemunitdir, and initdir are provided by dracut + # Copy the units into the initramfs' systemd unit dir + inst_simple "$moddir/sel-ebc.target" \ + "$systemdsystemunitdir/sel-ebc.target" + inst_simple "$moddir/sel-ebc-pvebc.service" \ + "$systemdsystemunitdir/sel-ebc-pvebc.service" + inst_simple "$moddir/sel-ebc-paes-enforce.service" \ + "$systemdsystemunitdir/sel-ebc-paes-enforce.service" + inst_simple "$moddir/sel-ebc-override-crypttab.service" \ + "$systemdsystemunitdir/sel-ebc-override-crypttab.service" + inst_simple "$moddir/boot.mount" \ + "$systemdsystemunitdir/boot.mount" + + # already exisitng unit we depend on for kernel modules + inst_simple /usr/lib/systemd/system/systemd-modules-load.service \ + "$systemdsystemunitdir/systemd-modules-load.service" + + # wrapper for sel-ebc.service + inst_simple "$moddir/pvebc-wrapper.sh" \ + "/etc/sel-ebc/pvebc-wrapper.sh" + + # override crypttab + inst_simple "$moddir/override-crypttab.sh" \ + "/etc/sel-ebc/override-crypttab.sh" + + # copy main application + inst_binary "/usr/bin/pvebc" + inst_binary "/usr/bin/pvsecret" + + inst_simple "$moddir/sel-ebc.crypttab" "/etc/sel-ebc/crypttab" + + # Create the enablement symlinks in the image using host systemctl: + # shellcheck disable=SC2154 + inst_dir "$initdir/etc/systemd/system" + systemctl --root "$initdir" --no-reload --quiet enable sel-ebc.target + systemctl --root "$initdir" --no-reload --quiet enable sel-ebc-pvebc.service + systemctl --root "$initdir" --no-reload --quiet enable sel-ebc-override-crypttab.service + systemctl --root "$initdir" --no-reload --quiet enable sel-ebc-paes-enforce.service + systemctl --root "$initdir" --no-reload --quiet enable systemd-modules-load.service + systemctl --root "$initdir" --no-reload --quiet enable boot.mount +} diff --git a/rust/pvebc/95sel-ebc/override-crypttab.sh b/rust/pvebc/95sel-ebc/override-crypttab.sh new file mode 100644 index 00000000..fb17498a --- /dev/null +++ b/rust/pvebc/95sel-ebc/override-crypttab.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# SPDX-License-Identifier: MIT +# +# Copyright IBM Corp. + +IBM_RSRC_DIR="/etc/sel-ebc" + +if [[ ! -f "${IBM_RSRC_DIR}/crypttab" ]]; then + echo "Error: source file $IBM_RSRC_DIR/crypttab does not exist" + exit 1 +fi + +# Unconditionally override /etc/crypttab to ensure correct EBC configuration +cp "${IBM_RSRC_DIR}/crypttab" "/etc/crypttab" + +systemctl daemon-reload + +systemctl restart systemd-cryptsetup@cryptroot_mapper.service + +exit 0 diff --git a/rust/pvebc/95sel-ebc/pvebc-wrapper.sh b/rust/pvebc/95sel-ebc/pvebc-wrapper.sh new file mode 100644 index 00000000..376ee975 --- /dev/null +++ b/rust/pvebc/95sel-ebc/pvebc-wrapper.sh @@ -0,0 +1,63 @@ +#!/bin/bash +# SPDX-License-Identifier: MIT +# +# Copyright IBM Corp. + +SYSFS=/sys/firmware/uv/prot_virt_guest +SICS=/boot/sics +EBC_TMPFS=/run/sel-ebc +TOC=toc.asr +ASR_NAME=luks-rfs-passphrase + +# Early exit for non SEL guests +if [[ ! -e $SYSFS ]]; then + echo "Not running in a SEL guest." + exit 1 +fi +if [[ $(cat $SYSFS) -ne 1 ]]; then + echo "Not running in a SEL guest." + exit 1 +fi +echo "Running in SEL guest." + +# Copy EBC resources from /boot/sics to tmpfs for security +# This protects against host injection attacks by moving resources to UV-protected RAM +echo "Copying EBC resources from $SICS to $EBC_TMPFS" +if ! mkdir -p "$EBC_TMPFS"; then + echo "Failed to create $EBC_TMPFS" + exit 1 +fi + +# Copy only .asr and .pol files +for file in "$SICS"/*.asr "$SICS"/*.pol; do + if [[ -f "$file" && ! -L "$file" ]]; then + cp "$file" "$EBC_TMPFS/" || { + echo "Failed to copy $file to $EBC_TMPFS" + exit 1 + } + fi +done + +# Verify toc.asr was copied +if [[ ! -f "$EBC_TMPFS/$TOC" ]]; then + echo "Error: $EBC_TMPFS/$TOC does not exist after copy" + exit 1 +fi + +# execute the actual tool with the copied toc.asr +pvebc --toc "$EBC_TMPFS/$TOC" +rc=$? +if [[ $rc -ne 0 ]]; then + exit $rc +fi + +# Retrieve and check for dummy LUKS passphrase +pvsecret retrieve --inform name -o "$EBC_TMPFS/$ASR_NAME" --outform bin "$ASR_NAME" + +if [[ ! -f "$EBC_TMPFS/$ASR_NAME" ]]; then + echo "$EBC_TMPFS/$ASR_NAME does not exist" +fi + +chmod 400 "$EBC_TMPFS/$ASR_NAME" + +exit 0 diff --git a/rust/pvebc/95sel-ebc/sel-ebc-override-crypttab.service b/rust/pvebc/95sel-ebc/sel-ebc-override-crypttab.service new file mode 100644 index 00000000..11c1e76e --- /dev/null +++ b/rust/pvebc/95sel-ebc/sel-ebc-override-crypttab.service @@ -0,0 +1,37 @@ +[Unit] +Description=Override crypttab + +# boot partition contains SICS +# Loading of kernel modules is required which are needed for protected keys +Requires=systemd-modules-load.service +Requires=boot.mount + +# Ensure this runs before the handoff to the real root, if that's required: +After=boot.mount +After=systemd-modules-load.service +Before=cryptsetup-pre.target +Before=cryptsetup.target +Before=systemd-cryptsetup@.service +Before=initrd-root-device.target +After=sel-ebc-pvebc.service + +# Initramfs requirement +DefaultDependencies=no +# Make absolutely sure this only runs in initramfs +ConditionPathExists=/etc/initrd-release +ConditionKernelCommandLine=rd.sel-ebc +ConditionKernelCommandLine=root + +[Service] +Type=oneshot +ExecStart=/bin/bash /etc/sel-ebc/override-crypttab.sh +RemainAfterExit=yes +# If pvebc fails immediately abort boot +FailureAction=poweroff-immediate +# boot partition is unencrypted and contains SICS so we can get logs out this way +# logs do not leek any sensitive information +StandardOutput=file:/boot/sics/log +StandardError=file:/boot/sics/log + +[Install] +RequiredBy=sel-ebc.target diff --git a/rust/pvebc/95sel-ebc/sel-ebc-paes-enforce.service b/rust/pvebc/95sel-ebc/sel-ebc-paes-enforce.service new file mode 100644 index 00000000..a335ba99 --- /dev/null +++ b/rust/pvebc/95sel-ebc/sel-ebc-paes-enforce.service @@ -0,0 +1,27 @@ +[Unit] +Description=Enforce PAES encrypted root fs for SEL guests + +# Ensure this runs before the handoff to the real root, if that's required: +After=cryptsetup.target +After=initrd-root-device.target +Before=sysroot.mount + +# Initramfs requirement +DefaultDependencies=no +# Make absolutely sure this only runs in initramfs +ConditionPathExists=/etc/initrd-release +ConditionKernelCommandLine=rd.sel-ebc +ConditionKernelCommandLine=root + +[Service] +Type=oneshot +ExecStart=bash -c 'dmsetup table /dev/disk/by-label/root | grep "paes-xts-plain64"' +FailureAction=poweroff-immediate +RemainAfterExit=yes + +# logs do not leek any sensitive information +StandardOutput=file:/boot/sics/log +StandardError=file:/boot/sics/log + +[Install] +RequiredBy=sel-ebc.target diff --git a/rust/pvebc/95sel-ebc/sel-ebc-pvebc.service b/rust/pvebc/95sel-ebc/sel-ebc-pvebc.service new file mode 100644 index 00000000..0965f416 --- /dev/null +++ b/rust/pvebc/95sel-ebc/sel-ebc-pvebc.service @@ -0,0 +1,36 @@ +[Unit] +Description=Run pvebc during early boot to process SICS + +# boot partition contains SICS +# Loading of kernel modules is required which are needed for protected keys +Requires=systemd-modules-load.service +Wants=boot.mount + +# Ensure this runs before the handoff to the real root, if that's required: +Before=initrd-root-device.target +Before=cryptsetup-pre.target +Before=cryptsetup.target +After=boot.mount +After=systemd-modules-load.service + +# Initramfs requirement +DefaultDependencies=no +# Make absolutely sure this only runs in initramfs +ConditionPathExists=/etc/initrd-release +AssertPathIsDirectory=/boot/sics +ConditionKernelCommandLine=rd.sel-ebc + +[Service] +Type=oneshot +# execute pvebc +ExecStart=/bin/bash /etc/sel-ebc/pvebc-wrapper.sh +RemainAfterExit=yes +# If pvebc fails immediately abort boot +FailureAction=poweroff-immediate +# boot partition is unencrypted and contains SICS so we can get logs out this way +# logs do not leek any sensitive information +StandardOutput=file:/boot/sics/log +StandardError=file:/boot/sics/log + +[Install] +RequiredBy=sel-ebc.target diff --git a/rust/pvebc/95sel-ebc/sel-ebc.crypttab b/rust/pvebc/95sel-ebc/sel-ebc.crypttab new file mode 100644 index 00000000..a9fcee77 --- /dev/null +++ b/rust/pvebc/95sel-ebc/sel-ebc.crypttab @@ -0,0 +1 @@ +cryptroot_mapper /dev/disk/by-label/cryptroot /run/sel-ebc/luks-rfs-passphrase diff --git a/rust/pvebc/95sel-ebc/sel-ebc.target b/rust/pvebc/95sel-ebc/sel-ebc.target new file mode 100644 index 00000000..3fa682a2 --- /dev/null +++ b/rust/pvebc/95sel-ebc/sel-ebc.target @@ -0,0 +1,27 @@ +[Unit] +Description=Target unit for IBM Secure Execution for Linux early boot customization + +# enable for use in initramfs +DefaultDependencies=no + +# if not met start the target without starting its dependencies +ConditionPathExists=/etc/initrd-release +ConditionKernelCommandLine=rd.sel-ebc + +# if not met the target is considered failed +# AssertVirtualization=kvm + +# if target fails abort immediately +FailureAction=poweroff-immediate + +Requires=sel-ebc-pvebc.service +Requires=sel-ebc-override-crypttab.service +Requires=sel-ebc-paes-enforce.service + +# ordering dependencies +After=systemd-modules-load.service +Before=initrd-root-device.target + +[Install] +# most basic hook to start target by default +WantedBy=initrd.target