/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-efi-common.h" #include "fu-efi-firmware-section.h" #include "fu-efi-firmware-common.h" #include "fu-efi-firmware-volume.h" typedef struct { guint8 type; } FuEfiFirmwareSectionPrivate; G_DEFINE_TYPE_WITH_PRIVATE (FuEfiFirmwareSection, fu_efi_firmware_section, FU_TYPE_FIRMWARE) #define GET_PRIVATE(o) (fu_efi_firmware_section_get_instance_private (o)) #define FU_EFI_FIRMWARE_SECTION_OFFSET_SIZE 0x00 #define FU_EFI_FIRMWARE_SECTION_OFFSET_TYPE 0x03 #define FU_EFI_FIRMWARE_SECTION_SIZE 0x04 /* only GUID defined */ #define FU_EFI_FIRMWARE_SECTION_OFFSET_GUID_NAME 0x04 #define FU_EFI_FIRMWARE_SECTION_OFFSET_GUID_DATA_OFFSET 0x14 #define FU_EFI_FIRMWARE_SECTION_OFFSET_GUID_ATTR 0x16 #define FU_EFI_FIRMWARE_SECTION_TYPE_COMPRESSION 0x01 #define FU_EFI_FIRMWARE_SECTION_TYPE_GUID_DEFINED 0x02 #define FU_EFI_FIRMWARE_SECTION_TYPE_DISPOSABLE 0x03 #define FU_EFI_FIRMWARE_SECTION_TYPE_PE32 0x10 #define FU_EFI_FIRMWARE_SECTION_TYPE_PIC 0x11 #define FU_EFI_FIRMWARE_SECTION_TYPE_TE 0x12 #define FU_EFI_FIRMWARE_SECTION_TYPE_DXE_DEPEX 0x13 #define FU_EFI_FIRMWARE_SECTION_TYPE_VERSION 0x14 #define FU_EFI_FIRMWARE_SECTION_TYPE_USER_INTERFACE 0x15 #define FU_EFI_FIRMWARE_SECTION_TYPE_COMPATIBILITY16 0x16 #define FU_EFI_FIRMWARE_SECTION_TYPE_VOLUME_IMAGE 0x17 #define FU_EFI_FIRMWARE_SECTION_TYPE_FREEFORM_SUBTYPE_GUID 0x18 #define FU_EFI_FIRMWARE_SECTION_TYPE_RAW 0x19 #define FU_EFI_FIRMWARE_SECTION_TYPE_PEI_DEPEX 0x1B #define FU_EFI_FIRMWARE_SECTION_TYPE_MM_DEPEX 0x1C static const gchar * fu_efi_firmware_section_type_to_string (guint8 type) { if (type == FU_EFI_FIRMWARE_SECTION_TYPE_COMPRESSION) return "compression"; if (type == FU_EFI_FIRMWARE_SECTION_TYPE_GUID_DEFINED) return "guid-defined"; if (type == FU_EFI_FIRMWARE_SECTION_TYPE_DISPOSABLE) return "disposable"; if (type == FU_EFI_FIRMWARE_SECTION_TYPE_PE32) return "pe32"; if (type == FU_EFI_FIRMWARE_SECTION_TYPE_PIC) return "pic"; if (type == FU_EFI_FIRMWARE_SECTION_TYPE_TE) return "te"; if (type == FU_EFI_FIRMWARE_SECTION_TYPE_DXE_DEPEX) return "dxe-depex"; if (type == FU_EFI_FIRMWARE_SECTION_TYPE_VERSION) return "version"; if (type == FU_EFI_FIRMWARE_SECTION_TYPE_USER_INTERFACE) return "user-interface"; if (type == FU_EFI_FIRMWARE_SECTION_TYPE_COMPATIBILITY16) return "compatibility16"; if (type == FU_EFI_FIRMWARE_SECTION_TYPE_VOLUME_IMAGE) return "volume-image"; if (type == FU_EFI_FIRMWARE_SECTION_TYPE_FREEFORM_SUBTYPE_GUID) return "freeform-subtype-guid"; if (type == FU_EFI_FIRMWARE_SECTION_TYPE_RAW) return "raw"; if (type == FU_EFI_FIRMWARE_SECTION_TYPE_PEI_DEPEX) return "pei-depex"; if (type == FU_EFI_FIRMWARE_SECTION_TYPE_MM_DEPEX) return "mm-depex"; return NULL; } static void fu_efi_firmware_section_export (FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuEfiFirmwareSection *self = FU_EFI_FIRMWARE_SECTION (firmware); FuEfiFirmwareSectionPrivate *priv = GET_PRIVATE (self); fu_xmlb_builder_insert_kx (bn, "type", priv->type); if (flags & FU_FIRMWARE_EXPORT_FLAG_INCLUDE_DEBUG) { fu_xmlb_builder_insert_kv (bn, "name", fu_efi_guid_to_name (fu_firmware_get_id (firmware))); fu_xmlb_builder_insert_kv (bn, "type_name", fu_efi_firmware_section_type_to_string (priv->type)); } } static gboolean fu_efi_firmware_section_parse (FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { FuEfiFirmwareSection *self = FU_EFI_FIRMWARE_SECTION (firmware); FuEfiFirmwareSectionPrivate *priv = GET_PRIVATE (self); gsize bufsz = 0; guint16 attr = 0x0; guint16 offset = FU_EFI_FIRMWARE_SECTION_SIZE; guint32 size = 0x0; const guint8 *buf = g_bytes_get_data (fw, &bufsz); g_autoptr(GBytes) blob = NULL; if (!fu_common_read_uint32_safe (buf, bufsz, /* uint24_t! */ FU_EFI_FIRMWARE_SECTION_OFFSET_SIZE, &size, G_LITTLE_ENDIAN, error)) return FALSE; size &= 0xFFFFFF; if (size < FU_EFI_FIRMWARE_SECTION_SIZE) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid section size, got 0x%x", (guint) size); return FALSE; } if (!fu_common_read_uint8_safe (buf, bufsz, FU_EFI_FIRMWARE_SECTION_OFFSET_TYPE, &priv->type, error)) return FALSE; /* name */ if (priv->type == FU_EFI_FIRMWARE_SECTION_TYPE_GUID_DEFINED) { fwupd_guid_t guid = { 0x0 }; g_autofree gchar *guid_str = NULL; if (!fu_memcpy_safe ((guint8 *) &guid, sizeof(guid), 0x0, /* dst */ buf, bufsz, FU_EFI_FIRMWARE_SECTION_OFFSET_GUID_NAME, /* src */ sizeof(guid), error)) return FALSE; guid_str = fwupd_guid_to_string (&guid, FWUPD_GUID_FLAG_MIXED_ENDIAN); fu_firmware_set_id (firmware, guid_str); if (!fu_common_read_uint16_safe (buf, bufsz, FU_EFI_FIRMWARE_SECTION_OFFSET_GUID_DATA_OFFSET, &offset, G_LITTLE_ENDIAN, error)) return FALSE; if (offset < FU_EFI_FIRMWARE_SECTION_SIZE) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid section size, got 0x%x", (guint) size); return FALSE; } if (!fu_common_read_uint16_safe (buf, bufsz, FU_EFI_FIRMWARE_SECTION_OFFSET_GUID_ATTR, &attr, G_LITTLE_ENDIAN, error)) return FALSE; } /* create blob */ blob = fu_common_bytes_new_offset (fw, offset, size - offset, error); if (blob == NULL) return FALSE; fu_firmware_set_offset (firmware, offset); fu_firmware_set_size (firmware, size); fu_firmware_set_bytes (firmware, blob); /* nested volume */ if (priv->type == FU_EFI_FIRMWARE_SECTION_TYPE_VOLUME_IMAGE) { g_autoptr(FuFirmware) img = fu_efi_firmware_volume_new (); if (!fu_firmware_parse (img, blob, flags, error)) return FALSE; fu_firmware_add_image (firmware, img); /* LZMA */ } else if (priv->type == FU_EFI_FIRMWARE_SECTION_TYPE_GUID_DEFINED && g_strcmp0 (fu_firmware_get_id (firmware), FU_EFI_FIRMWARE_SECTION_LZMA_COMPRESS) == 0) { g_autoptr(GBytes) blob_uncomp = NULL; /* parse all sections */ blob_uncomp = fu_efi_firmware_decompress_lzma (blob, error); if (blob_uncomp == NULL) return FALSE; if (!fu_efi_firmware_parse_sections (firmware, blob_uncomp, flags, error)) return FALSE; } /* success */ return TRUE; } static GBytes * fu_efi_firmware_section_write (FuFirmware *firmware, GError **error) { FuEfiFirmwareSection *self = FU_EFI_FIRMWARE_SECTION (firmware); FuEfiFirmwareSectionPrivate *priv = GET_PRIVATE (self); g_autoptr(GByteArray) buf = g_byte_array_new (); g_autoptr(GBytes) blob = NULL; /* simple blob for now */ blob = fu_firmware_get_bytes (firmware, error); if (blob == NULL) return NULL; /* header */ fu_byte_array_append_uint32 (buf, 0x0, G_LITTLE_ENDIAN); /* will fixup */ if (priv->type == FU_EFI_FIRMWARE_SECTION_TYPE_GUID_DEFINED) { fwupd_guid_t guid = { 0x0 }; if (!fwupd_guid_from_string (fu_firmware_get_id (firmware), &guid, FWUPD_GUID_FLAG_MIXED_ENDIAN, error)) return NULL; g_byte_array_append (buf, (guint8 *) &guid, sizeof(guid)); fu_byte_array_append_uint16 (buf, buf->len + 0x4, G_LITTLE_ENDIAN); fu_byte_array_append_uint16 (buf, 0x0, G_LITTLE_ENDIAN); } /* correct size and type in common header */ if (!fu_common_write_uint32_safe (buf->data, buf->len, /* uint24_t! */ FU_EFI_FIRMWARE_SECTION_OFFSET_SIZE, buf->len + g_bytes_get_size (blob), G_LITTLE_ENDIAN, error)) return NULL; buf->data[FU_EFI_FIRMWARE_SECTION_OFFSET_TYPE] = priv->type; /* blob */ fu_byte_array_append_bytes (buf, blob); return g_byte_array_free_to_bytes (g_steal_pointer (&buf)); } static gboolean fu_efi_firmware_section_build (FuFirmware *firmware, XbNode *n, GError **error) { FuEfiFirmwareSection *self = FU_EFI_FIRMWARE_SECTION (firmware); FuEfiFirmwareSectionPrivate *priv = GET_PRIVATE (self); guint64 tmp; /* simple properties */ tmp = xb_node_query_text_as_uint (n, "type", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT8) priv->type = tmp; /* success */ return TRUE; } static void fu_efi_firmware_section_init (FuEfiFirmwareSection *self) { FuEfiFirmwareSectionPrivate *priv = GET_PRIVATE (self); priv->type = FU_EFI_FIRMWARE_SECTION_TYPE_RAW; // fu_firmware_set_alignment (FU_FIRMWARE (self), FU_FIRMWARE_ALIGNMENT_8); } static void fu_efi_firmware_section_class_init (FuEfiFirmwareSectionClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS (klass); klass_firmware->parse = fu_efi_firmware_section_parse; klass_firmware->write = fu_efi_firmware_section_write; klass_firmware->build = fu_efi_firmware_section_build; klass_firmware->export = fu_efi_firmware_section_export; } /** * fu_efi_firmware_section_new: * * Creates a new #FuFirmware * * Since: 1.6.2 **/ FuFirmware * fu_efi_firmware_section_new (void) { return FU_FIRMWARE (g_object_new (FU_TYPE_EFI_FIRMWARE_SECTION, NULL)); }