f0ad2aa
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
f0ad2aa
From: Chris Coulson <chris.coulson@canonical.com>
f0ad2aa
Date: Mon, 2 May 2022 17:04:23 +0200
f0ad2aa
Subject: [PATCH] loader/i386/efi/linux: Use grub_loader_set_ex
f0ad2aa
f0ad2aa
This ports the linuxefi loader to use grub_loader_set_ex in order to fix
f0ad2aa
a use-after-fre bug that occurs when grub_cmd_linux is executed more than
f0ad2aa
once before a boot attempt is performed.
f0ad2aa
f0ad2aa
This is more complicated than for the chainloader command, as the initrd
f0ad2aa
command needs access to the loader state. To solve this, the linuxefi
f0ad2aa
module registers a dummy initrd command at startup that returns an error.
f0ad2aa
The linuxefi command then registers a proper initrd command with a higher
f0ad2aa
priority that is passed the loader state.
f0ad2aa
f0ad2aa
Signed-off-by: Chris Coulson <chris.coulson@canonical.com>
f0ad2aa
(cherry picked from commit 7cf736436b4c934df5ddfa6f44b46a7e07d99fdc)
f0ad2aa
[rharwood/pjones: set kernel_size in context]
f0ad2aa
Signed-off-by: Robbie Harwood <rharwood@redhat.com>
f0ad2aa
---
f0ad2aa
 grub-core/loader/i386/efi/linux.c | 146 +++++++++++++++++++++++---------------
f0ad2aa
 1 file changed, 87 insertions(+), 59 deletions(-)
f0ad2aa
f0ad2aa
diff --git a/grub-core/loader/i386/efi/linux.c b/grub-core/loader/i386/efi/linux.c
f0ad2aa
index 27bc2aa161..e3c2d6fe0b 100644
f0ad2aa
--- a/grub-core/loader/i386/efi/linux.c
f0ad2aa
+++ b/grub-core/loader/i386/efi/linux.c
f0ad2aa
@@ -34,13 +34,19 @@
f0ad2aa
 GRUB_MOD_LICENSE ("GPLv3+");
f0ad2aa
 
f0ad2aa
 static grub_dl_t my_mod;
f0ad2aa
-static int loaded;
f0ad2aa
-static void *kernel_mem;
f0ad2aa
-static grub_uint64_t kernel_size;
f0ad2aa
-static void *initrd_mem;
f0ad2aa
-static grub_uint32_t handover_offset;
f0ad2aa
-struct linux_kernel_params *params;
f0ad2aa
-static char *linux_cmdline;
f0ad2aa
+
f0ad2aa
+static grub_command_t cmd_linux, cmd_initrd;
f0ad2aa
+static grub_command_t cmd_linuxefi, cmd_initrdefi;
f0ad2aa
+
f0ad2aa
+struct grub_linuxefi_context {
f0ad2aa
+  void *kernel_mem;
f0ad2aa
+  grub_uint64_t kernel_size;
f0ad2aa
+  grub_uint32_t handover_offset;
f0ad2aa
+  struct linux_kernel_params *params;
f0ad2aa
+  char *cmdline;
f0ad2aa
+
f0ad2aa
+  void *initrd_mem;
f0ad2aa
+};
f0ad2aa
 
f0ad2aa
 #define MIN(a, b) \
