f0ad2aa
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
f0ad2aa
From: Chris Coulson <chris.coulson@canonical.com>
f0ad2aa
Date: Fri, 29 Apr 2022 21:13:08 +0100
f0ad2aa
Subject: [PATCH] loader/efi/chainloader: simplify the loader state
f0ad2aa
f0ad2aa
When not using the shim lock protocol, the chainloader command retains
f0ad2aa
the source buffer and device path passed to LoadImage, requiring the
f0ad2aa
unload hook passed to grub_loader_set to free them. It isn't required
f0ad2aa
to retain this state though - they aren't required by StartImage or
f0ad2aa
anything else in the boot hook, so clean them up before
f0ad2aa
grub_cmd_chainloader finishes.
f0ad2aa
f0ad2aa
This also wraps the loader state when using the shim lock protocol
f0ad2aa
inside a struct.
f0ad2aa
f0ad2aa
Signed-off-by: Chris Coulson <chris.coulson@canonical.com>
f0ad2aa
(cherry picked from commit fa39862933b3be1553a580a3a5c28073257d8046)
f0ad2aa
[rharwood: fix unitialized handle and double-frees of file/dev]
f0ad2aa
Signed-off-by: Robbie Harwood <rharwood@redhat.com>
f0ad2aa
---
f0ad2aa
 grub-core/loader/efi/chainloader.c | 160 +++++++++++++++++++++++--------------
f0ad2aa
 1 file changed, 102 insertions(+), 58 deletions(-)
f0ad2aa
f0ad2aa
diff --git a/grub-core/loader/efi/chainloader.c b/grub-core/loader/efi/chainloader.c
f0ad2aa
index d3bf02ed8a..3342492ff1 100644
f0ad2aa
--- a/grub-core/loader/efi/chainloader.c
f0ad2aa
+++ b/grub-core/loader/efi/chainloader.c
f0ad2aa
@@ -48,38 +48,21 @@ GRUB_MOD_LICENSE ("GPLv3+");
f0ad2aa
 
f0ad2aa
 static grub_dl_t my_mod;
f0ad2aa
 
f0ad2aa
-static grub_efi_physical_address_t address;
f0ad2aa
-static grub_efi_uintn_t pages;
f0ad2aa
-static grub_ssize_t fsize;
f0ad2aa
-static grub_efi_device_path_t *file_path;
f0ad2aa
 static grub_efi_handle_t image_handle;
f0ad2aa
-static grub_efi_char16_t *cmdline;
f0ad2aa
-static grub_ssize_t cmdline_len;
f0ad2aa
-static grub_efi_handle_t dev_handle;
f0ad2aa
 
f0ad2aa
-static grub_efi_status_t (*entry_point) (grub_efi_handle_t image_handle, grub_efi_system_table_t *system_table);
f0ad2aa
+struct grub_secureboot_chainloader_context {
f0ad2aa
+  grub_efi_physical_address_t address;
f0ad2aa
+  grub_efi_uintn_t pages;
f0ad2aa
+  grub_ssize_t fsize;
f0ad2aa
+  grub_efi_device_path_t *file_path;
f0ad2aa
+  grub_efi_char16_t *cmdline;
f0ad2aa
+  grub_ssize_t cmdline_len;
f0ad2aa
+  grub_efi_handle_t dev_handle;
f0ad2aa
+};
f0ad2aa
+static struct grub_secureboot_chainloader_context *sb_context;
f0ad2aa
 
f0ad2aa
 static grub_err_t
