| From 7fa1e4c7e6ee342755ebf83657f5e5804299cf89 Mon Sep 17 00:00:00 2001 |
| From: Robert Kolchmeyer <rkolchmeyer@google.com> |
| Date: Thu, 4 Oct 2018 22:21:06 -0700 |
| Subject: [PATCH 4/7] 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> |
| --- |
| grub-core/kern/efi/efi.c | 90 +++++++++++++++++++++++ |
| grub-core/normal/main.c | 152 ++++++++++++++++++++++++++++++++++++++- |
| include/grub/efi/efi.h | 8 +++ |
| 3 files changed, 249 insertions(+), 1 deletion(-) |
| |
| diff --git a/grub-core/kern/efi/efi.c b/grub-core/kern/efi/efi.c |
| index 5ee739d51..3e32ab5dd 100644 |
| --- a/grub-core/kern/efi/efi.c |
| +++ b/grub-core/kern/efi/efi.c |
| @@ -231,6 +231,39 @@ grub_efi_set_variable(const char *var, const grub_efi_guid_t *guid, |
| 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) |
| @@ -282,6 +315,63 @@ grub_efi_get_variable (const char *var, const grub_efi_guid_t *guid, |
| 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-core/normal/main.c b/grub-core/normal/main.c |
| index abdccc86a..4d0f4f28d 100644 |
| --- a/grub-core/normal/main.c |
| +++ b/grub-core/normal/main.c |
| @@ -34,6 +34,8 @@ |
| #include <grub/charset.h> |
| #include <grub/script_sh.h> |
| #include <grub/bufio.h> |
| +#include <grub/efi/sb.h> |
| +#include <grub/efi/efi.h> |
| #include <grub/net.h> |
| #ifdef GRUB_MACHINE_IEEE1275 |
| #include <grub/ieee1275/ieee1275.h> |
| @@ -267,6 +269,147 @@ read_lists_hook (struct grub_env_var *var __attribute__ ((unused)), |
| 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 |
| @@ -283,8 +426,15 @@ grub_normal_execute (const char *config, int nested, int batch) |
| } |
| |
| 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/include/grub/efi/efi.h b/include/grub/efi/efi.h |
| index d24afbadb..aa8aed87f 100644 |
| --- a/include/grub/efi/efi.h |
| +++ b/include/grub/efi/efi.h |
| @@ -116,11 +116,19 @@ grub_err_t EXPORT_FUNC (grub_efi_set_virtual_address_map) (grub_efi_uintn_t memo |
| 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); |
| -- |
| 2.30.0.478.g8a0d178c01-goog |
| |