f0ad2aa
   ({ typeof (a) _a = (a); \
f0ad2aa
@@ -123,25 +129,32 @@ kernel_alloc(grub_efi_uintn_t size, const char * const errmsg)
f0ad2aa
 }
f0ad2aa
 
f0ad2aa
 static grub_err_t
f0ad2aa
-grub_linuxefi_boot (void)
f0ad2aa
+grub_linuxefi_boot (void *data)
f0ad2aa
 {
f0ad2aa
+  struct grub_linuxefi_context *context = (struct grub_linuxefi_context *) data;
f0ad2aa
+
f0ad2aa
   asm volatile ("cli");
f0ad2aa
 
f0ad2aa
-  return grub_efi_linux_boot ((char *)kernel_mem,
f0ad2aa
-			      handover_offset,
f0ad2aa
-			      params);
f0ad2aa
+  return grub_efi_linux_boot ((char *)context->kernel_mem,
f0ad2aa
+			      context->handover_offset,
f0ad2aa
+			      context->params);
f0ad2aa
 }
f0ad2aa
 
f0ad2aa
 static grub_err_t
f0ad2aa
-grub_linuxefi_unload (void)
f0ad2aa
+grub_linuxefi_unload (void *data)
f0ad2aa
 {
f0ad2aa
+  struct grub_linuxefi_context *context = (struct grub_linuxefi_context *) data;
f0ad2aa
+  struct linux_kernel_params *params = context->params;
f0ad2aa
+
f0ad2aa
   grub_dl_unref (my_mod);
f0ad2aa
-  loaded = 0;
f0ad2aa
 
f0ad2aa
-  kernel_free(initrd_mem, params->ramdisk_size);
f0ad2aa
-  kernel_free(linux_cmdline, params->cmdline_size + 1);
f0ad2aa
-  kernel_free(kernel_mem, kernel_size);
f0ad2aa
-  kernel_free(params, sizeof(*params));
f0ad2aa
+  kernel_free (context->initrd_mem, params->ramdisk_size);
f0ad2aa
+  kernel_free (context->cmdline, params->cmdline_size + 1);
f0ad2aa
+  kernel_free (context->kernel_mem, context->kernel_size);
f0ad2aa
+  kernel_free (params, sizeof(*params));
f0ad2aa
+  cmd_initrd->data = 0;
f0ad2aa
+  cmd_initrdefi->data = 0;
f0ad2aa
+  grub_free (context);
f0ad2aa
 
f0ad2aa
   return GRUB_ERR_NONE;
f0ad2aa
 }
f0ad2aa
@@ -188,13 +201,14 @@ read(grub_file_t file, grub_uint8_t *bufp, grub_size_t len)
f0ad2aa
 #define HIGH_U32(val) ((grub_uint32_t)(((grub_addr_t)(val) >> 32) & 0xffffffffull))
f0ad2aa
 
f0ad2aa
 static grub_err_t
f0ad2aa
-grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)),
f0ad2aa
-                 int argc, char *argv[])
f0ad2aa
+grub_cmd_initrd (grub_command_t cmd, int argc, char *argv[])
f0ad2aa
 {
f0ad2aa
   grub_file_t *files = 0;
f0ad2aa
   int i, nfiles = 0;
f0ad2aa
   grub_size_t size = 0;
f0ad2aa
   grub_uint8_t *ptr;
f0ad2aa
+  struct grub_linuxefi_context *context = (struct grub_linuxefi_context *) cmd->data;
f0ad2aa
+  struct linux_kernel_params *params;
f0ad2aa
 
f0ad2aa
   if (argc == 0)
f0ad2aa
     {
f0ad2aa
@@ -202,12 +216,14 @@ grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)),
f0ad2aa
       goto fail;
f0ad2aa
     }
f0ad2aa
 
f0ad2aa
-  if (!loaded)
f0ad2aa
+  if (!context)
f0ad2aa
     {
f0ad2aa
       grub_error (GRUB_ERR_BAD_ARGUMENT, N_("you need to load the kernel first"));
f0ad2aa
       goto fail;
f0ad2aa
     }
f0ad2aa
 
f0ad2aa
+  params = context->params;
f0ad2aa
+
f0ad2aa
   files = grub_calloc (argc, sizeof (files[0]));
f0ad2aa
   if (!files)
f0ad2aa
     goto fail;
f0ad2aa
@@ -225,19 +241,19 @@ grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)),
f0ad2aa
 	}
f0ad2aa
     }
f0ad2aa
 
f0ad2aa
-  initrd_mem = kernel_alloc(size, N_("can't allocate initrd"));
f0ad2aa
-  if (initrd_mem == NULL)
f0ad2aa
+  context->initrd_mem = kernel_alloc(size, N_("can't allocate initrd"));
f0ad2aa
+  if (context->initrd_mem == NULL)
f0ad2aa
     goto fail;
f0ad2aa
-  grub_dprintf ("linux", "initrd_mem = %p\n", initrd_mem);
f0ad2aa
+  grub_dprintf ("linux", "initrd_mem = %p\n", context->initrd_mem);
f0ad2aa
 
f0ad2aa
   params->ramdisk_size = LOW_U32(size);
f0ad2aa
-  params->ramdisk_image = LOW_U32(initrd_mem);
f0ad2aa
+  params->ramdisk_image = LOW_U32(context->initrd_mem);
f0ad2aa
 #if defined(__x86_64__)
f0ad2aa
   params->ext_ramdisk_size = HIGH_U32(size);
f0ad2aa
-  params->ext_ramdisk_image = HIGH_U32(initrd_mem);
f0ad2aa
+  params->ext_ramdisk_image = HIGH_U32(context->initrd_mem);
f0ad2aa
 #endif
f0ad2aa
 
