[PATCH 5/6] efi: Use malloc() for the EFI pool

Ilias Apalodimas ilias.apalodimas at linaro.org
Tue Jul 30 10:19:59 CEST 2024


On Mon, 29 Jul 2024 at 18:28, Simon Glass <sjg at chromium.org> wrote:
>
> Hi Ilias,
>
> On Mon, 29 Jul 2024 at 04:02, Ilias Apalodimas
> <ilias.apalodimas at linaro.org> wrote:
> >
> > Hi Simon
> >
> > On Fri, 26 Jul 2024 at 17:54, Simon Glass <sjg at chromium.org> wrote:
> > >
> > > Hi Ilias,
> > >
> > > On Fri, 26 Jul 2024 at 02:31, Ilias Apalodimas
> > > <ilias.apalodimas at linaro.org> wrote:
> > > >
> > > > Hi Simon,
> > > >
> > > >
> > > > On Fri, 26 Jul 2024 at 02:33, Simon Glass <sjg at chromium.org> wrote:
> > > > >
> > > > > Hi Heinrich,
> > > > >
> > > > > On Thu, 25 Jul 2024 at 09:54, Heinrich Schuchardt <xypron.glpk at gmx.de> wrote:
> > > > > >
> > > > > > On 25.07.24 15:56, Simon Glass wrote:
> > > > > > > This API call is intended for allocating small amounts of memory,
> > > > > > > similar to malloc(). The current implementation rounds up to whole pages
> > > > > > > which can waste large amounts of memory. It also implements its own
> > > > > > > malloc()-style header on each block.
> > > > > > >
> > > > > > > Use U-Boot's built-in malloc() instead, to avoid these problems:
> > > > > > >
> > > > > > > - it should normally be large enough for pool allocations
> > > > > > > - if it isn't we can enforce a minimum size for boards which use
> > > > > > >    EFI_LOADER
> > > > > > > - the existing mechanism may create an unwatned entry in the memory map
> > > > > > > - it is used for most EFI allocations already
> > > > > > >
> > > > > > > One side effect is that this seems to be showing up some bugs in the
> > > > > > > EFI code, since the malloc() pool becomes corrupted with some tests.
> > > > > > > This has likely crept in due to the very large gaps between allocations
> > > > > > > (around 4KB), which provides a lot of leeway when the allocation size is
> > > > > > > too small. Work around this by increasing the size for now, until these
> > > > > > > (presumed) bugs are located.
> > > > > > >
> > > > > > > Signed-off-by: Simon Glass <sjg at chromium.org>
> > > > > > > ---
> > > > > > >
> > > > > > >   lib/efi_loader/efi_memory.c | 93 ++++++++-----------------------------
> > > > > > >   1 file changed, 19 insertions(+), 74 deletions(-)
> > > > > > >
> > > > > > > diff --git a/lib/efi_loader/efi_memory.c b/lib/efi_loader/efi_memory.c
> > > > > > > index 2945f5648c7..fabe9e3a87a 100644
> > > > > > > --- a/lib/efi_loader/efi_memory.c
> > > > > > > +++ b/lib/efi_loader/efi_memory.c
> > > > > > > @@ -80,45 +80,6 @@ static LIST_HEAD(efi_mem);
> > > > > > >   void *efi_bounce_buffer;
> > > > > > >   #endif
> > > > > > >
> > > > > > > -/**
> > > > > > > - * struct efi_pool_allocation - memory block allocated from pool
> > > > > > > - *
> > > > > > > - * @num_pages:       number of pages allocated
> > > > > > > - * @checksum:        checksum
> > > > > > > - * @data:    allocated pool memory
> > > > > > > - *
> > > > > > > - * U-Boot services each UEFI AllocatePool() request as a separate
> > > > > > > - * (multiple) page allocation. We have to track the number of pages
> > > > > > > - * to be able to free the correct amount later.
> > > > > > > - *
> > > > > > > - * The checksum calculated in function checksum() is used in FreePool() to avoid
> > > > > > > - * freeing memory not allocated by AllocatePool() and duplicate freeing.
> > > > > > > - *
> > > > > > > - * EFI requires 8 byte alignment for pool allocations, so we can
> > > > > > > - * prepend each allocation with these header fields.
> > > > > > > - */
> > > > > > > -struct efi_pool_allocation {
> > > > > > > -     u64 num_pages;
> > > > > > > -     u64 checksum;
> > > > > > > -     char data[] __aligned(ARCH_DMA_MINALIGN);
> > > > > > > -};
> > > > > > > -
> > > > > > > -/**
> > > > > > > - * checksum() - calculate checksum for memory allocated from pool
> > > > > > > - *
> > > > > > > - * @alloc:   allocation header
> > > > > > > - * Return:   checksum, always non-zero
> > > > > > > - */
> > > > > > > -static u64 checksum(struct efi_pool_allocation *alloc)
> > > > > > > -{
> > > > > > > -     u64 addr = (uintptr_t)alloc;
> > > > > > > -     u64 ret = (addr >> 32) ^ (addr << 32) ^ alloc->num_pages ^
> > > > > > > -               EFI_ALLOC_POOL_MAGIC;
> > > > > > > -     if (!ret)
> > > > > > > -             ++ret;
> > > > > > > -     return ret;
> > > > > > > -}
> > > > > > > -
> > > > > > >   /**
> > > > > > >    * efi_mem_cmp() - comparator function for sorting memory map
> > > > > > >    *
> > > > > > > @@ -681,13 +642,10 @@ void *efi_alloc_aligned_pages(u64 len, int memory_type, size_t align)
> > > > > > >    * @buffer: allocated memory
> > > > > > >    * Return:  status code
> > > > > > >    */
> > > > > > > -efi_status_t efi_allocate_pool(enum efi_memory_type pool_type, efi_uintn_t size, void **buffer)
> > > > > > > +efi_status_t efi_allocate_pool(enum efi_memory_type pool_type, efi_uintn_t size,
> > > > > > > +                            void **buffer)
> > > > > > >   {
> > > > > > > -     efi_status_t r;
> > > > > > > -     u64 addr;
> > > > > > > -     struct efi_pool_allocation *alloc;
> > > > > > > -     u64 num_pages = efi_size_in_pages(size +
> > > > > > > -                                       sizeof(struct efi_pool_allocation));
> > > > > > > +     void *ptr;
> > > > > > >
> > > > > > >       if (!check_allowed())
> > > > > > >               return EFI_UNSUPPORTED;
> > > > > > > @@ -700,16 +658,21 @@ efi_status_t efi_allocate_pool(enum efi_memory_type pool_type, efi_uintn_t size,
> > > > > > >               return EFI_SUCCESS;
> > > > > > >       }
> > > > > >
> > > > > > Unfortunately this patch does not match the UEFI specification:
> > > > > >
> > > > > > Two different memory types cannot reside in the same memory page. You
> > > > > > have to keep track of the memory type of each allocated memory page and
> > > > > > report it in GetMemoryMap().
> > > > >
> > > > > I actually hadn't thought about that. Is it implied in the spec or
> > > > > actually stated somewhere?
> > > >
> > > > I don't remember if it's explicitly stated, but it *really* doesn't
> > > > have to be. Although u-boot doesn't currently support this on modern
> > > > secure systems you can't have code and data mixed on the same page.
> > > > This would prevent you to map pages with proper permissions and take
> > > > advantage of CPU security features  e.g RW^X.
> > >
> > > As it stands, U-Boot's data is reported as EFI_BOOT_SERVICES_CODE
> > > through EFI. Perhaps the difference between EFI_BOOT_SERVICES_CODE and
> > > EFI_BOOT_SERVICES_DATA isn't that important?
> > >
> > > But if we did want to fix that, we could make the malloc region
> > > EFI_BOOT_SERVICES_DATA.
> > >
> > > >
> > > > In any case, the AllocatePool description reads "This function
> > > > allocates pages from EfiConventionalMemory as needed to grow the
> > > > requested pool type. All allocations are eight-byte aligned". I don't
> > > > think using malloc in AllocatePool is appropriate. If we ever want to
> > > > do that and allocate from the malloc space, we need to teach malloc
> > > > some EFI semantics, but that's a really bad idea.
> > >
> > > Here I don't see the difference between EFI's malloc(n) and
> > > memalign(8, n). I do see a lot of comments like 'use the spec', 'read
> > > the spec'. It seems very clear to me that the bootloader can use
> > > whatever algorithm it likes to provide the allocated memory for the
> > > pool.
> > >
> > > What sort of EFI semantics are you referring to?
> >
> > the memory type. Your patch removes the call to efi_allocate_pages
> > which tracks it.
>
> I can put that code back, depending on what we decide below.
>
> >
> > >
> > > >
> > > > >
> > > > > Anyway, from what I can tell, we mostly use EFI_BOOT_SERVICES_DATA.
> > > > > The only exception is EFI_RUNTIME_SERVICES_DATA for the runtime stuff.
> > > >
> > > > That's not entirely correct, we use a lot more than these 2.
> > > > EFI_LOADER_DATA, EFI_LOADER_CODE and EFI_ACPI_RECLAIM_MEMORY from a
> > > > quick grep
> > >
> > > Yes I did a quick grep too, but then checked each site. The first two
> > > are used in an EFI app, not U-Boot itself. The ACPI one is not an
> > > allocation.
> > >
> > > > >
> > > > > So perhaps we can use malloc() for EFI_BOOT_SERVICES_DATA allocations
> > > > > and stick with whole pages otherwise? That will mostly fix the problem
> > > > > I am seeing.
> > >
> > > Any comments on this?
> >
> > This violates the spec and probably breaks a few tests and the EFI
> > boot services API, as you are supposed to be able to define the memory
> > type. We might be able to control it from U-Boot, but EFI apps are
> > going to end up being buggy and hard to debug -- e.g an app calls
> > allocatePool to allocate memory that needs to be preserved at runtime.
>
> To be more specific, I am suggesting:
> - use malloc() for EFI_BOOT_SERVICES_DATA allocations (with no memory
> type since it is always that). This should cover all the U-Boot
> allocations and make sure they are safely within the malloc() pool
> - use efi_allocate_pages() for other memory types (keeping the memory
> type in metadata)
>

Personally, I don't see the point and we deviate from the spec as
well. Perhaps Heinrich thinks otherwise, but the EFI spec still says
you need to allocate *pages* to grow the pool. What's missing from our
efi_allocate_pool() is the ability to merge allocations of the same
memory type to an existing pool assuming there's space, rather than
requesting a new pool of 4kb.

Regards
/Ilias


> >
> > You can switch some callsite of efi_allocate_pool to
> > efi_allocate_pages() internally. Will that fix the behavior?
>
> It still allocates memory 'in space' and uses 4KB for each allocation.
>
> Regards,
> Simon
>
>
>
> >
> > Regards
> > /Ilias
> > >
> > > > >
> > > > > >
> > > > > > The specification is available https://uefi.org/specifications.
> > > > >
> > > > > The problem is that it is thousands of pages so it is hard to pick up
> > > > > on some of these issues.
> > > > >
> > > > > >
> > > > > > >
> > > > > > > -     r = efi_allocate_pages(EFI_ALLOCATE_ANY_PAGES, pool_type, num_pages,
> > > > > > > -                            &addr);
> > > > > > > -     if (r == EFI_SUCCESS) {
> > > > > > > -             alloc = (struct efi_pool_allocation *)(uintptr_t)addr;
> > > > > > > -             alloc->num_pages = num_pages;
> > > > > > > -             alloc->checksum = checksum(alloc);
> > > > > > > -             *buffer = alloc->data;
> > > > > > > -     }
> > > > > > > +     /*
> > > > > > > +      * Some tests crash on qemu_arm etc. if the correct size is allocated.
> > > > > > > +      * Adding 0x10 seems to fix test_efi_selftest_device_tree
> > > > > > > +      * Increasing it to 0x20 seems to fix test_efi_selftest_base except
> > > > > > > +      * for riscv64 (in CI only). But 0x100 fixes CI too.
> > > > > > > +      *
> > > > > > > +      * This workaround can be dropped once these problems are resolved
> > > > > > > +      */
> > > > > > > +     ptr = memalign(8, size + 0x100);
> > > > > >
> > > > > > We were using ARCH_DMA_MINALIGN before this patch.
> > > > >
> > > > > Where is that in the code? All I see is EFI_PAGE_SHIFT.
> > > > >
> > > > > >
> > > > > > If we are overwriting allocated memory, we must fix that instead of
> > > > > > increasing size. Do you have a git tag showing the problem?
> > > > >
> > > > > Not easy to do, because the 4KB per allocation thing has been there
> > > > > from the start. It might be possible to apply my patch to earlier and
> > > > > earlier U-Boots to try to bisect it.
> > >
> > > Would anyone like to take a look at these presumed bugs?
> > >
> > > It might be worth moving to a newer dlmalloc() [1,2] as it has a checker.
> > >
> > > > >
> > > [..]
> > >
> > > Regards,
> > > Simon
> > >
> > > [1] https://gee.cs.oswego.edu/dl/html/malloc.html
> > > [2] https://gee.cs.oswego.edu/pub/misc/malloc.c


More information about the U-Boot mailing list