grub-lakitu: CHROMIUM: Make grub config read-only in secure boot

To protect the rootfs hash, we want to make grub.cfg
read-only. We do this by storing it in a boot service
EFI variable. When the system boots for the first
time, the grub configuration is loaded into the GrubConfig
EFI variable. Subsequent boots only read the config
from the GrubConfig EFI variable.

Changes are made to the 'normal' command. The normal command is
executed when grub first starts and is the command that loads and
executes grub.cfg. We change the normal command to load the grub
configuration in a different way when secure boot is enabled.

I tried to follow the gnu C style guide to be consistent with
the rest of grub.

CQ-DEPEND=CL:1272375
BUG=b:112317631
TEST=Sign with dev keys; boot with EFI; change kernel cmdline in grub.cfg; reboot; /proc/cmdline hasn't changed
RELEASE_NOTE=None

Change-Id: I1adfcfe6f6ccf14e4eebe7f5be0835d0cd643437
Reviewed-on: https://chromium-review.googlesource.com/1265958
Commit-Ready: Robert Kolchmeyer <rkolchmeyer@google.com>
Tested-by: Robert Kolchmeyer <rkolchmeyer@google.com>
Reviewed-by: Robert Kolchmeyer <rkolchmeyer@google.com>
diff --git a/grub-lakitu/grub-core/kern/efi/efi.c b/grub-lakitu/grub-core/kern/efi/efi.c
index d467785..e060ed2 100644
--- a/grub-lakitu/grub-core/kern/efi/efi.c
+++ b/grub-lakitu/grub-core/kern/efi/efi.c
@@ -213,6 +213,39 @@
   return grub_error (GRUB_ERR_IO, "could not set EFI variable `%s'", var);
 }
 
+/* grub_efi_set_bootservice_variable sets the given EFI variable as a
+ * non-volatile boot-service-only variable.
+ * Mostly copied from grub_efi_set_variable.  */
+grub_err_t
+grub_efi_set_bootservice_variable(const char *var, const grub_efi_guid_t *guid,
+		      void *data, grub_size_t datasize)
+{
+  grub_efi_status_t status;
+  grub_efi_runtime_services_t *r;
+  grub_efi_char16_t *var16;
+  grub_size_t len, len16;
+
+  len = grub_strlen (var);
+  len16 = len * GRUB_MAX_UTF16_PER_UTF8;
+  var16 = grub_malloc ((len16 + 1) * sizeof (var16[0]));
+  if (!var16)
+    return grub_errno;
+  len16 = grub_utf8_to_utf16 (var16, len16, (grub_uint8_t *) var, len, NULL);
+  var16[len16] = 0;
+
+  r = grub_efi_system_table->runtime_services;
+
+  status = efi_call_5 (r->set_variable, var16, guid,
+		       (GRUB_EFI_VARIABLE_NON_VOLATILE
+			| GRUB_EFI_VARIABLE_BOOTSERVICE_ACCESS),
+		       datasize, data);
+  grub_free (var16);
+  if (status == GRUB_EFI_SUCCESS)
+    return GRUB_ERR_NONE;
+
+  return grub_error (GRUB_ERR_IO, "could not set EFI variable `%s'", var);
+}
+
 void *
 grub_efi_get_variable (const char *var, const grub_efi_guid_t *guid,
 		       grub_size_t *datasize_out)
@@ -264,6 +297,63 @@
   return NULL;
 }
 
