From: Enric Balletbo i Serra <[email protected]> Add unit tests and Python integration tests for the EFI_SECURITY_ARCH_PROTOCOL and EFI_SECURITY2_ARCH_PROTOCOL implementation.
The C-based unit test verifies: - Protocols are correctly installed on the root handle. - The FileAuthentication override mechanism works, which is required for tools like systemd-stub to bypass secure boot for embedded UKI payloads. The Python tests verify: - Protocols are discoverable via 'efidebug dh'. - Functional regression: Signed images still authenticate and boot correctly. - Functional regression: Unsigned images are still rejected under Secure Boot. Additionally, add a README with step-by-step instructions for manual end-to-end testing using a real Unified Kernel Image (UKI) and QEMU. Signed-off-by: Enric Balletbo i Serra <[email protected]> Signed-off-by: Ian Mullins <[email protected]> --- lib/efi_selftest/Makefile | 1 + lib/efi_selftest/efi_selftest_security_arch.c | 110 +++++++++++ .../py/tests/test_efi_secboot/README.security_arch | 208 +++++++++++++++++++++ .../tests/test_efi_secboot/test_security_arch.py | 73 ++++++++ 4 files changed, 392 insertions(+) diff --git a/lib/efi_selftest/Makefile b/lib/efi_selftest/Makefile index 67102648703..06de55c6f4f 100644 --- a/lib/efi_selftest/Makefile +++ b/lib/efi_selftest/Makefile @@ -65,6 +65,7 @@ obj-y += efi_selftest_unaligned.o endif obj-$(CONFIG_EFI_LOADER_HII) += efi_selftest_hii.o obj-$(CONFIG_EFI_RNG_PROTOCOL) += efi_selftest_rng.o +obj-$(CONFIG_EFI_SECURITY_ARCH_PROTOCOL) += efi_selftest_security_arch.o obj-$(CONFIG_EFI_GET_TIME) += efi_selftest_rtc.o obj-$(CONFIG_EFI_TCG2_PROTOCOL) += efi_selftest_tcg2.o diff --git a/lib/efi_selftest/efi_selftest_security_arch.c b/lib/efi_selftest/efi_selftest_security_arch.c new file mode 100644 index 00000000000..384146a187a --- /dev/null +++ b/lib/efi_selftest/efi_selftest_security_arch.c @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * efi_selftest_security_arch + * + * Test the EFI Security Architecture Protocols. + * + * Verifies that EFI_SECURITY_ARCH_PROTOCOL and EFI_SECURITY2_ARCH_PROTOCOL + * can be located, and that the override mechanism works -- this is the same + * pattern used by systemd-stub to temporarily bypass secure boot for + * embedded UKI payloads. + */ + +#include <efi_selftest.h> + +static struct efi_boot_services *boottime; + +static efi_guid_t security_guid = EFI_SECURITY_ARCH_PROTOCOL_GUID; +static efi_guid_t security2_guid = EFI_SECURITY2_ARCH_PROTOCOL_GUID; + +static int setup(const efi_handle_t handle, + const struct efi_system_table *systable) +{ + boottime = systable->boottime; + return EFI_ST_SUCCESS; +} + +/* + * Custom Security2 hook that always succeeds -- mimics what systemd-stub + * installs for its embedded kernel payload. + */ +static efi_status_t EFIAPI +permissive_file_authentication(const struct efi_security2_arch_protocol *this, + const struct efi_device_path *device_path, + void *file_buffer, efi_uintn_t file_size, + bool boot_policy) +{ + return EFI_SUCCESS; +} + +static int execute(void) +{ + struct efi_security_arch_protocol *security = NULL; + struct efi_security2_arch_protocol *security2 = NULL; + efi_security2_file_authentication original_hook; + efi_status_t ret; + + /* Locate Security Architecture Protocol */ + ret = boottime->locate_protocol(&security_guid, NULL, + (void **)&security); + if (ret != EFI_SUCCESS) { + efi_st_error("Could not locate EFI_SECURITY_ARCH_PROTOCOL\n"); + return EFI_ST_FAILURE; + } + + if (!security->file_authentication_state) { + efi_st_error("FileAuthenticationState callback is NULL\n"); + return EFI_ST_FAILURE; + } + + /* Locate Security2 Architecture Protocol */ + ret = boottime->locate_protocol(&security2_guid, NULL, + (void **)&security2); + if (ret != EFI_SUCCESS) { + efi_st_error("Could not locate EFI_SECURITY2_ARCH_PROTOCOL\n"); + return EFI_ST_FAILURE; + } + + if (!security2->file_authentication) { + efi_st_error("FileAuthentication callback is NULL\n"); + return EFI_ST_FAILURE; + } + + /* + * Test override mechanism: save original, install permissive hook, + * verify it was installed, then restore original. + */ + original_hook = security2->file_authentication; + + security2->file_authentication = permissive_file_authentication; + + if (security2->file_authentication != permissive_file_authentication) { + efi_st_error("Failed to override FileAuthentication\n"); + return EFI_ST_FAILURE; + } + + /* Call the overridden hook directly to verify it works */ + ret = security2->file_authentication(security2, NULL, NULL, 0, false); + if (ret != EFI_SUCCESS) { + efi_st_error("Permissive hook did not return EFI_SUCCESS\n"); + security2->file_authentication = original_hook; + return EFI_ST_FAILURE; + } + + /* Restore original handler */ + security2->file_authentication = original_hook; + + if (security2->file_authentication != original_hook) { + efi_st_error("Failed to restore FileAuthentication\n"); + return EFI_ST_FAILURE; + } + + return EFI_ST_SUCCESS; +} + +EFI_UNIT_TEST(security_arch) = { + .name = "security architecture protocols", + .phase = EFI_EXECUTE_BEFORE_BOOTTIME_EXIT, + .setup = setup, + .execute = execute, +}; diff --git a/test/py/tests/test_efi_secboot/README.security_arch b/test/py/tests/test_efi_secboot/README.security_arch new file mode 100644 index 00000000000..d1792703c26 --- /dev/null +++ b/test/py/tests/test_efi_secboot/README.security_arch @@ -0,0 +1,208 @@ +QEMU Integration Test: EFI Security Architecture Protocols with UKI +==================================================================== + +This describes how to manually test the EFI_SECURITY_ARCH_PROTOCOL and +EFI_SECURITY2_ARCH_PROTOCOL implementation end-to-end with a real Unified +Kernel Image (UKI) on QEMU. + +IMPORTANT: Both U-Boot and the UKI must be built for the same architecture. +If your UKI is x86_64, you must use qemu-x86_64. If your UKI is aarch64, +you must use qemu_arm64. A mismatch causes: + "Machine type 0x8664 is not supported" + +Prerequisites +------------- +- qemu-system-x86_64 (or qemu-system-aarch64 for arm64) +- systemd-ukify (for creating UKIs) +- sbsign (for signing PE binaries) +- efitools (cert-to-efi-sig-list, sign-efi-sig-list) +- mtools (mcopy, mmd -- for populating FAT images without root) +- openssl + +Overview +-------- +The test works by packing everything into a single FAT disk image (esp.img): + + esp.img (FAT, passed to QEMU as a virtio disk) + ├── PK.auth Secure Boot Platform Key + ├── KEK.auth Key Exchange Key + ├── db.auth Authorized Signature Database + └── EFI/ + └── BOOT/ + └── BOOTX64.EFI The signed UKI (contains systemd-stub + kernel + initrd) + +U-Boot sees this as "virtio 0:1". First you enroll the keys from the root +of the FAT, then you boot the UKI from EFI/BOOT/. + +Step 1: Build U-Boot +--------------------- + + For x86_64: + + make qemu-x86_64_defconfig + scripts/config --enable FIT_SIGNATURE + scripts/config --enable EFI_SECURE_BOOT + scripts/config --enable EFI_SECURITY_ARCH_PROTOCOL + make olddefconfig + make -j$(nproc) + + Output: u-boot.rom + + For aarch64: + + make qemu_arm64_defconfig + scripts/config --enable FIT_SIGNATURE + scripts/config --enable EFI_SECURE_BOOT + scripts/config --enable EFI_SECURITY_ARCH_PROTOCOL + make olddefconfig + make -j$(nproc) + + Output: u-boot.bin + +Step 2: Create Secure Boot Keys +--------------------------------- + GUID='11111111-2222-3333-4444-123456789abc' + mkdir -p keys && cd keys + + # Platform Key + openssl req -x509 -sha256 -newkey rsa:2048 \ + -subj /CN=Test_PK/ -keyout PK.key -out PK.crt -nodes -days 365 + cert-to-efi-sig-list -g $GUID PK.crt PK.esl + sign-efi-sig-list -t "2024-01-01" -c PK.crt -k PK.key PK PK.esl PK.auth + + # Key Exchange Key + openssl req -x509 -sha256 -newkey rsa:2048 \ + -subj /CN=Test_KEK/ -keyout KEK.key -out KEK.crt -nodes -days 365 + cert-to-efi-sig-list -g $GUID KEK.crt KEK.esl + sign-efi-sig-list -t "2024-01-01" -c PK.crt -k PK.key KEK KEK.esl KEK.auth + + # Authorized Signature Database + openssl req -x509 -sha256 -newkey rsa:2048 \ + -subj /CN=Test_db/ -keyout db.key -out db.crt -nodes -days 365 + cert-to-efi-sig-list -g $GUID db.crt db.esl + sign-efi-sig-list -t "2024-01-01" -c KEK.crt -k KEK.key db db.esl db.auth + + cd .. + +Step 3: Create and Sign a UKI +------------------------------- + # Get a kernel and initrd matching your target architecture. + # Tip: use a small initrd for initial testing to avoid memory issues. + + For x86_64: + + ukify build \ + --linux /boot/vmlinuz \ + --initrd /boot/initrd.img \ + --cmdline "console=ttyS0 root=/dev/vda1" \ + --output test.uki.unsigned + + For aarch64: + + ukify build \ + --linux /boot/vmlinuz \ + --initrd /boot/initrd.img \ + --cmdline "console=ttyAMA0 root=/dev/vda1" \ + --output test.uki.unsigned + + # Sign the UKI with the db key + sbsign --key keys/db.key --cert keys/db.crt \ + --output test.uki test.uki.unsigned + +Step 4: Build the ESP Disk Image +---------------------------------- + All files -- keys and the signed UKI -- go into one FAT image. + This image is what gets passed to QEMU as a disk. + + dd if=/dev/zero of=esp.img bs=1M count=128 + mkfs.vfat esp.img + + # Copy the key enrollment files to the FAT root + mcopy -i esp.img keys/PK.auth ::PK.auth + mcopy -i esp.img keys/KEK.auth ::KEK.auth + mcopy -i esp.img keys/db.auth ::db.auth + + # Copy the signed UKI as the default EFI boot binary + mmd -i esp.img ::EFI ::EFI/BOOT + + For x86_64: + mcopy -i esp.img test.uki ::EFI/BOOT/BOOTX64.EFI + + For aarch64: + mcopy -i esp.img test.uki ::EFI/BOOT/BOOTAA64.EFI + +Step 5: Launch QEMU +--------------------- + The esp.img disk image (containing both keys and the UKI) is passed + to QEMU as a virtio drive. U-Boot accesses it as "virtio 0:1". + + For x86_64: + + qemu-system-x86_64 \ + -bios u-boot.rom \ + -m 4G \ + -drive if=virtio,format=raw,file=esp.img \ + -nographic + + For aarch64: + + qemu-system-aarch64 -machine virt -cpu cortex-a57 -m 4G \ + -bios u-boot.bin \ + -drive if=virtio,format=raw,file=esp.img \ + -nographic + +Step 6: Enroll Keys and Boot (in U-Boot console) +--------------------------------------------------- + # All files are on the same disk: virtio 0:1 + + # 1. Enroll secure boot keys (from FAT root) + fatload virtio 0:1 4000000 db.auth + setenv -e -nv -bs -rt -at -i 4000000:$filesize db + fatload virtio 0:1 4000000 KEK.auth + setenv -e -nv -bs -rt -at -i 4000000:$filesize KEK + fatload virtio 0:1 4000000 PK.auth + setenv -e -nv -bs -rt -at -i 4000000:$filesize PK + + # 2. Verify secure boot is active + printenv -e SecureBoot + + # 3. Verify security protocols are installed + # Look for GUIDs a46423e3-... and 94ab2f58-... on the root handle + efidebug dh + + # 4. Boot the signed UKI (from EFI/BOOT/ on the same disk) + For x86_64: + efidebug boot add -b 1 UKI virtio 0:1 /EFI/BOOT/BOOTX64.EFI -s "" + + For aarch64: + efidebug boot add -b 1 UKI virtio 0:1 /EFI/BOOT/BOOTAA64.EFI -s "" + + efidebug boot order 1 + bootefi bootmgr + +Expected Result +---------------- +1. The UKI outer PE passes secure boot authentication (signed with db key). +2. systemd-stub inside the UKI locates EFI_SECURITY2_ARCH_PROTOCOL. +3. systemd-stub overrides the FileAuthentication callback. +4. systemd-stub calls LoadImage() for the embedded kernel. +5. U-Boot's efi_load_pe() calls the overridden callback, which returns + EFI_SUCCESS for the embedded kernel. +6. The kernel starts successfully via StartImage(). +7. After loading, systemd-stub restores the original callback. + +Troubleshooting +---------------- +- "Machine type 0x8664 is not supported": The UKI is x86_64 but U-Boot + was built for a different architecture. Rebuild with qemu-x86_64_defconfig. + +- "Machine type 0xaa64 is not supported": The UKI is aarch64 but U-Boot + was built for x86_64. Rebuild with qemu_arm64_defconfig. + +- "Failed to load initrd: 0x8000000000000009" (EFI_OUT_OF_RESOURCES): + Not enough EFI memory for the initrd. Increase QEMU RAM (-m 4G), use + a smaller initrd, or increase CONFIG_SYS_MALLOC_LEN in U-Boot. + +- Keys fail to enroll: Make sure the .auth files are time-based + authenticated variables signed with the correct parent key (db signed + by KEK, KEK signed by PK, PK self-signed). diff --git a/test/py/tests/test_efi_secboot/test_security_arch.py b/test/py/tests/test_efi_secboot/test_security_arch.py new file mode 100644 index 00000000000..9ba3cbcf944 --- /dev/null +++ b/test/py/tests/test_efi_secboot/test_security_arch.py @@ -0,0 +1,73 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright (c) 2026 +# +# U-Boot UEFI: Security Architecture Protocol Test + +""" +This test verifies that EFI_SECURITY_ARCH_PROTOCOL and +EFI_SECURITY2_ARCH_PROTOCOL are installed and discoverable. +""" + +import pytest + + [email protected]('sandbox') [email protected]('efi_security_arch_protocol') [email protected]('cmd_efidebug') [email protected] +class TestEfiSecurityArch(object): + def test_efi_security_protocols_installed(self, ubman): + """ + Test that Security Architecture Protocols are visible on root handle. + """ + ubman.restart_uboot() + output = ubman.run_command('efidebug dh') + # EFI_SECURITY_ARCH_PROTOCOL_GUID + assert 'a46423e3-4617-49f1-b9ff-d1bfa9115839' in ''.join(output).lower() \ + or 'Security' in ''.join(output) + # EFI_SECURITY2_ARCH_PROTOCOL_GUID + assert '94ab2f58-1438-4ef1-9152-18941a3a0e68' in ''.join(output).lower() \ + or 'Security2' in ''.join(output) + + def test_efi_security_signed_image_with_protocol(self, ubman, efi_boot_env): + """ + Regression: signed images still authenticate when the security + architecture protocols are installed and route the authentication. + """ + ubman.restart_uboot() + disk_img = efi_boot_env + output = ubman.run_command_list([ + 'host bind 0 %s' % disk_img, + 'fatload host 0:1 4000000 db.auth', + 'setenv -e -nv -bs -rt -at -i 4000000:$filesize db', + 'fatload host 0:1 4000000 KEK.auth', + 'setenv -e -nv -bs -rt -at -i 4000000:$filesize KEK', + 'fatload host 0:1 4000000 PK.auth', + 'setenv -e -nv -bs -rt -at -i 4000000:$filesize PK']) + assert 'Failed to set EFI variable' not in ''.join(output) + output = ubman.run_command_list([ + 'efidebug boot add -b 1 HELLO host 0:1 /helloworld.efi.signed -s ""', + 'efidebug boot order 1', + 'bootefi bootmgr']) + assert 'Hello, world!' in ''.join(output) + + def test_efi_security_unsigned_image_rejected(self, ubman, efi_boot_env): + """ + Regression: unsigned images are still rejected under secure boot + when the security architecture protocols are installed. + """ + ubman.restart_uboot() + disk_img = efi_boot_env + output = ubman.run_command_list([ + 'host bind 0 %s' % disk_img, + 'fatload host 0:1 4000000 KEK.auth', + 'setenv -e -nv -bs -rt -at -i 4000000:$filesize KEK', + 'fatload host 0:1 4000000 PK.auth', + 'setenv -e -nv -bs -rt -at -i 4000000:$filesize PK']) + assert 'Failed to set EFI variable' not in ''.join(output) + output = ubman.run_command_list([ + 'efidebug boot add -b 1 HELLO host 0:1 /helloworld.efi -s ""', + 'efidebug boot order 1', + 'efidebug test bootmgr']) + assert '\'HELLO\' failed' in ''.join(output) + assert 'efi_bootmgr_load() returned: 26' in ''.join(output) -- 2.54.0

