/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * * Copyright (C) 2016-2018 Richard Hughes * Copyright (C) 2015-2017 Peter Jones * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include #include #include "fu-plugin.h" #include "fu-plugin-vfuncs.h" #include "fu-uefi-bgrt.h" #include "fu-uefi-common.h" #include "fu-uefi-device.h" #include "fu-uefi-vars.h" struct FuPluginData { gchar *esp_path; gint esrt_status; FuUefiBgrt *bgrt; }; #ifndef HAVE_GIO_2_55_0 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(GUnixMountEntry, g_unix_mount_free) #pragma clang diagnostic pop #endif void fu_plugin_init (FuPlugin *plugin) { FuPluginData *data = fu_plugin_alloc_data (plugin, sizeof (FuPluginData)); #ifdef HAVE_FWUP_VERSION g_autofree gchar *version_str = NULL; #endif data->esp_path = NULL; data->bgrt = fu_uefi_bgrt_new (); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_RUN_AFTER, "upower"); fu_plugin_add_compile_version (plugin, "com.redhat.fwupdate", LIBFWUP_LIBRARY_VERSION); fu_plugin_add_compile_version (plugin, "com.redhat.efivar", EFIVAR_LIBRARY_VERSION); #ifdef HAVE_FWUP_VERSION version_str = g_strdup_printf ("%i", fwup_version ()); fu_plugin_add_runtime_version (plugin, "com.redhat.fwupdate", version_str); #endif } void fu_plugin_destroy (FuPlugin *plugin) { FuPluginData *data = fu_plugin_get_data (plugin); g_free (data->esp_path); g_object_unref (data->bgrt); } gboolean fu_plugin_clear_results (FuPlugin *plugin, FuDevice *device, GError **error) { FuUefiDevice *device_uefi = FU_UEFI_DEVICE (device); return fu_uefi_device_clear_status (device_uefi, error); } gboolean fu_plugin_get_results (FuPlugin *plugin, FuDevice *device, GError **error) { FuUefiDevice *device_uefi = FU_UEFI_DEVICE (device); FuUefiDeviceStatus status = fu_uefi_device_get_status (device_uefi); const gchar *tmp; g_autofree gchar *err_msg = NULL; g_autofree gchar *version_str = NULL; /* trivial case */ if (status == FU_UEFI_DEVICE_STATUS_SUCCESS) { fu_device_set_update_state (device, FWUPD_UPDATE_STATE_SUCCESS); return TRUE; } /* something went wrong */ fu_device_set_update_state (device, FWUPD_UPDATE_STATE_FAILED); version_str = g_strdup_printf ("%u", fu_uefi_device_get_version_error (device_uefi)); tmp = fu_uefi_device_status_to_string (status); if (tmp == NULL) { err_msg = g_strdup_printf ("failed to update to %s", version_str); } else { err_msg = g_strdup_printf ("failed to update to %s: %s", version_str, tmp); } fu_device_set_update_error (device, err_msg); return TRUE; } static GBytes * fu_plugin_uefi_get_splash_data (guint width, guint height, GError **error) { const gchar * const *langs = g_get_language_names (); const gchar *localedir = LOCALEDIR; const gsize chunk_size = 1024 * 1024; gsize buf_idx = 0; gsize buf_sz = chunk_size; gssize len; g_autofree gchar *basename = NULL; g_autofree guint8 *buf = NULL; g_autoptr(GBytes) compressed_data = NULL; g_autoptr(GConverter) conv = NULL; g_autoptr(GInputStream) stream_compressed = NULL; g_autoptr(GInputStream) stream_raw = NULL; /* ensure this is sane */ if (!g_str_has_prefix (localedir, "/")) localedir = "/usr/share/locale"; /* find the closest locale match, falling back to `en` and `C` */ basename = g_strdup_printf ("fwupd-%u-%u.bmp.gz", width, height); for (guint i = 0; langs[i] != NULL; i++) { g_autofree gchar *fn = NULL; if (g_str_has_suffix (langs[i], ".UTF-8")) continue; fn = g_build_filename (localedir, langs[i], "LC_IMAGES", basename, NULL); if (g_file_test (fn, G_FILE_TEST_EXISTS)) { compressed_data = fu_common_get_contents_bytes (fn, error); if (compressed_data == NULL) return NULL; break; } g_debug ("no %s found", fn); } /* we found nothing */ if (compressed_data == NULL) { g_autofree gchar *tmp = g_strjoinv (",", (gchar **) langs); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to get splash file for %s in %s", tmp, localedir); return NULL; } /* decompress data */ stream_compressed = g_memory_input_stream_new_from_bytes (compressed_data); conv = G_CONVERTER (g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP)); stream_raw = g_converter_input_stream_new (stream_compressed, conv); buf = g_malloc0 (buf_sz); while ((len = g_input_stream_read (stream_raw, buf + buf_idx, buf_sz - buf_idx, NULL, error)) > 0) { buf_idx += len; if (buf_sz - buf_idx < chunk_size) { buf_sz += chunk_size; buf = g_realloc (buf, buf_sz); } } if (len < 0) { g_prefix_error (error, "failed to decompress file: "); return NULL; } g_debug ("decompressed image to %" G_GSIZE_FORMAT "kb", buf_idx / 1024); return g_bytes_new_take (g_steal_pointer (&buf), buf_idx); } static gboolean fu_plugin_uefi_write_splash_data (FuPlugin *plugin, GBytes *blob, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); guint32 screen_x, screen_y; gsize buf_size = g_bytes_get_size (blob); gssize size; guint32 height, width; efi_ux_capsule_header_t header; efi_capsule_header_t capsule_header = { .flags = EFI_CAPSULE_HEADER_FLAGS_PERSIST_ACROSS_RESET, .guid = efi_guid_ux_capsule, .header_size = sizeof(efi_capsule_header_t), .capsule_image_size = 0 }; g_autofree gchar *fn = NULL; g_autofree gchar *directory = NULL; g_autofree gchar *basename = NULL; g_autoptr(GFile) ofile = NULL; g_autoptr(GOutputStream) ostream = NULL; /* get screen dimensions */ if (!fu_uefi_get_framebuffer_size (&screen_x, &screen_y, error)) return FALSE; if (!fu_uefi_get_bitmap_size ((const guint8 *) g_bytes_get_data (blob, NULL), buf_size, &width, &height, error)) { g_prefix_error (error, "splash invalid: "); return FALSE; } /* save to a predicatable filename */ directory = fu_uefi_get_esp_path_for_os (data->esp_path); basename = g_strdup_printf ("fwupdate-%s.cap", FU_UEFI_VARS_GUID_UX_CAPSULE); fn = g_build_filename (directory, "fw", basename, NULL); if (!fu_common_mkdir_parent (fn, error)) return FALSE; ofile = g_file_new_for_path (fn); ostream = G_OUTPUT_STREAM (g_file_replace (ofile, NULL, FALSE, G_FILE_CREATE_NONE, NULL, error)); if (ostream == NULL) return FALSE; capsule_header.capsule_image_size = g_bytes_get_size (blob) + sizeof(efi_capsule_header_t) + sizeof(header); memset (&header, '\0', sizeof(header)); header.version = 1; header.image_type = 0; header.reserved = 0; header.x_offset = (screen_x / 2) - (width / 2); header.y_offset = fu_uefi_bgrt_get_yoffset (data->bgrt) + fu_uefi_bgrt_get_height (data->bgrt); /* write capsule file */ size = g_output_stream_write (ostream, &capsule_header, capsule_header.header_size, NULL, error); if (size < 0) return FALSE; size = g_output_stream_write (ostream, &header, sizeof(header), NULL, error); if (size < 0) return FALSE; size = g_output_stream_write_bytes (ostream, blob, NULL, error); if (size < 0) return FALSE; /* success */ return TRUE; } static gboolean fu_plugin_uefi_update_splash (FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); guint best_idx = G_MAXUINT; guint32 lowest_border_pixels = G_MAXUINT; guint32 screen_height = 768; guint32 screen_width = 1024; g_autoptr(GBytes) image_bmp = NULL; struct { guint32 width; guint32 height; } sizes[] = { { 640, 480 }, /* matching the sizes in po/make-images */ { 800, 600 }, { 1024, 768 }, { 1920, 1080 }, { 3840, 2160 }, { 5120, 2880 }, { 5688, 3200 }, { 7680, 4320 }, { 0, 0 } }; /* get the boot graphics resource table data */ if (!fu_uefi_bgrt_get_supported (data->bgrt)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "BGRT is not supported"); return FALSE; } if (!fu_uefi_get_framebuffer_size (&screen_width, &screen_height, error)) return FALSE; g_debug ("framebuffer size %" G_GUINT32_FORMAT " x%" G_GUINT32_FORMAT, screen_width, screen_height); /* find the 'best sized' pre-generated image */ for (guint i = 0; sizes[i].width != 0; i++) { guint32 border_pixels; /* disregard any images that are bigger than the screen */ if (sizes[i].width > screen_width) continue; if (sizes[i].height > screen_height) continue; /* is this the best fit for the display */ border_pixels = (screen_width * screen_height) - (sizes[i].width * sizes[i].height); if (border_pixels < lowest_border_pixels) { lowest_border_pixels = border_pixels; best_idx = i; } } if (best_idx == G_MAXUINT) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to find a suitable image to use"); return FALSE; } /* get the raw data */ image_bmp = fu_plugin_uefi_get_splash_data (sizes[best_idx].width, sizes[best_idx].height, error); if (image_bmp == NULL) return FALSE; /* perform the upload */ return fu_plugin_uefi_write_splash_data (plugin, image_bmp, error); } static gboolean fu_plugin_uefi_esp_mounted (FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); g_autofree gchar *contents = NULL; g_auto(GStrv) lines = NULL; gsize length; if (!g_file_get_contents ("/proc/mounts", &contents, &length, error)) return FALSE; lines = g_strsplit (contents, "\n", 0); for (guint i = 0; lines[i] != NULL; i++) { if (lines[i] != NULL && g_strrstr (lines[i], data->esp_path)) return TRUE; } g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "EFI System partition %s is not mounted", data->esp_path); return FALSE; } gboolean fu_plugin_update (FuPlugin *plugin, FuDevice *device, GBytes *blob_fw, FwupdInstallFlags flags, GError **error) { const gchar *str; guint32 flashes_left; g_autofree gchar *efibootmgr_path = NULL; g_autofree gchar *boot_variables = NULL; g_autoptr(GError) error_splash = NULL; /* test the flash counter */ flashes_left = fu_device_get_flashes_left (device); if (flashes_left > 0) { g_debug ("%s has %" G_GUINT32_FORMAT " flashes left", fu_device_get_name (device), flashes_left); if ((flags & FWUPD_INSTALL_FLAG_FORCE) == 0 && flashes_left <= 2) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "%s only has %" G_GUINT32_FORMAT " flashes left -- " "see https://github.com/hughsie/fwupd/wiki/Dell-TPM:-flashes-left for more information.", fu_device_get_name (device), flashes_left); return FALSE; } } /* TRANSLATORS: this is shown when updating the firmware after the reboot */ str = _("Installing firmware update…"); g_assert (str != NULL); /* make sure that the ESP is mounted */ if (!fu_plugin_uefi_esp_mounted (plugin, error)) return FALSE; /* perform the update */ g_debug ("Performing UEFI capsule update"); fu_device_set_status (device, FWUPD_STATUS_SCHEDULING); if (!fu_plugin_uefi_update_splash (plugin, &error_splash)) { g_debug ("failed to upload UEFI UX capsule text: %s", error_splash->message); } if (!fu_device_write_firmware (device, blob_fw, error)) return FALSE; /* record boot information to system log for future debugging */ efibootmgr_path = g_find_program_in_path ("efibootmgr"); if (efibootmgr_path != NULL) { if (!g_spawn_command_line_sync ("efibootmgr -v", &boot_variables, NULL, NULL, error)) return FALSE; g_message ("Boot Information:\n%s", boot_variables); } return TRUE; } static AsVersionParseFlag fu_plugin_uefi_get_version_format_for_type (FuPlugin *plugin, FuUefiDeviceKind device_kind) { const gchar *content; const gchar *quirk; g_autofree gchar *group = NULL; /* we have no information for devices */ if (device_kind == FU_UEFI_DEVICE_KIND_DEVICE_FIRMWARE) return AS_VERSION_PARSE_FLAG_USE_TRIPLET; content = fu_plugin_get_dmi_value (plugin, FU_HWIDS_KEY_MANUFACTURER); if (content == NULL) return AS_VERSION_PARSE_FLAG_USE_TRIPLET; /* any quirks match */ group = g_strdup_printf ("SmbiosManufacturer=%s", content); quirk = fu_plugin_lookup_quirk_by_id (plugin, group, FU_QUIRKS_UEFI_VERSION_FORMAT); if (g_strcmp0 (quirk, "quad") == 0) return AS_VERSION_PARSE_FLAG_NONE; /* fall back */ return AS_VERSION_PARSE_FLAG_USE_TRIPLET; } static const gchar * fu_plugin_uefi_uefi_type_to_string (FuUefiDeviceKind device_kind) { if (device_kind == FU_UEFI_DEVICE_KIND_UNKNOWN) return "Unknown Firmware"; if (device_kind == FU_UEFI_DEVICE_KIND_SYSTEM_FIRMWARE) return "System Firmware"; if (device_kind == FU_UEFI_DEVICE_KIND_DEVICE_FIRMWARE) return "Device Firmware"; if (device_kind == FU_UEFI_DEVICE_KIND_UEFI_DRIVER) return "UEFI Driver"; if (device_kind == FU_UEFI_DEVICE_KIND_FMP) return "Firmware Management Protocol"; return NULL; } static gchar * fu_plugin_uefi_get_name_for_type (FuPlugin *plugin, FuUefiDeviceKind device_kind) { GString *display_name; /* set Display Name prefix for capsules that are not PCI cards */ display_name = g_string_new (fu_plugin_uefi_uefi_type_to_string (device_kind)); if (device_kind == FU_UEFI_DEVICE_KIND_DEVICE_FIRMWARE) { g_string_prepend (display_name, "UEFI "); } else { const gchar *tmp; tmp = fu_plugin_get_dmi_value (plugin, FU_HWIDS_KEY_PRODUCT_NAME); if (tmp != NULL && tmp[0] != '\0') { g_string_prepend (display_name, " "); g_string_prepend (display_name, tmp); } } return g_string_free (display_name, FALSE); } static gboolean fu_plugin_uefi_coldplug_device (FuPlugin *plugin, FuUefiDevice *dev, GError **error) { FuUefiDeviceKind device_kind; AsVersionParseFlag parse_flags; guint32 version_raw; g_autofree gchar *name = NULL; g_autofree gchar *version_lowest = NULL; g_autofree gchar *version = NULL; /* add details to the device */ device_kind = fu_uefi_device_get_kind (dev); parse_flags = fu_plugin_uefi_get_version_format_for_type (plugin, device_kind); version_raw = fu_uefi_device_get_version (dev); version = as_utils_version_from_uint32 (version_raw, parse_flags); fu_device_set_version (dev, version); name = fu_plugin_uefi_get_name_for_type (plugin, fu_uefi_device_get_kind (dev)); if (name != NULL) fu_device_set_name (FU_DEVICE (dev), name); version_raw = fu_uefi_device_get_version_lowest (dev); if (version_raw != 0) { version_lowest = as_utils_version_from_uint32 (version_raw, parse_flags); fu_device_set_version_lowest (FU_DEVICE (dev), version_lowest); } fu_device_add_flag (FU_DEVICE (dev), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_flag (FU_DEVICE (dev), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag (FU_DEVICE (dev), FWUPD_DEVICE_FLAG_NEEDS_REBOOT); fu_device_add_flag (FU_DEVICE (dev), FWUPD_DEVICE_FLAG_REQUIRE_AC); if (device_kind == FU_UEFI_DEVICE_KIND_DEVICE_FIRMWARE) { /* nothing better in the icon naming spec */ fu_device_add_icon (FU_DEVICE (dev), "audio-card"); } else { /* this is probably system firmware */ fu_device_add_icon (FU_DEVICE (dev), "computer"); fu_device_add_guid (FU_DEVICE (dev), "main-system-firmware"); } /* success */ return TRUE; } static void fu_plugin_uefi_test_secure_boot (FuPlugin *plugin) { const gchar *result_str = "Disabled"; if (fu_uefi_secure_boot_enabled ()) result_str = "Enabled"; g_debug ("SecureBoot is: %s", result_str); fu_plugin_add_report_metadata (plugin, "SecureBoot", result_str); } static gboolean fu_plugin_uefi_delete_old_capsules (FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); g_autofree gchar *pattern = NULL; g_autoptr(GPtrArray) files = NULL; /* delete any files matching the glob in the ESP */ files = fu_common_get_files_recursive (data->esp_path, error); if (files == NULL) return FALSE; pattern = g_build_filename (data->esp_path, "EFI/*/fw/fwupdate-*.cap", NULL); for (guint i = 0; i < files->len; i++) { const gchar *fn = g_ptr_array_index (files, i); if (fnmatch (pattern, fn, 0) == 0) { g_autoptr(GFile) file = g_file_new_for_path (fn); g_debug ("deleting %s", fn); if (!g_file_delete (file, NULL, error)) return FALSE; } } return TRUE; } static gchar * fu_plugin_uefi_guess_esp (void) { const gchar *paths[] = {"/boot/efi", "/boot", "/efi", NULL}; const gchar *path_tmp; /* for the test suite use local directory for ESP */ path_tmp = g_getenv ("FWUPD_UEFI_ESP_PATH"); if (path_tmp != NULL) return g_strdup (path_tmp); for (guint i = 0; paths[i] != NULL; i++) { g_autoptr(GUnixMountEntry) mount = g_unix_mount_at (paths[i], NULL); if (mount == NULL) continue; if (g_unix_mount_is_readonly (mount)) { g_debug ("%s is read only", paths[i]); continue; } return g_strdup (paths[i]); } return NULL; } gboolean fu_plugin_startup (FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); const gchar *key = "OverrideESPMountPoint"; /* get the supported status */ data->esrt_status = fwup_supported (); if (data->esrt_status == FWUP_SUPPORTED_STATUS_UNSUPPORTED) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "UEFI firmware updating not supported"); return FALSE; } /* are the EFI dirs set up so we can update each device */ if (!fu_uefi_vars_supported (error)) return FALSE; /* load from file */ data->esp_path = fu_plugin_get_config_value (plugin, key); if (data->esp_path != NULL) { if (!g_file_test (data->esp_path, G_FILE_TEST_IS_DIR)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Invalid %s specified in %s config: %s", fu_plugin_get_name (plugin), key, data->esp_path); return FALSE; } } /* try to guess from heuristics */ if (data->esp_path == NULL) { data->esp_path = fu_plugin_uefi_guess_esp (); if (data->esp_path == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Unable to determine EFI system partition " "location, override using %s in %s.conf", key, fu_plugin_get_name (plugin)); return FALSE; } } /* delete any existing .cap files to avoid the small ESP partition * from running out of space when we've done lots of firmware updates * -- also if the distro has changed the ESP may be different anyway */ if (fu_uefi_vars_exists (FU_UEFI_VARS_GUID_EFI_GLOBAL, "BootNext")) { g_debug ("detected BootNext, not cleaning up"); } else { if (!fu_plugin_uefi_delete_old_capsules (plugin, error)) return FALSE; if (!fu_uefi_vars_delete_with_glob (FU_UEFI_VARS_GUID_FWUPDATE, "fwupdate-*", error)) return FALSE; } /* save in report metadata */ g_debug ("ESP mountpoint set as %s", data->esp_path); fu_plugin_add_report_metadata (plugin, "ESPMountPoint", data->esp_path); return TRUE; } gboolean fu_plugin_coldplug (FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); const gchar *str; g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) entries = NULL; g_autofree gchar *esrt_path = NULL; g_autofree gchar *sysfsfwdir = NULL; /* get the directory of ESRT entries */ sysfsfwdir = fu_common_get_path (FU_PATH_KIND_SYSFSDIR_FW); esrt_path = g_build_filename (sysfsfwdir, "efi", "esrt", NULL); entries = fu_uefi_get_esrt_entry_paths (esrt_path, error); if (entries == NULL) return FALSE; /* add each device */ for (guint i = 0; i < entries->len; i++) { const gchar *path = g_ptr_array_index (entries, i); g_autoptr(FuUefiDevice) dev = fu_uefi_device_new_from_entry (path); if (!fu_plugin_uefi_coldplug_device (plugin, dev, error)) return FALSE; fu_device_set_metadata (FU_DEVICE (dev), "EspPath", data->esp_path); fu_plugin_device_add (plugin, FU_DEVICE (dev)); } /* for debugging problems later */ fu_plugin_uefi_test_secure_boot (plugin); if (!fu_uefi_bgrt_setup (data->bgrt, &error_local)) g_debug ("BGRT setup failed: %s", error_local->message); str = fu_uefi_bgrt_get_supported (data->bgrt) ? "Enabled" : "Disabled"; g_debug ("UX Capsule support : %s", str); fu_plugin_add_report_metadata (plugin, "UEFIUXCapsule", str); return TRUE; }