5c83f50
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
5c83f50
From: Zhang Boyang <zhangboyang.id@gmail.com>
5c83f50
Date: Sun, 29 Jan 2023 19:49:31 +0800
5c83f50
Subject: [PATCH] mm: Adjust new region size to take management overhead into
5c83f50
 account
5c83f50
5c83f50
When grub_memalign() encounters out-of-memory, it will try
5c83f50
grub_mm_add_region_fn() to request more memory from system firmware.
5c83f50
However, the size passed to it doesn't take region management overhead
5c83f50
into account. Adding a memory area of "size" bytes may result in a heap
5c83f50
region of less than "size" bytes really available. Thus, the new region
5c83f50
may not be adequate for current allocation request, confusing
5c83f50
out-of-memory handling code.
5c83f50
5c83f50
This patch introduces GRUB_MM_MGMT_OVERHEAD to address the region
5c83f50
management overhead (e.g. metadata, padding). The value of this new
5c83f50
constant must be large enough to make sure grub_memalign(align, size)
5c83f50
always succeeds after a successful call to
5c83f50
  grub_mm_init_region(addr, size + align + GRUB_MM_MGMT_OVERHEAD),
5c83f50
for any given addr and size (assuming no integer overflow).
5c83f50
5c83f50
The size passed to grub_mm_add_region_fn() is now correctly adjusted,
5c83f50
thus if grub_mm_add_region_fn() succeeded, current allocation request
5c83f50
can always succeed.
5c83f50
5c83f50
Signed-off-by: Zhang Boyang <zhangboyang.id@gmail.com>
5c83f50
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
5c83f50
(cherry picked from commit 2282cbfe5aa1ff6c1bbcbdcd2003089ad7c03ba3)
5c83f50
---
5c83f50
 grub-core/kern/mm.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++---
5c83f50
 1 file changed, 61 insertions(+), 3 deletions(-)
5c83f50
5c83f50
diff --git a/grub-core/kern/mm.c b/grub-core/kern/mm.c
5c83f50
index da1ac9427c..f29a3e5cbd 100644
5c83f50
--- a/grub-core/kern/mm.c
5c83f50
+++ b/grub-core/kern/mm.c
5c83f50
@@ -83,6 +83,46 @@
5c83f50
 
5c83f50
 
5c83f50
 
5c83f50
+/*
5c83f50
+ * GRUB_MM_MGMT_OVERHEAD is an upper bound of management overhead of
5c83f50
+ * each region, with any possible padding taken into account.
5c83f50
+ *
5c83f50
+ * The value must be large enough to make sure grub_memalign(align, size)
5c83f50
+ * always succeeds after a successful call to
5c83f50
+ * grub_mm_init_region(addr, size + align + GRUB_MM_MGMT_OVERHEAD),
5c83f50
+ * for any given addr, align and size (assuming no interger overflow).
5c83f50
+ *
5c83f50
+ * The worst case which has maximum overhead is shown in the figure below:
5c83f50
+ *
5c83f50
+ * +-- addr
5c83f50
+ * v                                           |<- size + align ->|
5c83f50
+ * +---------+----------------+----------------+------------------+---------+
5c83f50
+ * | padding | grub_mm_region | grub_mm_header |   usable bytes   | padding |
5c83f50
+ * +---------+----------------+----------------+------------------+---------+
5c83f50
+ * |<-  a  ->|<-     b      ->|<-     c      ->|<-      d       ->|<-  e  ->|
5c83f50
+ *                                             ^
5c83f50
+ *    b == sizeof (struct grub_mm_region)      | / Assuming no other suitable
5c83f50
+ *    c == sizeof (struct grub_mm_header)      | | block is available, then:
5c83f50
+ *    d == size + align                        +-| If align == 0, this will be
5c83f50
+ *                                               | the pointer returned by next
5c83f50
+ * Assuming addr % GRUB_MM_ALIGN == 1, then:     | grub_memalign(align, size).
5c83f50
+ *    a == GRUB_MM_ALIGN - 1                     | If align > 0, this chunk may
5c83f50
+ *                                               | need to be split to fulfill
5c83f50
+ * Assuming d % GRUB_MM_ALIGN == 1, then:        | alignment requirements, and
5c83f50
+ *    e == GRUB_MM_ALIGN - 1                     | the returned pointer may be
5c83f50
+ *                                               \ inside these usable bytes.
5c83f50
+ * Therefore, the maximum overhead is:
5c83f50
+ *    a + b + c + e == (GRUB_MM_ALIGN - 1) + sizeof (struct grub_mm_region)
5c83f50
+ *                     + sizeof (struct grub_mm_header) + (GRUB_MM_ALIGN - 1)
5c83f50
+ */
5c83f50
+#define GRUB_MM_MGMT_OVERHEAD	((GRUB_MM_ALIGN - 1) \
5c83f50
+				 + sizeof (struct grub_mm_region) \
5c83f50
+				 + sizeof (struct grub_mm_header) \
5c83f50
+				 + (GRUB_MM_ALIGN - 1))
5c83f50
+
5c83f50
+/* The size passed to grub_mm_add_region_fn() is aligned up by this value. */
5c83f50
+#define GRUB_MM_HEAP_GROW_ALIGN	4096
5c83f50
+
5c83f50
 grub_mm_region_t grub_mm_base;