f0ad2aa
-grub_chainloader_unload (void)
f0ad2aa
-{
f0ad2aa
-  grub_efi_boot_services_t *b;
f0ad2aa
-
f0ad2aa
-  b = grub_efi_system_table->boot_services;
f0ad2aa
-  efi_call_1 (b->unload_image, image_handle);
f0ad2aa
-  grub_efi_free_pages (address, pages);
f0ad2aa
-
f0ad2aa
-  grub_free (file_path);
f0ad2aa
-  grub_free (cmdline);
f0ad2aa
-  cmdline = 0;
f0ad2aa
-  file_path = 0;
f0ad2aa
-  dev_handle = 0;
f0ad2aa
-
f0ad2aa
-  grub_dl_unref (my_mod);
f0ad2aa
-  return GRUB_ERR_NONE;
f0ad2aa
-}
f0ad2aa
-
f0ad2aa
-static grub_err_t
f0ad2aa
-grub_chainloader_boot (void)
f0ad2aa
+grub_start_image (grub_efi_handle_t handle)
f0ad2aa
 {
f0ad2aa
   grub_efi_boot_services_t *b;
f0ad2aa
   grub_efi_status_t status;
f0ad2aa
@@ -87,7 +70,7 @@ grub_chainloader_boot (void)
f0ad2aa
   grub_efi_char16_t *exit_data = NULL;
f0ad2aa
 
f0ad2aa
   b = grub_efi_system_table->boot_services;
f0ad2aa
-  status = efi_call_3 (b->start_image, image_handle, &exit_data_size, &exit_data);
f0ad2aa
+  status = efi_call_3 (b->start_image, handle, &exit_data_size, &exit_data);
f0ad2aa
   if (status != GRUB_EFI_SUCCESS)
f0ad2aa
     {
f0ad2aa
       if (exit_data)
f0ad2aa
@@ -111,11 +94,37 @@ grub_chainloader_boot (void)
f0ad2aa
   if (exit_data)
f0ad2aa
     grub_efi_free_pool (exit_data);
f0ad2aa
 
f0ad2aa
-  grub_loader_unset ();
f0ad2aa
-
f0ad2aa
   return grub_errno;
f0ad2aa
 }
f0ad2aa
 
f0ad2aa
+static grub_err_t
f0ad2aa
+grub_chainloader_unload (void)
f0ad2aa
+{
f0ad2aa
+  grub_efi_loaded_image_t *loaded_image;
f0ad2aa
+  grub_efi_boot_services_t *b;
f0ad2aa
+
f0ad2aa
+  loaded_image = grub_efi_get_loaded_image (image_handle);
f0ad2aa
+  if (loaded_image != NULL)
f0ad2aa
+    grub_free (loaded_image->load_options);
f0ad2aa
+
f0ad2aa
+  b = grub_efi_system_table->boot_services;
f0ad2aa
+  efi_call_1 (b->unload_image, image_handle);
f0ad2aa
+
f0ad2aa
+  grub_dl_unref (my_mod);
f0ad2aa
+  return GRUB_ERR_NONE;
f0ad2aa
+}
f0ad2aa
+
f0ad2aa
+static grub_err_t
f0ad2aa
+grub_chainloader_boot (void)
f0ad2aa
+{
f0ad2aa
+  grub_err_t err;
f0ad2aa
+
f0ad2aa
+  err = grub_start_image (image_handle);
f0ad2aa
+
f0ad2aa
+  grub_loader_unset ();
f0ad2aa
+  return err;
f0ad2aa
+}
f0ad2aa
+
f0ad2aa
 static grub_err_t
f0ad2aa
 copy_file_path (grub_efi_file_path_device_path_t *fp,
f0ad2aa
 		const char *str, grub_efi_uint16_t len)
f0ad2aa
@@ -150,7 +159,7 @@ make_file_path (grub_efi_device_path_t *dp, const char *filename)
f0ad2aa
   char *dir_start;
f0ad2aa
   char *dir_end;
f0ad2aa
   grub_size_t size;
f0ad2aa
-  grub_efi_device_path_t *d;
f0ad2aa
+  grub_efi_device_path_t *d, *file_path;
f0ad2aa
 
f0ad2aa
   dir_start = grub_strchr (filename, ')');
f0ad2aa
   if (! dir_start)
f0ad2aa
@@ -526,10 +535,12 @@ grub_efi_get_media_file_path (grub_efi_device_path_t *dp)
f0ad2aa
 }