+/* grub_efi_get_variable_or_die gets the given EFI variable, but only returns
+ * if either the read succeeded or if the variable doesn't exist. Otherwise,
+ * crash.
+ * Mostly copied from grub_efi_get_variable.  */
+void *
+grub_efi_get_variable_or_die (const char *var, const grub_efi_guid_t *guid,
+		       grub_size_t *datasize_out)
+{
+  grub_efi_status_t status;
+  grub_efi_uintn_t datasize = 0;
+  grub_efi_runtime_services_t *r;
+  grub_efi_char16_t *var16;
+  void *data;
+  grub_size_t len, len16;
+
+  *datasize_out = 0;
+
+  len = grub_strlen (var);
+  len16 = len * GRUB_MAX_UTF16_PER_UTF8;
+  var16 = grub_malloc ((len16 + 1) * sizeof (var16[0]));
+  if (!var16)
+    grub_fatal ("memory allocation error\n");
+  len16 = grub_utf8_to_utf16 (var16, len16, (grub_uint8_t *) var, len, NULL);
+  var16[len16] = 0;
+
+  r = grub_efi_system_table->runtime_services;
+
+  status = efi_call_5 (r->get_variable, var16, guid, NULL, &datasize, NULL);
+
+  if (status != GRUB_EFI_BUFFER_TOO_SMALL || !datasize)
+    {
+      grub_free (var16);
+      if (status == GRUB_EFI_NOT_FOUND)
+        return NULL;
+      else
+        grub_fatal ("error getting EFI variable: %d\n", status);
+    }
+
+  data = grub_malloc (datasize);
+  if (!data)
+    {
+      grub_free (var16);
+      grub_fatal ("memory allocation error\n");
+    }
+
+  status = efi_call_5 (r->get_variable, var16, guid, NULL, &datasize, data);
+  grub_free (var16);
+
+  if (status == GRUB_EFI_SUCCESS)
+    {
+      *datasize_out = datasize;
+      return data;
+    }
+
+  grub_free (data);
+  grub_fatal ("error getting EFI variable: %d\n", status);
+}
 #pragma GCC diagnostic ignored "-Wcast-align"
 
 /* Search the mods section from the PE32/PE32+ image. This code uses
diff --git a/grub-lakitu/grub-core/normal/main.c b/grub-lakitu/grub-core/normal/main.c
index 1b03dfd..2998eff 100644
--- a/grub-lakitu/grub-core/normal/main.c
+++ b/grub-lakitu/grub-core/normal/main.c
@@ -33,6 +33,8 @@
 #include <grub/charset.h>
 #include <grub/script_sh.h>
 #include <grub/bufio.h>
+#include <grub/efi/sb.h>
+#include <grub/efi/efi.h>
 
 GRUB_MOD_LICENSE ("GPLv3+");
 
@@ -254,6 +256,147 @@
   return val ? grub_strdup (val) : NULL;
 }
 
+/* nv_config_setdefault gets a grub config string from an EFI variable if
+ * it exists. If it doesn't exist, it reads the config from the given file,
+ * stores it in an EFI variable, and returns the config as a string.  */
+static char *
+nv_config_setdefault (const char *config)
+{
+  grub_size_t datasize;
+  char *data;
+  grub_file_t file;
+  grub_err_t err;
+  /* I picked a random GUID for this, so it shouldn't collide with any existing
+   * GUIDs.  */
+  grub_efi_guid_t efi_var_guid = \
+    { 0x531A0584, 0xF8FF, 0x4CE0, { 0xAC, 0x93, 0x41, 0xA4, 0x5D, 0x8C, 0xDE, 0xDB }};
+  data = (char *) grub_efi_get_variable_or_die ("GrubConfig", &efi_var_guid,
+                                                &datasize);
+  if (data)
+    {
+      grub_printf ("using config from GrubConfig EFI variable\n");
+      return data;
+    }
+  /* Read data from config file */
+  if (! config)
+    grub_fatal ("no config file given\n");
+  file = grub_file_open (config, GRUB_FILE_TYPE_CONFIG);
+  if (! file)
+    grub_fatal ("cannot open grub config\n");
+  data = grub_malloc (file->size + 1);
+  if (! data)
+    {
+      grub_file_close (file);
+      grub_fatal ("cannot allocate memory for grub config\n");
+    }
+  if (grub_file_read (file, data, file->size) < file->size)
+    {
+      grub_free (data);
+      grub_file_close (file);
+      grub_fatal ("cannot read grub config\n");
+    }
+  grub_file_close (file);
+  /* Null terminate data */
+  data[file->size] = '\0';
+  /* Set EFI variable */
+  err = grub_efi_set_bootservice_variable ("GrubConfig", &efi_var_guid, data,
+                                           file->size + 1);
+  if (err != GRUB_ERR_NONE)
+    {
+      grub_free (data);
+      grub_fatal ("could not set GrubConfig EFI variable\n");
+    }
+  grub_printf ("initialized GrubConfig EFI variable from config file\n");
+  return data;
+}
+
+/* config_data_iter is an iterator for a grub config string.  */
+typedef struct config_data_iter
+{
+  char *data;
+  grub_uint32_t pos;
+} *config_data_iter_t;
+
+/* read_config_data_getline gets the next line in the provided
+ * config_data_iter. Analogous to 'read_config_file_getline'.  */
+static grub_err_t
+read_config_data_getline (char **line, int cont __attribute__ ((unused)),
+                          void *data)
+{
+  grub_uint32_t start, end;
+  config_data_iter_t iter = (config_data_iter_t) data;
+  if (! iter || ! iter->data)
+    {
+      *line = 0;
+      return GRUB_ERR_EOF;
+    }
+  do
+    {
+      start = iter->pos;
+      while (1)
+        {
+          if (iter->data[iter->pos] == '\n')
+            {
+              end = ++iter->pos;
+              break;
+            }
+          if (iter->data[iter->pos] == '\0')
+            {
+              end = iter->pos;
+              break;
+            }
+          iter->pos++;
+        }
+    }
+  while (iter->data[start] == '#');
+  if (start == end)
+    {
+      *line = 0;
+      return GRUB_ERR_EOF;
+    }
+  *line = (char *) grub_malloc (end - start + 1);
+  if (! *line)
+    {
+      return GRUB_ERR_OUT_OF_MEMORY;
+    }
+  grub_memcpy (*line, &iter->data[start], end - start);
+  line[0][end-start] = '\0';
+  return GRUB_ERR_NONE;
+}
+
+/* read_config_secure_boot reads the grub config from an EFI variable (or
+ * the given file if the EFI variable doesn't exist), parses it, and executes
+ * it. Analogous to the 'read_config_file' function. In general, errors in
+ * this function are treated as fatal.  */
+static grub_menu_t
+read_config_secure_boot (const char *config)
+{
+  grub_menu_t newmenu;
+  struct config_data_iter iter;
+  newmenu = grub_env_get_menu ();
+  if (! newmenu)
+    {
+      newmenu = grub_zalloc (sizeof (*newmenu));
+      if (! newmenu)
+        grub_fatal ("could not allocate new menu\n");
+      grub_env_set_menu (newmenu);
+    }
+  iter.data = nv_config_setdefault (config);
+  iter.pos = 0;
+  while (1)
+    {
+      char *line;
+      grub_print_error ();
+      grub_errno = GRUB_ERR_NONE;
+      if ((read_config_data_getline (&line, 0, &iter)) || (! line))
+        break;
+      grub_normal_parse_line (line, read_config_data_getline, (void *) &iter);
+      grub_free (line);
+    }
+  grub_free (iter.data);
+  return newmenu;
+}
+
 /* Read the config file CONFIG and execute the menu interface or
    the command line interface if BATCH is false.  */
 void