5c83f50
 grub_mm_add_region_func_t grub_mm_add_region_fn;
5c83f50
 
5c83f50
@@ -230,6 +270,11 @@ grub_mm_init_region (void *addr, grub_size_t size)
5c83f50
 
5c83f50
   grub_dprintf ("regions", "No: considering a new region at %p of size %" PRIxGRUB_SIZE "\n",
5c83f50
 		addr, size);
5c83f50
+  /*
5c83f50
+   * If you want to modify the code below, please also take a look at
5c83f50
+   * GRUB_MM_MGMT_OVERHEAD and make sure it is synchronized with the code.
5c83f50
+   */
5c83f50
+
5c83f50
   /* Allocate a region from the head.  */
5c83f50
   r = (grub_mm_region_t) ALIGN_UP ((grub_addr_t) addr, GRUB_MM_ALIGN);
5c83f50
 
5c83f50
@@ -410,6 +455,7 @@ grub_memalign (grub_size_t align, grub_size_t size)
5c83f50
 {
5c83f50
   grub_mm_region_t r;
5c83f50
   grub_size_t n = ((size + GRUB_MM_ALIGN - 1) >> GRUB_MM_ALIGN_LOG2) + 1;
5c83f50
+  grub_size_t grow;
5c83f50
   int count = 0;
5c83f50
 
5c83f50
   if (!grub_mm_base)
5c83f50
@@ -418,10 +464,22 @@ grub_memalign (grub_size_t align, grub_size_t size)
5c83f50
   if (size > ~(grub_size_t) align)
5c83f50
     goto fail;
5c83f50
 
5c83f50
+  /*
5c83f50
+   * Pre-calculate the necessary size of heap growth (if applicable),
5c83f50
+   * with region management overhead taken into account.
5c83f50
+   */
5c83f50
+  if (grub_add (size + align, GRUB_MM_MGMT_OVERHEAD, &grow))
5c83f50
+    goto fail;
5c83f50
+
5c83f50
+  /* Align up heap growth to make it friendly to CPU/MMU. */
5c83f50
+  if (grow > ~(grub_size_t) (GRUB_MM_HEAP_GROW_ALIGN - 1))
5c83f50
+    goto fail;
5c83f50
+  grow = ALIGN_UP (grow, GRUB_MM_HEAP_GROW_ALIGN);
5c83f50
+
5c83f50
   /* We currently assume at least a 32-bit grub_size_t,
5c83f50
      so limiting allocations to <adress space size> - 1MiB
5c83f50
      in name of sanity is beneficial. */
5c83f50
-  if ((size + align) > ~(grub_size_t) 0x100000)
5c83f50
+  if (grow > ~(grub_size_t) 0x100000)
5c83f50
     goto fail;
5c83f50
 
5c83f50
   align = (align >> GRUB_MM_ALIGN_LOG2);
5c83f50
@@ -447,7 +505,7 @@ grub_memalign (grub_size_t align, grub_size_t size)
5c83f50
       count++;
5c83f50
 
5c83f50
       if (grub_mm_add_region_fn != NULL &&
5c83f50
-          grub_mm_add_region_fn (size, GRUB_MM_ADD_REGION_CONSECUTIVE) == GRUB_ERR_NONE)
5c83f50
+          grub_mm_add_region_fn (grow, GRUB_MM_ADD_REGION_CONSECUTIVE) == GRUB_ERR_NONE)
5c83f50
 	goto again;
5c83f50
 
5c83f50
       /* fallthrough  */
5c83f50
@@ -462,7 +520,7 @@ grub_memalign (grub_size_t align, grub_size_t size)
5c83f50
            * Try again even if this fails, in case it was able to partially
5c83f50
            * satisfy the request
5c83f50
            */
5c83f50
-          grub_mm_add_region_fn (size, GRUB_MM_ADD_REGION_NONE);
5c83f50
+          grub_mm_add_region_fn (grow, GRUB_MM_ADD_REGION_NONE);
5c83f50
           goto again;
5c83f50
         }
5c83f50