mirror of
https://git.proxmox.com/git/fwupd
synced 2025-04-29 04:52:55 +00:00

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.
308 lines
9.1 KiB
C
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));
|
|
}
|