f0ad2aa
 
f0ad2aa
 static grub_efi_boolean_t
f0ad2aa
-handle_image (void *data, grub_efi_uint32_t datasize)
f0ad2aa
+handle_image (struct grub_secureboot_chainloader_context *load_context)
f0ad2aa
 {
f0ad2aa
   grub_efi_loaded_image_t *li, li_bak;
f0ad2aa
   grub_efi_status_t efi_status;
f0ad2aa
+  void *data = (void *)(unsigned long)load_context->address;
f0ad2aa
+  grub_efi_uint32_t datasize = load_context->fsize;
f0ad2aa
   void *buffer = NULL;
f0ad2aa
   char *buffer_aligned = NULL;
f0ad2aa
   grub_efi_uint32_t i;
f0ad2aa
@@ -540,6 +551,7 @@ handle_image (void *data, grub_efi_uint32_t datasize)
f0ad2aa
   grub_uint32_t buffer_size;
f0ad2aa
   int found_entry_point = 0;
f0ad2aa
   int rc;
f0ad2aa
+  grub_efi_status_t (*entry_point) (grub_efi_handle_t image_handle, grub_efi_system_table_t *system_table);
f0ad2aa
 
f0ad2aa
   rc = read_header (data, datasize, &context);
f0ad2aa
   if (rc < 0)
f0ad2aa
@@ -797,10 +809,10 @@ handle_image (void *data, grub_efi_uint32_t datasize)
f0ad2aa
   grub_memcpy (&li_bak, li, sizeof (grub_efi_loaded_image_t));
f0ad2aa
   li->image_base = buffer_aligned;
f0ad2aa
   li->image_size = context.image_size;
f0ad2aa
-  li->load_options = cmdline;
f0ad2aa
-  li->load_options_size = cmdline_len;
f0ad2aa
-  li->file_path = grub_efi_get_media_file_path (file_path);
f0ad2aa
-  li->device_handle = dev_handle;
f0ad2aa
+  li->load_options = load_context->cmdline;
f0ad2aa
+  li->load_options_size = load_context->cmdline_len;
f0ad2aa
+  li->file_path = grub_efi_get_media_file_path (load_context->file_path);
f0ad2aa
+  li->device_handle = load_context->dev_handle;
f0ad2aa
   if (!li->file_path)
f0ad2aa
     {
f0ad2aa
       grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no matching file path found");
f0ad2aa
@@ -829,19 +841,22 @@ error_exit:
f0ad2aa
 static grub_err_t
f0ad2aa
 grub_secureboot_chainloader_unload (void)
f0ad2aa
 {
f0ad2aa
-  grub_efi_free_pages (address, pages);
f0ad2aa
-  grub_free (file_path);
f0ad2aa
-  grub_free (cmdline);
f0ad2aa
-  cmdline = 0;
f0ad2aa
-  file_path = 0;
f0ad2aa
-  dev_handle = 0;
f0ad2aa
+  grub_efi_free_pages (sb_context->address, sb_context->pages);
f0ad2aa
+  grub_free (sb_context->file_path);
f0ad2aa
+  grub_free (sb_context->cmdline);
f0ad2aa
+  grub_free (sb_context);
f0ad2aa
+
f0ad2aa
+  sb_context = 0;
f0ad2aa
 
f0ad2aa
   grub_dl_unref (my_mod);
f0ad2aa
   return GRUB_ERR_NONE;
f0ad2aa
 }
f0ad2aa
 
f0ad2aa
 static grub_err_t
f0ad2aa
-grub_load_image(void *boot_image)
f0ad2aa
+grub_load_image(grub_efi_device_path_t *file_path, void *boot_image,
f0ad2aa
+		grub_efi_uintn_t image_size, grub_efi_handle_t dev_handle,
f0ad2aa
+		grub_efi_char16_t *cmdline, grub_ssize_t cmdline_len,
f0ad2aa
+		grub_efi_handle_t *image_handle_out)
f0ad2aa
 {
f0ad2aa
   grub_efi_boot_services_t *b;
f0ad2aa
   grub_efi_status_t status;
f0ad2aa
@@ -850,7 +865,7 @@ grub_load_image(void *boot_image)
f0ad2aa
   b = grub_efi_system_table->boot_services;
f0ad2aa
 
f0ad2aa
   status = efi_call_6 (b->load_image, 0, grub_efi_image_handle, file_path,
f0ad2aa
-		       boot_image, fsize, &image_handle);
f0ad2aa
+		       boot_image, image_size, image_handle_out);
f0ad2aa
   if (status != GRUB_EFI_SUCCESS)
f0ad2aa
     {
f0ad2aa
       if (status == GRUB_EFI_OUT_OF_RESOURCES)
f0ad2aa
@@ -863,7 +878,7 @@ grub_load_image(void *boot_image)
f0ad2aa
   /* LoadImage does not set a device handler when the image is
f0ad2aa
      loaded from memory, so it is necessary to set it explicitly here.
f0ad2aa
      This is a mess.  */
f0ad2aa
-  loaded_image = grub_efi_get_loaded_image (image_handle);
f0ad2aa
+  loaded_image = grub_efi_get_loaded_image (*image_handle_out);
f0ad2aa
   if (! loaded_image)
f0ad2aa
     {
f0ad2aa
       grub_error (GRUB_ERR_BAD_OS, "no loaded image available");
f0ad2aa
@@ -885,20 +900,25 @@ grub_secureboot_chainloader_boot (void)
f0ad2aa
 {
f0ad2aa
   grub_efi_boot_services_t *b;
f0ad2aa
   int rc;
f0ad2aa
+  grub_efi_handle_t handle = 0;
f0ad2aa
 
f0ad2aa
-  rc = handle_image ((void *)(unsigned long)address, fsize);
f0ad2aa
+  rc = handle_image (sb_context);
f0ad2aa
   if (rc == 0)
f0ad2aa
     {
f0ad2aa
       /* We weren't able to attempt to execute the image, so fall back
f0ad2aa
        * to LoadImage / StartImage.
f0ad2aa
        */
f0ad2aa
-      rc = grub_load_image((void *)(unsigned long)address);
f0ad2aa
+      rc = grub_load_image(sb_context->file_path,
f0ad2aa
+			   (void *)(unsigned long)sb_context->address,
f0ad2aa
+			   sb_context->fsize, sb_context->dev_handle,
f0ad2aa
+			   sb_context->cmdline, sb_context->cmdline_len,
f0ad2aa
+			   &handle);
f0ad2aa
       if (rc == 0)
f0ad2aa
-        grub_chainloader_boot ();
f0ad2aa
+	grub_start_image (handle);
f0ad2aa
     }
f0ad2aa
 
f0ad2aa
   b = grub_efi_system_table->boot_services;
f0ad2aa
-  efi_call_1 (b->unload_image, image_handle);
f0ad2aa
+  efi_call_1 (b->unload_image, handle);
f0ad2aa
 
f0ad2aa
   grub_loader_unset ();
f0ad2aa
   return grub_errno;
f0ad2aa
@@ -913,9 +933,15 @@ grub_cmd_chainloader (grub_command_t cmd __attribute__ ((unused)),
f0ad2aa
   grub_efi_boot_services_t *b;
f0ad2aa
   grub_device_t dev = 0;
f0ad2aa
   grub_device_t orig_dev = 0;
f0ad2aa
-  grub_efi_device_path_t *dp = 0;
f0ad2aa
+  grub_efi_device_path_t *dp = 0, *file_path = 0;
f0ad2aa
   char *filename;
f0ad2aa
   void *boot_image = 0;
f0ad2aa
+  grub_efi_physical_address_t address = 0;
f0ad2aa
+  grub_ssize_t fsize;
f0ad2aa
+  grub_efi_uintn_t pages = 0;
f0ad2aa
+  grub_efi_char16_t *cmdline = 0;
f0ad2aa
+  grub_ssize_t cmdline_len = 0;
f0ad2aa
+  grub_efi_handle_t dev_handle = 0;
f0ad2aa
 
f0ad2aa
   if (argc == 0)
f0ad2aa
     return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
f0ad2aa
@@ -923,12 +949,6 @@ grub_cmd_chainloader (grub_command_t cmd __attribute__ ((unused)),
f0ad2aa
 
f0ad2aa
   grub_dl_ref (my_mod);
f0ad2aa
 
f0ad2aa
-  /* Initialize some global variables.  */
f0ad2aa
-  address = 0;
f0ad2aa
-  image_handle = 0;
f0ad2aa
-  file_path = 0;
f0ad2aa
-  dev_handle = 0;
f0ad2aa
-
f0ad2aa
   b = grub_efi_system_table->boot_services;
f0ad2aa
 
f0ad2aa
   if (argc > 1)
f0ad2aa
@@ -1093,17 +1113,35 @@ grub_cmd_chainloader (grub_command_t cmd __attribute__ ((unused)),
f0ad2aa
 
f0ad2aa
   if (grub_efi_get_secureboot () == GRUB_EFI_SECUREBOOT_MODE_ENABLED)
f0ad2aa
     {
f0ad2aa
+      sb_context = grub_malloc (sizeof (*sb_context));
f0ad2aa
+      if (sb_context == NULL)
f0ad2aa
+        goto fail;
f0ad2aa
+      sb_context->address = address;
f0ad2aa
+      sb_context->fsize = fsize;
f0ad2aa
+      sb_context->pages = pages;
f0ad2aa
+      sb_context->file_path = file_path;
f0ad2aa
+      sb_context->cmdline = cmdline;
f0ad2aa
+      sb_context->cmdline_len = cmdline_len;
f0ad2aa
+      sb_context->dev_handle = dev_handle;
f0ad2aa
+
f0ad2aa
       grub_file_close (file);
f0ad2aa
       grub_device_close (dev);
f0ad2aa
+
f0ad2aa
       grub_loader_set (grub_secureboot_chainloader_boot,
f0ad2aa
 		       grub_secureboot_chainloader_unload, 0);
f0ad2aa
       return 0;
f0ad2aa
     }
f0ad2aa
   else
f0ad2aa
     {
f0ad2aa
-      grub_load_image(boot_image);
f0ad2aa
+      grub_load_image(file_path, boot_image, fsize, dev_handle, cmdline,
f0ad2aa
+		      cmdline_len, &image_handle);
f0ad2aa
       grub_file_close (file);
f0ad2aa
       grub_device_close (dev);
f0ad2aa
+
f0ad2aa
+      /* We're finished with the source image buffer and file path now */
f0ad2aa
+      efi_call_2 (b->free_pages, address, pages);
f0ad2aa
+      grub_free (file_path);
f0ad2aa
+
f0ad2aa
       grub_loader_set (grub_chainloader_boot, grub_chainloader_unload, 0);
f0ad2aa
 
f0ad2aa
       return 0;
f0ad2aa
@@ -1130,6 +1168,12 @@ fail:
f0ad2aa
   if (cmdline)
f0ad2aa
     grub_free (cmdline);
f0ad2aa
 
f0ad2aa
+  if (image_handle != 0)
f0ad2aa
+    {
f0ad2aa
+      efi_call_1 (b->unload_image, image_handle);
f0ad2aa
+      image_handle = 0;
f0ad2aa
+    }
f0ad2aa
+
f0ad2aa
   grub_dl_unref (my_mod);
f0ad2aa
 
f0ad2aa
   return grub_errno;