fwupd/libfwupdplugin/fu-efi-firmware-section.c
Richard Hughes 0c51630991 Check firmware magic in a more standard way
Some parsers are ignoring the magic when using _FLAG_IGNORE_CHECKSUM
(which is wrong; fuzzers have no problem with enforcing a static prefix)
and other either disregard the offset or check the magic in an unsafe
way. Also, use FWUPD_ERROR_INVALID_FILE consistently for magic failure.

Add a vfunc, and move all the clever code into one place.
2022-07-14 14:48:15 +01:00

308 lines
9.1 KiB
C

/*
* Copyright (C) 2020 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include "fu-byte-array.h"
#include "fu-bytes.h"
#include "fu-efi-common.h"
#include "fu-efi-firmware-common.h"
#include "fu-efi-firmware-section.h"
#include "fu-efi-firmware-volume.h"
#include "fu-mem.h"
/**
* FuEfiFirmwareSection:
*
* A UEFI firmware section.
*
* See also: [class@FuFirmware]
*/
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,
gsize offset_ignored,
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_memread_uint24_safe(buf,
bufsz,
FU_EFI_FIRMWARE_SECTION_OFFSET_SIZE,
&size,
G_LITTLE_ENDIAN,
error))
return FALSE;
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_memread_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_memread_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_memread_uint16_safe(buf,
bufsz,
FU_EFI_FIRMWARE_SECTION_OFFSET_GUID_ATTR,
&attr,
G_LITTLE_ENDIAN,
error))
return FALSE;
}
/* create blob */
blob = fu_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 | FWUPD_INSTALL_FLAG_NO_SEARCH, 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_with_patches(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_memwrite_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));
}