b1bcb2
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
c4e390
From: Alexander Graf <agraf@suse.de>
c4e390
Date: Thu, 31 Aug 2017 16:40:19 +0200
b1bcb2
Subject: [PATCH] efi: Free malloc regions on exit
c4e390
c4e390
When we exit grub, we don't free all the memory that we allocated earlier
c4e390
for our heap region. This can cause problems with setups where you try
c4e390
to descend the boot order using "exit" entries, such as PXE -> HD boot
c4e390
scenarios.
c4e390
c4e390
Signed-off-by: Alexander Graf <agraf@suse.de>
c4e390
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
c4e390
Upstream-commit-id: 92bfc33db98
c4e390
---
c4e390
 grub-core/kern/efi/init.c |  1 +
b1bcb2
 grub-core/kern/efi/mm.c   | 82 +++++++++++++++++++++++++++++++++++++++++++++++
c4e390
 include/grub/efi/efi.h    |  1 +
c4e390
 3 files changed, 84 insertions(+)
c4e390
c4e390
diff --git a/grub-core/kern/efi/init.c b/grub-core/kern/efi/init.c
c4e390
index c5d8a76144a..e8bf993f6d4 100644
c4e390
--- a/grub-core/kern/efi/init.c
c4e390
+++ b/grub-core/kern/efi/init.c
c4e390
@@ -111,4 +111,5 @@ grub_efi_fini (void)
c4e390
 {
c4e390
   grub_efidisk_fini ();
c4e390
   grub_console_fini ();
c4e390
+  grub_efi_memory_fini ();
c4e390
 }
c4e390
diff --git a/grub-core/kern/efi/mm.c b/grub-core/kern/efi/mm.c
c4e390
index ddeca6073f6..abe9c70557f 100644
c4e390
--- a/grub-core/kern/efi/mm.c
c4e390
+++ b/grub-core/kern/efi/mm.c
c4e390
@@ -49,6 +49,70 @@ static grub_efi_uintn_t finish_desc_size;
c4e390
 static grub_efi_uint32_t finish_desc_version;
c4e390
 int grub_efi_is_finished = 0;
c4e390
 
c4e390
+/*
c4e390
+ * We need to roll back EFI allocations on exit. Remember allocations that
c4e390
+ * we'll free on exit.
c4e390
+ */
c4e390
+struct efi_allocation;
c4e390
+struct efi_allocation {
c4e390
+	grub_efi_physical_address_t address;
c4e390
+	grub_efi_uint64_t pages;
c4e390
+	struct efi_allocation *next;
c4e390
+};
c4e390
+static struct efi_allocation *efi_allocated_memory;
c4e390
+
c4e390
+static void
c4e390
+grub_efi_store_alloc (grub_efi_physical_address_t address,
c4e390
+                         grub_efi_uintn_t pages)
c4e390
+{
c4e390
+  grub_efi_boot_services_t *b;
c4e390
+  struct efi_allocation *alloc;
c4e390
+  grub_efi_status_t status;
c4e390
+
c4e390
+  b = grub_efi_system_table->boot_services;
c4e390
+  status = efi_call_3 (b->allocate_pool, GRUB_EFI_LOADER_DATA,
c4e390
+                           sizeof(*alloc), (void**)&alloc);
c4e390
+
c4e390
+  if (status == GRUB_EFI_SUCCESS)
c4e390
+    {
c4e390
+      alloc->next = efi_allocated_memory;
c4e390
+      alloc->address = address;
c4e390
+      alloc->pages = pages;
c4e390
+      efi_allocated_memory = alloc;
c4e390
+    }
c4e390
+  else
c4e390
+      grub_printf ("Could not malloc memory to remember EFI allocation. "
c4e390
+                   "Exiting GRUB won't free all memory.\n");
c4e390
+}
c4e390
+
c4e390
+static void
c4e390
+grub_efi_drop_alloc (grub_efi_physical_address_t address,
c4e390
+                           grub_efi_uintn_t pages)
c4e390
+{
c4e390
+  struct efi_allocation *ea, *eap;
c4e390
+  grub_efi_boot_services_t *b;
c4e390
+
c4e390
+  b = grub_efi_system_table->boot_services;
c4e390
+
c4e390
+  for (eap = NULL, ea = efi_allocated_memory; ea; eap = ea, ea = ea->next)
c4e390
+    {
c4e390
+      if (ea->address != address || ea->pages != pages)
c4e390
+         continue;
c4e390
+
c4e390
+      /* Remove the current entry from the list. */
c4e390
+      if (eap)
c4e390
+        eap->next = ea->next;
c4e390
+      else
c4e390
+        efi_allocated_memory = ea->next;
c4e390
+
c4e390
+      /* Then free the memory backing it. */
c4e390
+      efi_call_1 (b->free_pool, ea);
c4e390
+
c4e390
+      /* And leave, we're done. */
c4e390
+      break;
c4e390
+    }
c4e390
+}
c4e390
+
c4e390
 /* Allocate pages below a specified address */
