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);

Reply via email to