From 431a111c60095fc973d83fe9209f26f29ce78784 Mon Sep 17 00:00:00 2001
From: Hitendra Prajapati <hprajapati@mvista.com>
Date: Mon, 1 Aug 2022 11:17:17 +0530
Subject: [PATCH] CVE-2022-28736

Upstream-Status: Backport [https://git.savannah.gnu.org/gitweb/?p=grub.git;a=commit;h=04c86e0bb7b58fc2f913f798cdb18934933e532d]
CVE: CVE-2022-28736
Signed-off-by: Hitendra Prajapati <hprajapati@mvista.com>

loader/efi/chainloader: Use grub_loader_set_ex()

This ports the EFI chainloader to use grub_loader_set_ex() in order to fix
a use-after-free bug that occurs when grub_cmd_chainloader() is executed
more than once before a boot attempt is performed.

Fixes: CVE-2022-28736

Signed-off-by: Chris Coulson <chris.coulson@canonical.com>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
---
 grub-core/commands/boot.c          | 66 ++++++++++++++++++++++++++----
 grub-core/loader/efi/chainloader.c | 46 +++++++++++----------
 include/grub/loader.h              |  5 +++
 3 files changed, 87 insertions(+), 30 deletions(-)

diff --git a/grub-core/commands/boot.c b/grub-core/commands/boot.c
index bbca81e..6151478 100644
--- a/grub-core/commands/boot.c
+++ b/grub-core/commands/boot.c
@@ -27,10 +27,20 @@
 
 GRUB_MOD_LICENSE ("GPLv3+");
 
-static grub_err_t (*grub_loader_boot_func) (void);
-static grub_err_t (*grub_loader_unload_func) (void);
+static grub_err_t (*grub_loader_boot_func) (void *context);
+static grub_err_t (*grub_loader_unload_func) (void *context);
+static void *grub_loader_context;
 static int grub_loader_flags;
 
+struct grub_simple_loader_hooks
+{
+  grub_err_t (*boot) (void);
+  grub_err_t (*unload) (void);
+};
+
+/* Don't heap allocate this to avoid making grub_loader_set() fallible. */
+static struct grub_simple_loader_hooks simple_loader_hooks;
+
 struct grub_preboot
 {
   grub_err_t (*preboot_func) (int);
@@ -44,6 +54,29 @@ static int grub_loader_loaded;
 static struct grub_preboot *preboots_head = 0,
   *preboots_tail = 0;
 
+static grub_err_t
+grub_simple_boot_hook (void *context)
+{
+  struct grub_simple_loader_hooks *hooks;
+
+  hooks = (struct grub_simple_loader_hooks *) context;
+  return hooks->boot ();
+}
+
+static grub_err_t
+grub_simple_unload_hook (void *context)
+{
+  struct grub_simple_loader_hooks *hooks;
+  grub_err_t ret;
+
+  hooks = (struct grub_simple_loader_hooks *) context;
+
+  ret = hooks->unload ();
+  grub_memset (hooks, 0, sizeof (*hooks));
+
+  return ret;
+}
+
 int
 grub_loader_is_loaded (void)
 {
@@ -110,28 +143,45 @@ grub_loader_unregister_preboot_hook (struct grub_preboot *hnd)
 }
 
 void
-grub_loader_set (grub_err_t (*boot) (void),
-		 grub_err_t (*unload) (void),
-		 int flags)
+grub_loader_set_ex (grub_err_t (*boot) (void *context),
+		    grub_err_t (*unload) (void *context),
+		    void *context,
+		    int flags)
 {
   if (grub_loader_loaded && grub_loader_unload_func)
-    grub_loader_unload_func ();
+    grub_loader_unload_func (grub_loader_context);
 
   grub_loader_boot_func = boot;
   grub_loader_unload_func = unload;
+  grub_loader_context = context;
   grub_loader_flags = flags;
 
   grub_loader_loaded = 1;
 }
 
+void
+grub_loader_set (grub_err_t (*boot) (void),
+		 grub_err_t (*unload) (void),
+		 int flags)
+{
+  grub_loader_set_ex (grub_simple_boot_hook,
+		      grub_simple_unload_hook,
+		      &simple_loader_hooks,
+		      flags);
+
+  simple_loader_hooks.boot = boot;
+  simple_loader_hooks.unload = unload;
+}
+
 void
 grub_loader_unset(void)
 {
   if (grub_loader_loaded && grub_loader_unload_func)
-    grub_loader_unload_func ();
+    grub_loader_unload_func (grub_loader_context);
 
   grub_loader_boot_func = 0;
   grub_loader_unload_func = 0;
+  grub_loader_context = 0;
 
   grub_loader_loaded = 0;
 }
@@ -158,7 +208,7 @@ grub_loader_boot (void)
 	  return err;
 	}
     }
-  err = (grub_loader_boot_func) ();
+  err = (grub_loader_boot_func) (grub_loader_context);
 
   for (cur = preboots_tail; cur; cur = cur->prev)
     if (! err)