c4e390
 void *
c4e390
 grub_efi_allocate_pages_max (grub_efi_physical_address_t max,
c4e390
@@ -78,6 +142,8 @@ grub_efi_allocate_pages_max (grub_efi_physical_address_t max,
c4e390
 	return 0;
c4e390
     }
c4e390
 
c4e390
+  grub_efi_store_alloc (address, pages);
c4e390
+
c4e390
   return (void *) ((grub_addr_t) address);
c4e390
 }
c4e390
 
c4e390
@@ -139,6 +205,8 @@ grub_efi_free_pages (grub_efi_physical_address_t address,
c4e390
 
c4e390
   b = grub_efi_system_table->boot_services;
c4e390
   efi_call_2 (b->free_pages, address, pages);
c4e390
+
c4e390
+  grub_efi_drop_alloc (address, pages);
c4e390
 }
c4e390
 
c4e390
 #if defined (__i386__) || defined (__x86_64__)
c4e390
@@ -446,6 +514,20 @@ add_memory_regions (grub_efi_memory_descriptor_t *memory_map,
c4e390
     grub_fatal ("too little memory");
c4e390
 }
c4e390
 
c4e390
+void
c4e390
+grub_efi_memory_fini (void)
c4e390
+{
c4e390
+  /*
c4e390
+   * Free all stale allocations. grub_efi_free_pages() will remove
c4e390
+   * the found entry from the list and it will always find the first
c4e390
+   * list entry (efi_allocated_memory is the list start). Hence we
c4e390
+   * remove all entries from the list until none is left altogether.
c4e390
+   */
c4e390
+  while (efi_allocated_memory)
c4e390
+      grub_efi_free_pages (efi_allocated_memory->address,
c4e390
+                           efi_allocated_memory->pages);
c4e390
+}
c4e390
+
c4e390
 #if 0
c4e390
 /* Print the memory map.  */
c4e390
 static void
c4e390
diff --git a/include/grub/efi/efi.h b/include/grub/efi/efi.h
c4e390
index 9a2da0eb38d..f761bbb1389 100644
c4e390
--- a/include/grub/efi/efi.h
c4e390
+++ b/include/grub/efi/efi.h
c4e390
@@ -51,6 +51,7 @@ EXPORT_FUNC(grub_efi_get_memory_map) (grub_efi_uintn_t *memory_map_size,
c4e390
 				      grub_efi_uintn_t *map_key,
c4e390
 				      grub_efi_uintn_t *descriptor_size,
c4e390
 				      grub_efi_uint32_t *descriptor_version);
c4e390
+void grub_efi_memory_fini (void);
c4e390
 grub_efi_loaded_image_t *EXPORT_FUNC(grub_efi_get_loaded_image) (grub_efi_handle_t image_handle);
c4e390
 void EXPORT_FUNC(grub_efi_print_device_path) (grub_efi_device_path_t *dp);
c4e390
 char *EXPORT_FUNC(grub_efi_get_filename) (grub_efi_device_path_t *dp);