/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include "fwupd-error.h" #include "fu-ucs2.h" #include "fu-uefi-bootmgr.h" #include "fu-uefi-common.h" #include "fu-efivar.h" /* XXX PJFIX: this should be in efiboot-loadopt.h in efivar */ #define LOAD_OPTION_ACTIVE 0x00000001 static gboolean fu_uefi_bootmgr_add_to_boot_order (guint16 boot_entry, GError **error) { gsize boot_order_size = 0; guint i = 0; guint32 attr = 0; g_autofree guint16 *boot_order = NULL; g_autofree guint16 *new_boot_order = NULL; /* get the current boot order */ if (!fu_efivar_get_data (FU_EFIVAR_GUID_EFI_GLOBAL, "BootOrder", (guint8 **) &boot_order, &boot_order_size, &attr, error)) return FALSE; /* already set next */ for (i = 0; i < boot_order_size / sizeof (guint16); i++) { guint16 val = boot_order[i]; if (val == boot_entry) return TRUE; } /* add the new boot index to the end of the list */ new_boot_order = g_malloc0 (boot_order_size + sizeof (guint16)); if (boot_order_size != 0) memcpy (new_boot_order, boot_order, boot_order_size); attr |= FU_EFIVAR_ATTR_NON_VOLATILE | FU_EFIVAR_ATTR_BOOTSERVICE_ACCESS | FU_EFIVAR_ATTR_RUNTIME_ACCESS; i = boot_order_size / sizeof (guint16); new_boot_order[i] = boot_entry; boot_order_size += sizeof (guint16); return fu_efivar_set_data (FU_EFIVAR_GUID_EFI_GLOBAL, "BootOrder", (guint8 *)new_boot_order, boot_order_size, attr, error); } static guint16 fu_uefi_bootmgr_parse_name (const gchar *name) { gint rc; gint scanned = 0; guint16 entry = 0; /* BootXXXX */ rc = sscanf (name, "Boot%hX%n", &entry, &scanned); if (rc != 1 || scanned != 8) return G_MAXUINT16; return entry; } gboolean fu_uefi_bootmgr_verify_fwupd (GError **error) { g_autoptr(GPtrArray) names = NULL; names = fu_efivar_get_names (FU_EFIVAR_GUID_EFI_GLOBAL, error); if (names == NULL) return FALSE; for (guint i = 0; i < names->len; i++) { const gchar *desc; const gchar *name = g_ptr_array_index (names, i); efi_load_option *loadopt; gsize var_data_size = 0; guint16 entry; g_autofree guint8 *var_data_tmp = NULL; g_autoptr(GError) error_local = NULL; /* not BootXXXX */ entry = fu_uefi_bootmgr_parse_name (name); if (entry == G_MAXUINT16) continue; /* parse key */ if (!fu_efivar_get_data (FU_EFIVAR_GUID_EFI_GLOBAL, name, &var_data_tmp, &var_data_size, NULL, &error_local)) { g_debug ("failed to get data for name %s: %s", name, error_local->message); continue; } loadopt = (efi_load_option *) var_data_tmp; if (!efi_loadopt_is_valid(loadopt, var_data_size)) { g_debug ("%s -> load option was invalid", name); continue; } desc = (const gchar *) efi_loadopt_desc (loadopt, var_data_size); if (g_strcmp0 (desc, "Linux Firmware Updater") == 0 || g_strcmp0 (desc, "Linux-Firmware-Updater") == 0) { g_debug ("found %s at Boot%04X", desc, entry); return TRUE; } } /* did not find */ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "no 'Linux Firmware Updater' entry found"); return FALSE; } static gboolean fu_uefi_setup_bootnext_with_dp (const guint8 *dp_buf, guint8 *opt, gssize opt_size, GError **error) { const gchar *desc; const gchar *name; efi_load_option *loadopt = NULL; gsize var_data_size = 0; guint32 attr; guint16 boot_next = G_MAXUINT16; g_autofree guint8 *var_data = NULL; g_autofree guint8 *set_entries = g_malloc0 (G_MAXUINT16); g_autoptr(GPtrArray) names = NULL; names = fu_efivar_get_names (FU_EFIVAR_GUID_EFI_GLOBAL, error); if (names == NULL) return FALSE; for (guint i = 0; i < names->len; i++) { guint16 entry = 0; g_autofree guint8 *var_data_tmp = NULL; g_autoptr(GError) error_local = NULL; /* not BootXXXX */ name = g_ptr_array_index (names, i); entry = fu_uefi_bootmgr_parse_name (name); if (entry == G_MAXUINT16) continue; /* mark this as used */ set_entries[entry] = 1; if (!fu_efivar_get_data (FU_EFIVAR_GUID_EFI_GLOBAL, name, &var_data_tmp, &var_data_size, &attr, &error_local)) { g_debug ("failed to get data for name %s: %s", name, error_local->message); continue; } loadopt = (efi_load_option *)var_data_tmp; if (!efi_loadopt_is_valid(loadopt, var_data_size)) { g_debug ("%s -> load option was invalid", name); continue; } desc = (const gchar *) efi_loadopt_desc (loadopt, var_data_size); if (g_strcmp0 (desc, "Linux Firmware Updater") != 0 && g_strcmp0 (desc, "Linux-Firmware-Updater") != 0) { g_debug ("%s -> '%s' : does not match", name, desc); continue; } var_data = g_steal_pointer (&var_data_tmp); boot_next = entry; break; } /* already exists */ if (var_data != NULL) { /* is different than before */ if (var_data_size != (gsize) opt_size || memcmp (var_data, opt, opt_size) != 0) { g_debug ("%s -> '%s' : updating existing boot entry", name, desc); efi_loadopt_attr_set (loadopt, LOAD_OPTION_ACTIVE); if (!fu_efivar_set_data (FU_EFIVAR_GUID_EFI_GLOBAL, name, opt, opt_size, attr, error)) { g_prefix_error (error, "could not set boot variable active: "); return FALSE; } } else { g_debug ("%s -> %s : re-using existing boot entry", name, desc); } /* create a new one */ } else { g_autofree gchar *boot_next_name = NULL; for (guint16 value = 0; value < G_MAXUINT16; value++) { if (set_entries[value]) continue; boot_next = value; break; } if (boot_next == G_MAXUINT16) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "no free boot variables (tried %x)", boot_next); return FALSE; } boot_next_name = g_strdup_printf ("Boot%04X", (guint) boot_next); g_debug ("%s -> creating new entry", boot_next_name); if (!fu_efivar_set_data (FU_EFIVAR_GUID_EFI_GLOBAL, boot_next_name, opt, opt_size, FU_EFIVAR_ATTR_NON_VOLATILE | FU_EFIVAR_ATTR_BOOTSERVICE_ACCESS | FU_EFIVAR_ATTR_RUNTIME_ACCESS, error)) { g_prefix_error (error, "could not set boot variable %s: ", boot_next_name); return FALSE; } } /* TODO: conditionalize this on the UEFI version? */ if(!fu_uefi_bootmgr_add_to_boot_order (boot_next, error)) return FALSE; /* set the boot next */ if (!fu_efivar_set_data (FU_EFIVAR_GUID_EFI_GLOBAL, "BootNext", (guint8 *)&boot_next, 2, FU_EFIVAR_ATTR_NON_VOLATILE | FU_EFIVAR_ATTR_BOOTSERVICE_ACCESS | FU_EFIVAR_ATTR_RUNTIME_ACCESS, error)) { g_prefix_error (error, "could not set BootNext(%" G_GUINT16_FORMAT "): ", boot_next); return FALSE; } return TRUE; } static gboolean fu_uefi_cmp_asset (const gchar *source, const gchar *target) { gsize len = 0; g_autofree gchar *source_checksum = NULL; g_autofree gchar *source_data = NULL; g_autofree gchar *target_checksum = NULL; g_autofree gchar *target_data = NULL; /* nothing in target yet */ if (!g_file_test (target, G_FILE_TEST_EXISTS)) return FALSE; /* test if the file needs to be updated */ if (!g_file_get_contents (source, &source_data, &len, NULL)) return FALSE; source_checksum = g_compute_checksum_for_data (G_CHECKSUM_SHA256, (guchar *) source_data, len); if (!g_file_get_contents (target, &target_data, &len, NULL)) return FALSE; target_checksum = g_compute_checksum_for_data (G_CHECKSUM_SHA256, (guchar *) target_data, len); return g_strcmp0 (target_checksum, source_checksum) == 0; } static gboolean fu_uefi_copy_asset (const gchar *source, const gchar *target, GError **error) { g_autoptr(GFile) source_file = g_file_new_for_path (source); g_autoptr(GFile) target_file = g_file_new_for_path (target); if (!g_file_copy (source_file, target_file, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, error)) { g_prefix_error (error, "Failed to copy %s to %s: ", source, target); return FALSE; } return TRUE; } gboolean fu_uefi_bootmgr_bootnext (FuDevice *device, const gchar *esp_path, const gchar *description, FuUefiBootmgrFlags flags, GError **error) { const gchar *filepath; gboolean use_fwup_path = TRUE; gboolean secure_boot = FALSE; gsize loader_sz = 0; gssize opt_size = 0; gssize sz, dp_size = 0; guint32 attributes = LOAD_OPTION_ACTIVE; g_autofree guint16 *loader_str = NULL; g_autofree gchar *label = NULL; g_autofree gchar *shim_app = NULL; g_autofree gchar *shim_cpy = NULL; g_autofree guint8 *dp_buf = NULL; g_autofree guint8 *opt = NULL; g_autofree gchar *source_app = NULL; g_autofree gchar *target_app = NULL; /* skip for self tests */ if (g_getenv ("FWUPD_UEFI_TEST") != NULL) return TRUE; /* if secure boot was turned on this might need to be installed separately */ source_app = fu_uefi_get_built_app_path (error); if (source_app == NULL) return FALSE; /* test if we should use shim */ secure_boot = fu_efivar_secure_boot_enabled (); if (secure_boot) { /* test to make sure shim is there if we need it */ shim_app = fu_uefi_get_esp_app_path (device, esp_path, "shim", error); if (shim_app == NULL) return FALSE; /* try to fallback to use UEFI removable path if the shim path doesn't exist */ if (!g_file_test (shim_app, G_FILE_TEST_EXISTS)) { if (fu_device_get_metadata_boolean (device, "FallbacktoRemovablePath")) { shim_app = fu_uefi_get_esp_app_path (device, esp_path, "boot", error); if (shim_app == NULL) return FALSE; } } if (g_file_test (shim_app, G_FILE_TEST_EXISTS)) { /* use a custom copy of shim for firmware updates */ if (flags & FU_UEFI_BOOTMGR_FLAG_USE_SHIM_UNIQUE) { shim_cpy = fu_uefi_get_esp_app_path (device, esp_path, "shimfwupd", error); if (shim_cpy == NULL) return FALSE; if (!fu_uefi_cmp_asset (shim_app, shim_cpy)) { if (!fu_uefi_copy_asset (shim_app, shim_cpy, error)) return FALSE; } filepath = shim_cpy; } else { filepath = shim_app; } use_fwup_path = FALSE; } else if ((flags & FU_UEFI_BOOTMGR_FLAG_USE_SHIM_FOR_SB) > 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_BROKEN_SYSTEM, "Secure boot is enabled, but shim isn't installed to the EFI system partition"); return FALSE; } } /* test if correct asset in place */ target_app = fu_uefi_get_esp_app_path (device, esp_path, "fwupd", error); if (target_app == NULL) return FALSE; if (!fu_uefi_cmp_asset (source_app, target_app)) { if (!fu_uefi_copy_asset (source_app, target_app, error)) return FALSE; } /* no shim, so use this directly */ if (use_fwup_path) filepath = target_app; /* generate device path for target */ sz = efi_generate_file_device_path (dp_buf, dp_size, filepath, EFIBOOT_OPTIONS_IGNORE_FS_ERROR| EFIBOOT_ABBREV_HD); if (sz < 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "efi_generate_file_device_path(%s) failed", filepath); return FALSE; } /* add the fwupdx64.efi ESP path as the shim loadopt data */ dp_size = sz; dp_buf = g_malloc0 (dp_size); if (!use_fwup_path) { g_autofree gchar *fwup_fs_basename = g_path_get_basename (target_app); g_autofree gchar *fwup_esp_path = g_strdup_printf ("\\%s", fwup_fs_basename); loader_str = fu_uft8_to_ucs2 (fwup_esp_path, -1); loader_sz = fu_ucs2_strlen (loader_str, -1) * 2; if (loader_sz) loader_sz += 2; } sz = efi_generate_file_device_path (dp_buf, dp_size, filepath, EFIBOOT_OPTIONS_IGNORE_FS_ERROR| EFIBOOT_ABBREV_HD); if (sz != dp_size) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "efi_generate_file_device_path(%s) failed", filepath); return FALSE; } label = g_strdup (description); sz = efi_loadopt_create (opt, opt_size, attributes, (efidp)dp_buf, dp_size, (guint8 *)label, (guint8 *)loader_str, loader_sz); if (sz < 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "efi_loadopt_create(%s) failed", label); return FALSE; } opt = g_malloc0 (sz); opt_size = sz; sz = efi_loadopt_create (opt, opt_size, attributes, (efidp)dp_buf, dp_size, (guint8 *)label, (guint8 *)loader_str, loader_sz); if (sz != opt_size) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "loadopt size was unreasonable."); return FALSE; } return fu_uefi_setup_bootnext_with_dp (dp_buf, opt, opt_size, error); }