diff --git a/grub-core/loader/efi/chainloader.c b/grub-core/loader/efi/chainloader.c
index a8d7b91..93a028a 100644
--- a/grub-core/loader/efi/chainloader.c
+++ b/grub-core/loader/efi/chainloader.c
@@ -44,33 +44,28 @@ GRUB_MOD_LICENSE ("GPLv3+");
 
 static grub_dl_t my_mod;
 
-static grub_efi_physical_address_t address;
-static grub_efi_uintn_t pages;
-static grub_efi_device_path_t *file_path;
-static grub_efi_handle_t image_handle;
-static grub_efi_char16_t *cmdline;
-
 static grub_err_t
-grub_chainloader_unload (void)
+grub_chainloader_unload (void *context)
 {
+  grub_efi_handle_t image_handle = (grub_efi_handle_t) context;
+  grub_efi_loaded_image_t *loaded_image;
   grub_efi_boot_services_t *b;
 
+  loaded_image = grub_efi_get_loaded_image (image_handle);
+  if (loaded_image != NULL)
+    grub_free (loaded_image->load_options);
+
   b = grub_efi_system_table->boot_services;
   efi_call_1 (b->unload_image, image_handle);
-  efi_call_2 (b->free_pages, address, pages);
-
-  grub_free (file_path);
-  grub_free (cmdline);
-  cmdline = 0;
-  file_path = 0;
 
   grub_dl_unref (my_mod);
   return GRUB_ERR_NONE;
 }
 
 static grub_err_t
-grub_chainloader_boot (void)
+grub_chainloader_boot (void *context)
 {
+  grub_efi_handle_t image_handle = (grub_efi_handle_t) context;
   grub_efi_boot_services_t *b;
   grub_efi_status_t status;
   grub_efi_uintn_t exit_data_size;
@@ -139,7 +134,7 @@ make_file_path (grub_efi_device_path_t *dp, const char *filename)
   char *dir_start;
   char *dir_end;
   grub_size_t size;
-  grub_efi_device_path_t *d;
+  grub_efi_device_path_t *d, *file_path;
 
   dir_start = grub_strchr (filename, ')');
   if (! dir_start)
@@ -215,11 +210,15 @@ grub_cmd_chainloader (grub_command_t cmd __attribute__ ((unused)),
   grub_efi_status_t status;
   grub_efi_boot_services_t *b;
   grub_device_t dev = 0;
-  grub_efi_device_path_t *dp = 0;
+  grub_efi_device_path_t *dp = NULL, *file_path = NULL;
   grub_efi_loaded_image_t *loaded_image;
   char *filename;
   void *boot_image = 0;
   grub_efi_handle_t dev_handle = 0;
+  grub_efi_physical_address_t address = 0;
+  grub_efi_uintn_t pages = 0;
+  grub_efi_char16_t *cmdline = NULL;
+  grub_efi_handle_t image_handle = NULL;
 
   if (argc == 0)
     return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
@@ -227,11 +226,6 @@ grub_cmd_chainloader (grub_command_t cmd __attribute__ ((unused)),
 
   grub_dl_ref (my_mod);
 
-  /* Initialize some global variables.  */
-  address = 0;
-  image_handle = 0;
-  file_path = 0;
-
   b = grub_efi_system_table->boot_services;
 
   file = grub_file_open (filename, GRUB_FILE_TYPE_EFI_CHAINLOADED_IMAGE);
@@ -401,7 +395,11 @@ grub_cmd_chainloader (grub_command_t cmd __attribute__ ((unused)),
   grub_file_close (file);
   grub_device_close (dev);
 
-  grub_loader_set (grub_chainloader_boot, grub_chainloader_unload, 0);
+  /* We're finished with the source image buffer and file path now. */
+  efi_call_2 (b->free_pages, address, pages);
+  grub_free (file_path);
+
+  grub_loader_set_ex (grub_chainloader_boot, grub_chainloader_unload, image_handle, 0);
   return 0;
 
  fail:
@@ -412,11 +410,15 @@ grub_cmd_chainloader (grub_command_t cmd __attribute__ ((unused)),
   if (file)
     grub_file_close (file);
 
+  grub_free (cmdline);
   grub_free (file_path);
 
   if (address)
     efi_call_2 (b->free_pages, address, pages);
 
+  if (image_handle != NULL)
+    efi_call_1 (b->unload_image, image_handle);
+
   grub_dl_unref (my_mod);
 
   return grub_errno;
diff --git a/include/grub/loader.h b/include/grub/loader.h
index 7f82a49..3071a50 100644
--- a/include/grub/loader.h
+++ b/include/grub/loader.h
@@ -39,6 +39,11 @@ void EXPORT_FUNC (grub_loader_set) (grub_err_t (*boot) (void),
 				    grub_err_t (*unload) (void),
 				    int flags);
 
+void EXPORT_FUNC (grub_loader_set_ex) (grub_err_t (*boot) (void *context),
+				       grub_err_t (*unload) (void *context),
+				       void *context,
+				       int flags);
+
 /* Unset current loader, if any.  */
 void EXPORT_FUNC (grub_loader_unset) (void);
 
-- 
2.25.1