f0ad2aa
-  ptr = initrd_mem;
f0ad2aa
+  ptr = context->initrd_mem;
f0ad2aa
 
f0ad2aa
   for (i = 0; i < nfiles; i++)
f0ad2aa
     {
f0ad2aa
@@ -261,8 +277,8 @@ grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)),
f0ad2aa
     grub_file_close (files[i]);
f0ad2aa
   grub_free (files);
f0ad2aa
 
f0ad2aa
-  if (initrd_mem && grub_errno)
f0ad2aa
-    grub_efi_free_pages ((grub_efi_physical_address_t)(grub_addr_t)initrd_mem,
f0ad2aa
+  if (context->initrd_mem && grub_errno)
f0ad2aa
+    grub_efi_free_pages ((grub_efi_physical_address_t)(grub_addr_t)context->initrd_mem,
f0ad2aa
 			 BYTES_TO_PAGES(size));
f0ad2aa
 
f0ad2aa
   return grub_errno;
f0ad2aa
@@ -277,6 +293,12 @@ grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)),
f0ad2aa
   grub_ssize_t start, filelen;
f0ad2aa
   void *kernel = NULL;
f0ad2aa
   int setup_header_end_offset;
f0ad2aa
+  void *kernel_mem = 0;
f0ad2aa
+  grub_uint64_t kernel_size = 0;
f0ad2aa
+  grub_uint32_t handover_offset;
f0ad2aa
+  struct linux_kernel_params *params = 0;
f0ad2aa
+  char *cmdline = 0;
f0ad2aa
+  struct grub_linuxefi_context *context = 0;
f0ad2aa
 
f0ad2aa
   grub_dl_ref (my_mod);
f0ad2aa
 
f0ad2aa
@@ -390,27 +412,27 @@ grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)),
f0ad2aa
   grub_dprintf ("linux", "new lh is at %p\n", lh);
f0ad2aa
 
f0ad2aa
   grub_dprintf ("linux", "setting up cmdline\n");
f0ad2aa
-  linux_cmdline = kernel_alloc (lh->cmdline_size + 1, N_("can't allocate cmdline"));
f0ad2aa
-  if (!linux_cmdline)
f0ad2aa
+  cmdline = kernel_alloc (lh->cmdline_size + 1, N_("can't allocate cmdline"));
f0ad2aa
+  if (!cmdline)
f0ad2aa
     goto fail;
f0ad2aa
-  grub_dprintf ("linux", "linux_cmdline = %p\n", linux_cmdline);
f0ad2aa
+  grub_dprintf ("linux", "cmdline = %p\n", cmdline);
f0ad2aa
 
f0ad2aa
-  grub_memcpy (linux_cmdline, LINUX_IMAGE, sizeof (LINUX_IMAGE));
f0ad2aa
+  grub_memcpy (cmdline, LINUX_IMAGE, sizeof (LINUX_IMAGE));
f0ad2aa
   grub_create_loader_cmdline (argc, argv,
f0ad2aa
-                              linux_cmdline + sizeof (LINUX_IMAGE) - 1,
f0ad2aa
+                              cmdline + sizeof (LINUX_IMAGE) - 1,
f0ad2aa
 			      lh->cmdline_size - (sizeof (LINUX_IMAGE) - 1),
f0ad2aa
 			      GRUB_VERIFY_KERNEL_CMDLINE);
f0ad2aa
 
f0ad2aa
-  grub_dprintf ("linux", "cmdline:%s\n", linux_cmdline);
f0ad2aa
+  grub_dprintf ("linux", "cmdline:%s\n", cmdline);
f0ad2aa
   grub_dprintf ("linux", "setting lh->cmd_line_ptr to 0x%08x\n",
f0ad2aa
-		LOW_U32(linux_cmdline));
f0ad2aa
-  lh->cmd_line_ptr = LOW_U32(linux_cmdline);
f0ad2aa
+		LOW_U32(cmdline));
f0ad2aa
+  lh->cmd_line_ptr = LOW_U32(cmdline);
f0ad2aa
 #if defined(__x86_64__)
f0ad2aa
-  if ((grub_efi_uintn_t)linux_cmdline > 0xffffffffull)
f0ad2aa
+  if ((grub_efi_uintn_t)cmdline > 0xffffffffull)
f0ad2aa
     {
f0ad2aa
       grub_dprintf ("linux", "setting params->ext_cmd_line_ptr to 0x%08x\n",
f0ad2aa
-		    HIGH_U32(linux_cmdline));
f0ad2aa
-      params->ext_cmd_line_ptr = HIGH_U32(linux_cmdline);
f0ad2aa
+		    HIGH_U32(cmdline));
f0ad2aa
+      params->ext_cmd_line_ptr = HIGH_U32(cmdline);
f0ad2aa
     }
