fwupd/libfwupdplugin/fu-efi-firmware-section.c
Richard Hughes 488f2e1f37 Export the EFI and IFD parsers
We'll need to use this from another plugin soon.
2021-07-12 10:33:38 +01:00

280 lines
8.9 KiB
C

/*
* Copyright (C) 2020 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <fwupdplugin.h>
#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));
}