[PATCH 1/1] efi_loader: validate PE-COFF relocation data

Heinrich Schuchardt xypron.glpk at gmx.de
Wed May 27 08:28:03 CEST 2026


On 5/25/26 17:13, Simon Glass wrote:
> Hi Heinrich,
> 
> On 2026-05-18T06:30:20, Heinrich Schuchardt <xypron.glpk at gmx.de> 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


More information about the U-Boot mailing list