f0ad2aa
 #endif
f0ad2aa
 
f0ad2aa
@@ -435,16 +457,13 @@ grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)),
f0ad2aa
     }
f0ad2aa
   max_addresses[1].addr = GRUB_EFI_MAX_ALLOCATION_ADDRESS;
f0ad2aa
   max_addresses[2].addr = GRUB_EFI_MAX_ALLOCATION_ADDRESS;
f0ad2aa
-  kernel_mem = kernel_alloc (lh->init_size, N_("can't allocate kernel"));
f0ad2aa
+  kernel_size = lh->init_size;
f0ad2aa
+  kernel_mem = kernel_alloc (kernel_size, N_("can't allocate kernel"));
f0ad2aa
   restore_addresses();
f0ad2aa
   if (!kernel_mem)
f0ad2aa
     goto fail;
f0ad2aa
   grub_dprintf("linux", "kernel_mem = %p\n", kernel_mem);
f0ad2aa
 
f0ad2aa
-  grub_loader_set (grub_linuxefi_boot, grub_linuxefi_unload, 0);
f0ad2aa
-
f0ad2aa
-  loaded = 1;
f0ad2aa
-
f0ad2aa
   grub_dprintf ("linux", "setting lh->code32_start to 0x%08x\n",
f0ad2aa
 		LOW_U32(kernel_mem));
f0ad2aa
   lh->code32_start = LOW_U32(kernel_mem);
f0ad2aa
@@ -461,33 +480,42 @@ grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)),
f0ad2aa
 		"setting lh->ext_loader_{type,ver} = {0x%02x,0x%02x}\n",
f0ad2aa
 		params->ext_loader_type, params->ext_loader_ver);
f0ad2aa
 
f0ad2aa
+  context = grub_zalloc (sizeof (*context));
f0ad2aa
+  if (!context)
f0ad2aa
+    goto fail;
f0ad2aa
+  context->kernel_mem = kernel_mem;
f0ad2aa
+  context->kernel_size = kernel_size;
f0ad2aa
+  context->handover_offset = handover_offset;
f0ad2aa
+  context->params = params;
f0ad2aa
+  context->cmdline = cmdline;
f0ad2aa
+
f0ad2aa
+  grub_loader_set_ex (grub_linuxefi_boot, grub_linuxefi_unload, context, 0);
f0ad2aa
+
f0ad2aa
+  cmd_initrd->data = context;
f0ad2aa
+  cmd_initrdefi->data = context;
f0ad2aa
+
f0ad2aa
+  grub_file_close (file);
f0ad2aa
+  grub_free (kernel);
f0ad2aa
+  return 0;
f0ad2aa
+
f0ad2aa
 fail:
f0ad2aa
   if (file)
f0ad2aa
     grub_file_close (file);
f0ad2aa
 
f0ad2aa
-  if (grub_errno != GRUB_ERR_NONE)
f0ad2aa
-    {
f0ad2aa
-      grub_dl_unref (my_mod);
f0ad2aa
-      loaded = 0;
f0ad2aa
-    }
f0ad2aa
+  grub_dl_unref (my_mod);
f0ad2aa
 
f0ad2aa
-  if (!loaded)
f0ad2aa
-    {
f0ad2aa
-      if (lh)
f0ad2aa
-	kernel_free (linux_cmdline, lh->cmdline_size + 1);
f0ad2aa
+  if (lh)
f0ad2aa
+    kernel_free (cmdline, lh->cmdline_size + 1);
f0ad2aa
 
f0ad2aa
-      kernel_free (kernel_mem, kernel_size);
f0ad2aa
-      kernel_free (params, sizeof(*params));
f0ad2aa
-    }
f0ad2aa
+  kernel_free (kernel_mem, kernel_size);
f0ad2aa
+  kernel_free (params, sizeof(*params));
f0ad2aa
 
f0ad2aa
+  grub_free (context);
f0ad2aa
   grub_free (kernel);
f0ad2aa
 
f0ad2aa
   return grub_errno;
f0ad2aa
 }
f0ad2aa
 
f0ad2aa
-static grub_command_t cmd_linux, cmd_initrd;
f0ad2aa
-static grub_command_t cmd_linuxefi, cmd_initrdefi;
f0ad2aa
-
f0ad2aa
 GRUB_MOD_INIT(linux)
f0ad2aa
 {
f0ad2aa
   cmd_linux =