On 10/26/26 14:19, Ana Bobillo wrote:
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
Thanks for adding tests. Maybe consider if patches 1, 2, 5 can also be
covered.
Best regards
Heinrich
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);