When grub_memalign() encounters out-of-memory, it will try grub_mm_add_region_fn() to request more memory from system firmware. However, the size passed to it doesn't take region management cost into account. Adding a memory area of `size` bytes will result in a heap region of less than `size` bytes truely avaliable. Thus, the new region might not adequate for current allocation request, confusing out-of-memory handling code.
This patch introduces GRUB_MM_MAX_COST to address the region management cost (e.g. metadata, alignment). The value of this new constant should make `grub_malloc (size)` always success after a successful call to `grub_mm_init_region (addr, size + GRUB_MM_MAX_COST)`. The new region size is now set to `size + GRUB_MM_MAX_COST`, thus if grub_mm_add_region_fn() succeeded, current allocation request can always success. Signed-off-by: Zhang Boyang <zhangboyang...@gmail.com> --- grub-core/kern/mm.c | 15 ++++++++++++--- include/grub/mm_private.h | 9 +++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/grub-core/kern/mm.c b/grub-core/kern/mm.c index ae2279133..973cb6b15 100644 --- a/grub-core/kern/mm.c +++ b/grub-core/kern/mm.c @@ -410,7 +410,9 @@ grub_memalign (grub_size_t align, grub_size_t size) { grub_mm_region_t r; grub_size_t n = ((size + GRUB_MM_ALIGN - 1) >> GRUB_MM_ALIGN_LOG2) + 1; + grub_size_t bound; int count = 0; + grub_size_t grow; if (!grub_mm_base) goto fail; @@ -418,10 +420,13 @@ grub_memalign (grub_size_t align, grub_size_t size) if (size > ~(grub_size_t) align) goto fail; + /* If largest free chunk in region >= bound, allocation can always success */ + bound = size + align; + /* We currently assume at least a 32-bit grub_size_t, so limiting allocations to <adress space size> - 1MiB in name of sanity is beneficial. */ - if ((size + align) > ~(grub_size_t) 0x100000) + if (bound > ~(grub_size_t) 0x100000) goto fail; align = (align >> GRUB_MM_ALIGN_LOG2); @@ -443,11 +448,15 @@ grub_memalign (grub_size_t align, grub_size_t size) switch (count) { case 0: + /* Adjust heap growth to address the region management cost. */ + if (grub_add (bound, GRUB_MM_MAX_COST, &grow)) + goto fail; + /* Request additional pages, contiguous */ count++; if (grub_mm_add_region_fn != NULL && - grub_mm_add_region_fn (size, GRUB_MM_ADD_REGION_CONSECUTIVE) == GRUB_ERR_NONE) + grub_mm_add_region_fn (grow, GRUB_MM_ADD_REGION_CONSECUTIVE) == GRUB_ERR_NONE) goto again; /* fallthrough */ @@ -462,7 +471,7 @@ grub_memalign (grub_size_t align, grub_size_t size) * Try again even if this fails, in case it was able to partially * satisfy the request */ - grub_mm_add_region_fn (size, GRUB_MM_ADD_REGION_NONE); + grub_mm_add_region_fn (grow, GRUB_MM_ADD_REGION_NONE); goto again; } diff --git a/include/grub/mm_private.h b/include/grub/mm_private.h index 96c2d816b..f212110e4 100644 --- a/include/grub/mm_private.h +++ b/include/grub/mm_private.h @@ -95,6 +95,15 @@ typedef struct grub_mm_region } *grub_mm_region_t; +/* + * Set an upper bound of management cost of each region, + * with sizeof(struct grub_mm_region), sizeof(struct grub_mm_header) and + * any possible alignment or padding taken into account. + * The value should make `grub_malloc (size)` always success after a successful + * call to `grub_mm_init_region (addr, size + GRUB_MM_MAX_COST)`. + */ +#define GRUB_MM_MAX_COST 0x1000 + #ifndef GRUB_MACHINE_EMU extern grub_mm_region_t EXPORT_VAR (grub_mm_base); #endif -- 2.30.2 _______________________________________________ Grub-devel mailing list Grub-devel@gnu.org https://lists.gnu.org/mailman/listinfo/grub-devel