On Mon, 18 May 2026 at 09:30, Heinrich Schuchardt <[email protected]> wrote: > > 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 > the per-entry bounds check. > > Reported-by: Anas Cherni <[email protected]> > Signed-off-by: Heinrich Schuchardt <[email protected]> > ---
Reviewed-by: Ilias Apalodimas <[email protected]> > 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 > index d002eb0c744..0bc66a0f732 100644 > --- a/lib/efi_loader/efi_image_loader.c > +++ b/lib/efi_loader/efi_image_loader.c > @@ -111,8 +111,9 @@ void efi_print_image_infos(void *pc) > * Return: status code > */ > static efi_status_t efi_loader_relocate(const IMAGE_BASE_RELOCATION *rel, > - unsigned long rel_size, void *efi_reloc, > - unsigned long pref_address) > + unsigned long rel_size, void > *efi_reloc, > + unsigned long pref_address, > + unsigned long virt_size) > { > unsigned long delta = (unsigned long)efi_reloc - pref_address; > const IMAGE_BASE_RELOCATION *end; > @@ -122,34 +123,86 @@ static efi_status_t efi_loader_relocate(const > IMAGE_BASE_RELOCATION *rel, > return EFI_SUCCESS; > > end = (const IMAGE_BASE_RELOCATION *)((const char *)rel + rel_size); > - while (rel + 1 < end && rel->SizeOfBlock) { > + while (rel + 1 < end) { > const uint16_t *relocs = (const uint16_t *)(rel + 1); > + > + /* Each block must start on a 32-bit boundary */ > + if (!IS_ALIGNED((uintptr_t)rel, sizeof(uint32_t))) { > + log_debug("Relocation block not 32-bit aligned\n"); > + return EFI_LOAD_ERROR; > + } > + /* Relocation block cannot be shorter than its header */ > + if (rel->SizeOfBlock < sizeof(*rel)) { > + log_debug("Relocation block too small: %u\n", > + rel->SizeOfBlock); > + return EFI_LOAD_ERROR; > + } > + /* All relocation entries must be inside the .reloc section */ > + if ((const char *)rel + rel->SizeOfBlock > (const char *)end) > { > + log_debug("Relocation block exceeds relocation > data\n"); > + return EFI_LOAD_ERROR; > + } > i = (rel->SizeOfBlock - sizeof(*rel)) / sizeof(uint16_t); > while (i--) { > - uint32_t offset = (uint32_t)(*relocs & 0xfff) + > - rel->VirtualAddress; > + uint32_t entry_offset = *relocs & 0xfff; > + unsigned long offset; > int type = *relocs >> EFI_PAGE_SHIFT; > - uint64_t *x64 = efi_reloc + offset; > - uint32_t *x32 = efi_reloc + offset; > - uint16_t *x16 = efi_reloc + offset; > + uint64_t *x64; > + uint32_t *x32; > + uint16_t *x16; > + > + /* > + * 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; > + } > + > + offset = rel->VirtualAddress + entry_offset; > + x64 = efi_reloc + offset; > + x32 = efi_reloc + offset; > + x16 = efi_reloc + offset; > > switch (type) { > case IMAGE_REL_BASED_ABSOLUTE: > break; > 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; > + } > *x32 = ((*x32 & 0xfffff000) + > (uint32_t)delta) | > (*x32 & 0x00000fff); > break; > @@ -163,7 +216,7 @@ static efi_status_t efi_loader_relocate(const > IMAGE_BASE_RELOCATION *rel, > break; > #endif > default: > - log_err("Unknown Relocation off %x type %x\n", > + log_err("Unknown Relocation off %lx type > %x\n", > offset, type); > return EFI_LOAD_ERROR; > } > @@ -970,8 +1023,9 @@ efi_status_t efi_load_pe(struct efi_loaded_image_obj > *handle, > > /* Run through relocations */ > if (efi_loader_relocate(rel, rel_size, efi_reloc, > - (unsigned long)image_base) != EFI_SUCCESS) { > - efi_free_pages((uintptr_t) efi_reloc, > + (unsigned long)image_base, > + virt_size) != EFI_SUCCESS) { > + efi_free_pages((uintptr_t)efi_reloc, > (virt_size + EFI_PAGE_MASK) >> EFI_PAGE_SHIFT); > ret = EFI_LOAD_ERROR; > goto err; > -- > 2.53.0 >

