From: Ana Bobillo <[email protected]> Add unit tests that pass malformed FMP capsules to efi_update_capsule() and an image with a certificate longer than itself to efi_capsule_authenticate(), and check that they are rejected:
- a capsule ending inside the FMP capsule header; - an item offset list running past the end of the capsule; - an item offset that wraps around when the image header size is added; - an image or vendor code larger than the rest of the capsule; - a certificate longer than the image. Each capsule is built inside a larger zeroed buffer, so that without the checks the code reads the bytes around it and returns another status (for the certificate, a wrapped image size) instead of faulting. Signed-off-by: Ana Bobillo <[email protected]> --- test/lib/Makefile | 1 + test/lib/efi_capsule.c | 193 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 194 insertions(+) create mode 100644 test/lib/efi_capsule.c diff --git a/test/lib/Makefile b/test/lib/Makefile index 9fc4a1a9b5d..0fb6c6eabde 100644 --- a/test/lib/Makefile +++ b/test/lib/Makefile @@ -10,6 +10,7 @@ obj-y += abuf.o obj-y += alist.o obj-$(CONFIG_ECDSA_VERIFY_MBEDTLS) += ecdsa.o obj-$(CONFIG_EFI_LOADER) += efi_device_path.o efi_memory.o +obj-$(CONFIG_EFI_CAPSULE_FIRMWARE) += efi_capsule.o obj-$(CONFIG_EFI_SECURE_BOOT) += efi_image_region.o ifdef CONFIG_RISCV obj-$(CONFIG_USE_PRIVATE_LIBGCC) += test_clz.o diff --git a/test/lib/efi_capsule.c b/test/lib/efi_capsule.c new file mode 100644 index 00000000000..7e4e1e69540 --- /dev/null +++ b/test/lib/efi_capsule.c @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Test the size checks of capsule updates + * + * Copyright (c) 2026 Ana Bobillo <[email protected]> + */ + +#include <efi_loader.h> +#include <pe.h> +#include <test/lib.h> +#include <test/test.h> +#include <test/ut.h> + +static const efi_guid_t fmp_capsule_guid = + EFI_FIRMWARE_MANAGEMENT_CAPSULE_ID_GUID; + +/* + * Capsules are built in the middle of this buffer, so that the bytes + * before and after a capsule are zero and can be read without a fault + * if a size check is missing. + */ +static u8 buf[1024] __aligned(8); + +/** + * fmp_capsule_init() - set up an FMP capsule in the middle of buf + * + * @fmp_size: size of the FMP capsule following the capsule header + * Return: capsule header + */ +static struct efi_capsule_header *fmp_capsule_init(size_t fmp_size) +{ + struct efi_capsule_header *capsule = (void *)buf + 256; + + memset(buf, 0, sizeof(buf)); + guidcpy(&capsule->capsule_guid, &fmp_capsule_guid); + capsule->header_size = sizeof(*capsule); + capsule->capsule_image_size = sizeof(*capsule) + fmp_size; + + return capsule; +} + +/** + * update_capsule() - pass one capsule to UpdateCapsule() + * + * @capsule: capsule header + * Return: status code + */ +static efi_status_t update_capsule(struct efi_capsule_header *capsule) +{ + return EFI_CALL(efi_update_capsule(&capsule, 1, 0)); +} + +/* efi_capsule_update_firmware() must check the FMP capsule header size */ +static int lib_test_efi_capsule_update_fmp_header(struct unit_test_state *uts) +{ + struct efi_capsule_header *capsule; + struct efi_firmware_management_capsule_header *fmp; + + /* The FWU checks reject a capsule before it is parsed */ + if (IS_ENABLED(CONFIG_FWU_MULTI_BANK_UPDATE)) + return -EAGAIN; + + ut_asserteq_64(EFI_SUCCESS, efi_init_obj_list()); + + /* The capsule ends after the version field */ + capsule = fmp_capsule_init(sizeof(fmp->version)); + fmp = (void *)capsule + capsule->header_size; + fmp->version = 1; + ut_asserteq_64(EFI_INVALID_PARAMETER, update_capsule(capsule)); + + return 0; +} +LIB_TEST(lib_test_efi_capsule_update_fmp_header, 0); + +/* efi_capsule_update_firmware() must check the item offset list size */ +static int lib_test_efi_capsule_update_item_list(struct unit_test_state *uts) +{ + struct efi_capsule_header *capsule; + struct efi_firmware_management_capsule_header *fmp; + + /* The FWU checks reject a capsule before it is parsed */ + if (IS_ENABLED(CONFIG_FWU_MULTI_BANK_UPDATE)) + return -EAGAIN; + + ut_asserteq_64(EFI_SUCCESS, efi_init_obj_list()); + + /* Room for eight item offsets, but nine items */ + capsule = fmp_capsule_init(sizeof(*fmp) + 8 * sizeof(u64)); + fmp = (void *)capsule + capsule->header_size; + fmp->version = 1; + fmp->embedded_driver_count = 8; + fmp->payload_item_count = 1; + /* The ninth item offset lies past the end of the capsule */ + fmp->item_offset_list[8] = sizeof(*fmp); + ut_asserteq_64(EFI_INVALID_PARAMETER, update_capsule(capsule)); + + return 0; +} +LIB_TEST(lib_test_efi_capsule_update_item_list, 0); + +/* efi_capsule_update_firmware() must check an item offset without overflow */ +static int lib_test_efi_capsule_update_item_offset(struct unit_test_state *uts) +{ + struct efi_capsule_header *capsule; + struct efi_firmware_management_capsule_header *fmp; + struct efi_firmware_management_capsule_image_header *image; + + /* The FWU checks reject a capsule before it is parsed */ + if (IS_ENABLED(CONFIG_FWU_MULTI_BANK_UPDATE)) + return -EAGAIN; + + ut_asserteq_64(EFI_SUCCESS, efi_init_obj_list()); + + capsule = fmp_capsule_init(sizeof(*fmp) + sizeof(u64) + + sizeof(*image) + 16); + fmp = (void *)capsule + capsule->header_size; + fmp->version = 1; + fmp->payload_item_count = 1; + /* Offset plus image header size wraps around to zero */ + fmp->item_offset_list[0] = -(u64)sizeof(*image); + ut_asserteq_64(EFI_INVALID_PARAMETER, update_capsule(capsule)); + + return 0; +} +LIB_TEST(lib_test_efi_capsule_update_item_offset, 0); + +/* efi_capsule_update_firmware() must check the image and vendor code sizes */ +static int lib_test_efi_capsule_update_image_size(struct unit_test_state *uts) +{ + struct efi_capsule_header *capsule; + struct efi_firmware_management_capsule_header *fmp; + struct efi_firmware_management_capsule_image_header *image; + + /* The FWU checks reject a capsule before it is parsed */ + if (IS_ENABLED(CONFIG_FWU_MULTI_BANK_UPDATE)) + return -EAGAIN; + + ut_asserteq_64(EFI_SUCCESS, efi_init_obj_list()); + + capsule = fmp_capsule_init(sizeof(*fmp) + sizeof(u64) + + sizeof(*image) + 16); + fmp = (void *)capsule + capsule->header_size; + fmp->version = 1; + fmp->payload_item_count = 1; + fmp->item_offset_list[0] = sizeof(*fmp) + sizeof(u64); + image = (void *)fmp + fmp->item_offset_list[0]; + image->version = 3; + + /* The image is larger than the capsule */ + image->update_image_size = 17; + ut_asserteq_64(EFI_INVALID_PARAMETER, update_capsule(capsule)); + + /* The vendor code does not fit after the image */ + image->update_image_size = 16; + image->update_vendor_code_size = 1; + ut_asserteq_64(EFI_INVALID_PARAMETER, update_capsule(capsule)); + + /* Both fit: there is no FMP driver for the zero image type */ + image->update_vendor_code_size = 0; + ut_asserteq_64(EFI_UNSUPPORTED, update_capsule(capsule)); + + return 0; +} +LIB_TEST(lib_test_efi_capsule_update_image_size, 0); + +/* efi_remove_auth_hdr() must check the certificate length */ +static int lib_test_efi_remove_auth_hdr_length(struct unit_test_state *uts) +{ + struct efi_firmware_image_authentication *auth; + efi_uintn_t capsule_size = 64; + efi_uintn_t image_size; + void *image; + + if (!IS_ENABLED(CONFIG_EFI_CAPSULE_AUTHENTICATE)) + return -EAGAIN; + + memset(buf, 0, sizeof(buf)); + auth = (void *)buf; + /* The certificate is longer than the capsule */ + auth->auth_info.hdr.dwLength = 256; + auth->auth_info.hdr.wRevision = WIN_CERT_REVISION_2_0; + auth->auth_info.hdr.wCertificateType = WIN_CERT_TYPE_EFI_GUID; + guidcpy(&auth->auth_info.cert_type, &efi_guid_cert_type_pkcs7); + + ut_asserteq_64(EFI_SECURITY_VIOLATION, + efi_capsule_authenticate(buf, capsule_size, &image, + &image_size)); + /* The image size must not have wrapped around */ + ut_asserteq_64(capsule_size, image_size); + + return 0; +} +LIB_TEST(lib_test_efi_remove_auth_hdr_length, 0);