@@ -270,8 +413,15 @@
     }
 
   grub_boot_time ("Executing config file");
+  if (grub_efi_secure_boot())
+    {
+      menu = read_config_secure_boot (config);
 
-  if (config)
+      /* If grub_errno is populated, it should contain a parsing error.
+       * We can ignore parsing errors.  */
+      grub_errno = GRUB_ERR_NONE;
+    }
+  else if (config)
     {
       menu = read_config_file (config);
 
diff --git a/grub-lakitu/include/grub/efi/efi.h b/grub-lakitu/include/grub/efi/efi.h
index 764cd11..cc0b94e 100644
--- a/grub-lakitu/include/grub/efi/efi.h
+++ b/grub-lakitu/include/grub/efi/efi.h
@@ -71,11 +71,19 @@
 void *EXPORT_FUNC (grub_efi_get_variable) (const char *variable,
 					   const grub_efi_guid_t *guid,
 					   grub_size_t *datasize_out);
+void *EXPORT_FUNC (grub_efi_get_variable_or_die) (const char *variable,
+					   const grub_efi_guid_t *guid,
+					   grub_size_t *datasize_out);
 grub_err_t
 EXPORT_FUNC (grub_efi_set_variable) (const char *var,
 				     const grub_efi_guid_t *guid,
 				     void *data,
 				     grub_size_t datasize);
+grub_err_t
+EXPORT_FUNC (grub_efi_set_bootservice_variable) (const char *var,
+				     const grub_efi_guid_t *guid,
+				     void *data,
+				     grub_size_t datasize);
 int
 EXPORT_FUNC (grub_efi_compare_device_paths) (const grub_efi_device_path_t *dp1,
 					     const grub_efi_device_path_t *dp2);