On 5/25/26 17:13, Simon Glass wrote:
Hi Heinrich,

On 2026-05-18T06:30:20, Heinrich Schuchardt <[email protected]> wrote:
efi_loader: validate PE-COFF relocation data

When applying base relocations from a PE-COFF binary all data must
be treated as untrusted. Add the following checks to
efi_loader_relocate():

* Reject relocation blocks that don't start on a 32-bit aligned
   address.
* Reject relocation blocks whose SizeOfBlock is smaller than the
   block header, which would cause an unsigned underflow when computing
   the entry count.
* A block with SizeOfBlock == 0 is invalid and does not mark the end of
   the relocation table.
* Reject relocation blocks that extend beyond the end of the
   relocation section.
* Reject individual relocation entries whose target offset, together
   with the access width, exceeds the mapped image size, preventing
   out-of-bounds writes.

Pass virt_size to efi_loader_relocate() from efi_load_pe() to enable
[...]

lib/efi_loader/efi_image_loader.c | 76 +++++++++++++++++++++++++++++++++------
  1 file changed, 65 insertions(+), 11 deletions(-)

diff --git a/lib/efi_loader/efi_image_loader.c 
b/lib/efi_loader/efi_image_loader.c
@@ -122,34 +123,86 @@ static efi_status_t efi_loader_relocate(const 
IMAGE_BASE_RELOCATION *rel,
+                     /*
+                      * This first check covers VirtualAddress + Offset
+                      * resulting in an overflow.
+                      */
+                     if (rel->VirtualAddress > virt_size ||
+                         entry_offset > virt_size - rel->VirtualAddress) {
+                             log_debug("relocation address out of bounds\n");
+                             return EFI_LOAD_ERROR;
+                     }

The rel->VirtualAddress > virt_size half is invariant for the whole
block - so it should be out of the while (i--) loop so it runs once
per block, not per entry. The per-entry overflow check on entry_offset
stays inside.

Hello Simon,

Thanks for reviewing.

ok


diff --git a/lib/efi_loader/efi_image_loader.c 
b/lib/efi_loader/efi_image_loader.c
@@ -122,34 +123,86 @@ static efi_status_t efi_loader_relocate(const 
IMAGE_BASE_RELOCATION *rel,
                       case IMAGE_REL_BASED_HIGH:
+                             if (sizeof(uint16_t) > virt_size - offset) {
+                                     log_debug("relocation address out of 
bounds\n");
+                                     return EFI_LOAD_ERROR;
+                             }
                               *x16 += ((uint32_t)delta) >> 16;
                               break;
                       case IMAGE_REL_BASED_LOW:
+                             if (sizeof(uint16_t) > virt_size - offset) {
+                                     log_debug("relocation address out of 
bounds\n");
+                                     return EFI_LOAD_ERROR;
+                             }
                               *x16 += (uint16_t)delta;
                               break;
                       case IMAGE_REL_BASED_HIGHLOW:
+                             if (sizeof(uint32_t) > virt_size - offset) {
+                                     log_debug("relocation address out of 
bounds\n");
+                                     return EFI_LOAD_ERROR;
+                             }
                               *x32 += (uint32_t)delta;
                               break;
                       case IMAGE_REL_BASED_DIR64:
+                             if (sizeof(uint64_t) > virt_size - offset) {
+                                     log_debug("relocation address out of 
bounds\n");
+                                     return EFI_LOAD_ERROR;
+                             }
                               *x64 += (uint64_t)delta;
                               break;
  #ifdef __riscv
                       case IMAGE_REL_BASED_RISCV_HI20:
+                             if (sizeof(uint32_t) > virt_size - offset) {
+                                     log_debug("relocation address out of 
bounds\n");
+                                     return EFI_LOAD_ERROR;
+                             }

Five copies of the same check make the switch hard to read. Please set
a width per case and do the bounds check once, e.g.

    size_t width;

    switch (type) {
    case IMAGE_REL_BASED_ABSOLUTE:
        continue;
    case IMAGE_REL_BASED_HIGH:
    case IMAGE_REL_BASED_LOW:
        width = sizeof(uint16_t);
        break;
    case IMAGE_REL_BASED_HIGHLOW:
    case IMAGE_REL_BASED_RISCV_HI20:
        width = sizeof(uint32_t);
        break;
    case IMAGE_REL_BASED_DIR64:
        width = sizeof(uint64_t);
        break;
    ...
    }
    if (width > virt_size - offset) {
        log_debug("relocation address out of bounds\n");
        return EFI_LOAD_ERROR;
    }
> > Then a second switch (or if-ladder) does the write. That keeps the
duplication out of the hot path and stops anyone adding a new case
without the bounds check.

This neither makes the code easier to read nor the code faster nor the resulting binary image smaller.


diff --git a/lib/efi_loader/efi_image_loader.c 
b/lib/efi_loader/efi_image_loader.c
@@ -122,34 +123,86 @@ static efi_status_t efi_loader_relocate(const 
IMAGE_BASE_RELOCATION *rel,
-     while (rel + 1 < end && rel->SizeOfBlock) {
+     while (rel + 1 < end) {

Since this plugs a security hole driven by malformed input, please add
a py (or an lib/efi_selftest case) that feeds at least one rejected
shape - short SizeOfBlock, unaligned block, out-of-bounds
VirtualAddress - through efi_load_pe() and confirms EFI_LOAD_ERROR.
Without coverage there's nothing to stop a regression.

@@ -163,7 +216,7 @@ static efi_status_t efi_loader_relocate(const 
IMAGE_BASE_RELOCATION *rel,
  #endif
                       default:
-                             log_err("Unknown Relocation off %x type %x\n",
+                             log_err("Unknown Relocation off %lx type %x\n",
                                       offset, type);

Just to check - every other new diagnostic here uses log_debug(), but
the default arm keeps log_err(). I'm fine with that split, but the
other new "out of bounds" messages probably want log_err() too: a
refused image is something the user will want to see without enabling
debug logs, otherwise they just see the load fail with no clue why.

We originally introduced log_err() here when we were not sure if U-Boot covered all relocation types.

efi_loader_relocate() is invoked by EFI applications like GRUB. It probably would be better to convert the log_err() to log_debug() as we should not output anything inside an EFI application.

But this is beyond the scope of this patch.

Best regards

Heinrich

Reply via email to