mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-03 07:08:53 +00:00

This makes a lot more sense; we can parse a firmware and export the same XML we would use in a .builder.xml file. This allows us to two two things: * Check we can round trip from XML -> binary -> XML * Using a .builder.xml file we can check ->write() is endian safe
383 lines
12 KiB
C
383 lines
12 KiB
C
/*
|
|
* Copyright (C) 2020 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "fu-common.h"
|
|
|
|
#include "fu-efi-common.h"
|
|
#include "fu-efi-firmware-file.h"
|
|
#include "fu-efi-firmware-section.h"
|
|
#include "fu-efi-firmware-common.h"
|
|
|
|
typedef struct {
|
|
guint8 type;
|
|
guint8 attrib;
|
|
} FuEfiFirmwareFilePrivate;
|
|
|
|
G_DEFINE_TYPE_WITH_PRIVATE (FuEfiFirmwareFile, fu_efi_firmware_file, FU_TYPE_FIRMWARE)
|
|
#define GET_PRIVATE(o) (fu_efi_firmware_file_get_instance_private (o))
|
|
|
|
#define FU_EFI_FIRMWARE_FILE_ATTRIB_NONE 0x00
|
|
#define FU_EFI_FIRMWARE_FILE_ATTRIB_LARGE_FILE 0x01
|
|
#define FU_EFI_FIRMWARE_FILE_ATTRIB_DATA_ALIGNMENT_2 0x02
|
|
#define FU_EFI_FIRMWARE_FILE_ATTRIB_FIXED 0x04
|
|
#define FU_EFI_FIRMWARE_FILE_ATTRIB_DATA_ALIGNMENT 0x38
|
|
#define FU_EFI_FIRMWARE_FILE_ATTRIB_CHECKSUM 0x40
|
|
|
|
#define FU_EFI_FIRMWARE_FILE_TYPE_ALL 0x00
|
|
#define FU_EFI_FIRMWARE_FILE_TYPE_RAW 0x01
|
|
#define FU_EFI_FIRMWARE_FILE_TYPE_FREEFORM 0x02
|
|
#define FU_EFI_FIRMWARE_FILE_TYPE_SECURITY_CORE 0x03
|
|
#define FU_EFI_FIRMWARE_FILE_TYPE_PEI_CORE 0x04
|
|
#define FU_EFI_FIRMWARE_FILE_TYPE_DXE_CORE 0x05
|
|
#define FU_EFI_FIRMWARE_FILE_TYPE_PEIM 0x06
|
|
#define FU_EFI_FIRMWARE_FILE_TYPE_DRIVER 0x07
|
|
#define FU_EFI_FIRMWARE_FILE_TYPE_COMBINED_PEIM_DRIVER 0x08
|
|
#define FU_EFI_FIRMWARE_FILE_TYPE_APPLICATION 0x09
|
|
#define FU_EFI_FIRMWARE_FILE_TYPE_MM 0x0A
|
|
#define FU_EFI_FIRMWARE_FILE_TYPE_FIRMWARE_VOLUME_IMAGE 0x0B
|
|
#define FU_EFI_FIRMWARE_FILE_TYPE_COMBINED_MM_DXE 0x0C
|
|
#define FU_EFI_FIRMWARE_FILE_TYPE_MM_CORE 0x0D
|
|
#define FU_EFI_FIRMWARE_FILE_TYPE_MM_STANDALONE 0x0E
|
|
#define FU_EFI_FIRMWARE_FILE_TYPE_MM_CORE_STANDALONE 0x0F
|
|
#define FU_EFI_FIRMWARE_FILE_TYPE_FFS_PAD 0xF0
|
|
|
|
#define FU_EFI_FIRMWARE_FILE_OFFSET_NAME 0x00
|
|
#define FU_EFI_FIRMWARE_FILE_OFFSET_HDR_CHECKSUM 0x10
|
|
#define FU_EFI_FIRMWARE_FILE_OFFSET_DATA_CHECKSUM 0x11
|
|
#define FU_EFI_FIRMWARE_FILE_OFFSET_TYPE 0x12
|
|
#define FU_EFI_FIRMWARE_FILE_OFFSET_ATTRS 0x13
|
|
#define FU_EFI_FIRMWARE_FILE_OFFSET_SIZE 0x14
|
|
#define FU_EFI_FIRMWARE_FILE_OFFSET_STATE 0x17
|
|
#define FU_EFI_FIRMWARE_FILE_SIZE 0x18
|
|
|
|
static const gchar *
|
|
fu_efi_firmware_file_type_to_string (guint8 type)
|
|
{
|
|
if (type == FU_EFI_FIRMWARE_FILE_TYPE_ALL)
|
|
return "all";
|
|
if (type == FU_EFI_FIRMWARE_FILE_TYPE_RAW)
|
|
return "raw";
|
|
if (type == FU_EFI_FIRMWARE_FILE_TYPE_FREEFORM)
|
|
return "freeform";
|
|
if (type == FU_EFI_FIRMWARE_FILE_TYPE_SECURITY_CORE)
|
|
return "security-core";
|
|
if (type == FU_EFI_FIRMWARE_FILE_TYPE_PEI_CORE)
|
|
return "pei-core";
|
|
if (type == FU_EFI_FIRMWARE_FILE_TYPE_DXE_CORE)
|
|
return "dxe-core";
|
|
if (type == FU_EFI_FIRMWARE_FILE_TYPE_PEIM)
|
|
return "peim";
|
|
if (type == FU_EFI_FIRMWARE_FILE_TYPE_DRIVER)
|
|
return "driver";
|
|
if (type == FU_EFI_FIRMWARE_FILE_TYPE_COMBINED_PEIM_DRIVER)
|
|
return "combined-peim-driver";
|
|
if (type == FU_EFI_FIRMWARE_FILE_TYPE_APPLICATION)
|
|
return "application";
|
|
if (type == FU_EFI_FIRMWARE_FILE_TYPE_MM)
|
|
return "mm";
|
|
if (type == FU_EFI_FIRMWARE_FILE_TYPE_FIRMWARE_VOLUME_IMAGE)
|
|
return "firmware-volume-image";
|
|
if (type == FU_EFI_FIRMWARE_FILE_TYPE_COMBINED_MM_DXE)
|
|
return "combined-mm-dxe";
|
|
if (type == FU_EFI_FIRMWARE_FILE_TYPE_MM_CORE)
|
|
return "mm-core";
|
|
if (type == FU_EFI_FIRMWARE_FILE_TYPE_MM_STANDALONE)
|
|
return "mm-standalone";
|
|
if (type == FU_EFI_FIRMWARE_FILE_TYPE_MM_CORE_STANDALONE)
|
|
return "core-standalone";
|
|
if (type == FU_EFI_FIRMWARE_FILE_TYPE_FFS_PAD)
|
|
return "ffs-pad";
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
fu_efi_firmware_file_export (FuFirmware *firmware,
|
|
FuFirmwareExportFlags flags,
|
|
XbBuilderNode *bn)
|
|
{
|
|
FuEfiFirmwareFile *self = FU_EFI_FIRMWARE_FILE (firmware);
|
|
FuEfiFirmwareFilePrivate *priv = GET_PRIVATE (self);
|
|
|
|
fu_xmlb_builder_insert_kx (bn, "attrib", priv->attrib);
|
|
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_file_type_to_string (priv->type));
|
|
}
|
|
}
|
|
|
|
static guint8
|
|
fu_efi_firmware_file_data_checksum8 (GBytes *blob)
|
|
{
|
|
gsize bufsz = 0;
|
|
guint8 checksum = 0;
|
|
const guint8 *buf = g_bytes_get_data (blob, &bufsz);
|
|
for (gsize i = 0; i < bufsz; i++)
|
|
checksum += buf[i];
|
|
return 0x100 - checksum;
|
|
}
|
|
|
|
static guint8
|
|
fu_efi_firmware_file_hdr_checksum8 (GBytes *blob)
|
|
{
|
|
gsize bufsz = 0;
|
|
guint8 checksum = 0;
|
|
const guint8 *buf = g_bytes_get_data (blob, &bufsz);
|
|
for (gsize i = 0; i < bufsz; i++) {
|
|
if (i == FU_EFI_FIRMWARE_FILE_OFFSET_HDR_CHECKSUM)
|
|
continue;
|
|
if (i == FU_EFI_FIRMWARE_FILE_OFFSET_DATA_CHECKSUM)
|
|
continue;
|
|
if (i == FU_EFI_FIRMWARE_FILE_OFFSET_STATE)
|
|
continue;
|
|
checksum += buf[i];
|
|
}
|
|
return 0x100 - checksum;
|
|
}
|
|
|
|
static gboolean
|
|
fu_efi_firmware_file_parse (FuFirmware *firmware,
|
|
GBytes *fw,
|
|
guint64 addr_start,
|
|
guint64 addr_end,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
FuEfiFirmwareFile *self = FU_EFI_FIRMWARE_FILE (firmware);
|
|
FuEfiFirmwareFilePrivate *priv = GET_PRIVATE (self);
|
|
gsize bufsz = 0;
|
|
guint32 size = 0x0;
|
|
guint8 data_checksum = 0x0;
|
|
guint8 hdr_checksum = 0x0;
|
|
guint8 img_state = 0x0;
|
|
fwupd_guid_t guid = { 0x0 };
|
|
const guint8 *buf = g_bytes_get_data (fw, &bufsz);
|
|
g_autofree gchar *guid_str = NULL;
|
|
g_autoptr(GBytes) blob = NULL;
|
|
|
|
/* header */
|
|
if (!fu_memcpy_safe ((guint8 *) &guid, sizeof(guid), 0x0, /* dst */
|
|
buf, bufsz, FU_EFI_FIRMWARE_FILE_OFFSET_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_uint8_safe (buf, bufsz,
|
|
FU_EFI_FIRMWARE_FILE_OFFSET_STATE,
|
|
&img_state, error))
|
|
return FALSE;
|
|
if (img_state != 0xF8) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"state invalid, got 0x%x, expected 0x%x",
|
|
img_state, (guint) 0xF8);
|
|
return FALSE;
|
|
}
|
|
if (!fu_common_read_uint8_safe (buf, bufsz,
|
|
FU_EFI_FIRMWARE_FILE_OFFSET_HDR_CHECKSUM,
|
|
&hdr_checksum, error))
|
|
return FALSE;
|
|
if (!fu_common_read_uint8_safe (buf, bufsz,
|
|
FU_EFI_FIRMWARE_FILE_OFFSET_DATA_CHECKSUM,
|
|
&data_checksum, error))
|
|
return FALSE;
|
|
if (!fu_common_read_uint8_safe (buf, bufsz,
|
|
FU_EFI_FIRMWARE_FILE_OFFSET_TYPE,
|
|
&priv->type, error))
|
|
return FALSE;
|
|
if (!fu_common_read_uint8_safe (buf, bufsz,
|
|
FU_EFI_FIRMWARE_FILE_OFFSET_ATTRS,
|
|
&priv->attrib, error))
|
|
return FALSE;
|
|
if (!fu_common_read_uint32_safe (buf, bufsz, /* uint24_t! */
|
|
FU_EFI_FIRMWARE_FILE_OFFSET_SIZE,
|
|
&size, G_LITTLE_ENDIAN, error))
|
|
return FALSE;
|
|
size &= 0xFFFFFF;
|
|
if (size < FU_EFI_FIRMWARE_FILE_SIZE) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"invalid FFS length, got 0x%x",
|
|
(guint) size);
|
|
return FALSE;
|
|
}
|
|
|
|
/* verify header checksum */
|
|
if ((flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) {
|
|
g_autoptr(GBytes) hdr_blob = g_bytes_new_from_bytes (fw, 0x0, FU_EFI_FIRMWARE_FILE_SIZE);
|
|
guint8 hdr_checksum_verify = fu_efi_firmware_file_hdr_checksum8 (hdr_blob);
|
|
if (hdr_checksum_verify != hdr_checksum) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"checksum invalid, got %02x, expected %02x",
|
|
hdr_checksum_verify, hdr_checksum);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* add simple blob */
|
|
blob = fu_common_bytes_new_offset (fw,
|
|
FU_EFI_FIRMWARE_FILE_SIZE,
|
|
size - FU_EFI_FIRMWARE_FILE_SIZE,
|
|
error);
|
|
if (blob == NULL)
|
|
return FALSE;
|
|
|
|
/* add fv-image */
|
|
if (priv->type == FU_EFI_FIRMWARE_FILE_TYPE_FIRMWARE_VOLUME_IMAGE) {
|
|
if (!fu_efi_firmware_parse_sections (firmware, blob, flags, error))
|
|
return FALSE;
|
|
} else {
|
|
fu_firmware_set_bytes (firmware, blob);
|
|
}
|
|
|
|
/* verify data checksum */
|
|
if ((priv->attrib & FU_EFI_FIRMWARE_FILE_ATTRIB_CHECKSUM) > 0 &&
|
|
(flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) {
|
|
guint8 data_checksum_verify = fu_efi_firmware_file_data_checksum8 (blob);
|
|
if (data_checksum_verify != data_checksum) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"checksum invalid, got %02x, expected %02x",
|
|
data_checksum_verify, data_checksum);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* align size for volume */
|
|
fu_firmware_set_size (firmware,
|
|
fu_common_align_up (size,
|
|
fu_firmware_get_alignment (firmware)));
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static GBytes *
|
|
fu_efi_firmware_file_write_sections (FuFirmware *firmware, GError **error)
|
|
{
|
|
g_autoptr(GPtrArray) images = fu_firmware_get_images (firmware);
|
|
g_autoptr(GByteArray) buf = g_byte_array_new ();
|
|
|
|
/* no sections defined */
|
|
if (images->len == 0)
|
|
return fu_firmware_get_bytes (firmware, error);
|
|
|
|
/* add each section */
|
|
for (guint i = 0; i < images->len; i++) {
|
|
FuFirmware *img = g_ptr_array_index (images, i);
|
|
g_autoptr(GBytes) blob = NULL;
|
|
fu_firmware_set_offset (img, buf->len);
|
|
blob = fu_firmware_write (img, error);
|
|
if (blob == NULL)
|
|
return NULL;
|
|
g_byte_array_append (buf,
|
|
g_bytes_get_data (blob, NULL),
|
|
g_bytes_get_size (blob));
|
|
fu_byte_array_align_up (buf, fu_firmware_get_alignment (img), 0xFF);
|
|
}
|
|
|
|
/* success */
|
|
return g_byte_array_free_to_bytes (g_steal_pointer (&buf));
|
|
}
|
|
|
|
static GBytes *
|
|
fu_efi_firmware_file_write (FuFirmware *firmware, GError **error)
|
|
{
|
|
FuEfiFirmwareFile *self = FU_EFI_FIRMWARE_FILE (firmware);
|
|
FuEfiFirmwareFilePrivate *priv = GET_PRIVATE (self);
|
|
fwupd_guid_t guid = { 0x0 };
|
|
g_autoptr(GByteArray) buf = g_byte_array_new ();
|
|
g_autoptr(GBytes) blob = NULL;
|
|
g_autoptr(GBytes) hdr_blob = NULL;
|
|
|
|
/* simple blob for now */
|
|
blob = fu_efi_firmware_file_write_sections (firmware, error);
|
|
if (blob == NULL)
|
|
return NULL;
|
|
|
|
/* header */
|
|
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_uint8 (buf, 0x0); /* hdr_checksum */
|
|
fu_byte_array_append_uint8 (buf, fu_efi_firmware_file_data_checksum8 (blob));
|
|
fu_byte_array_append_uint8 (buf, priv->type); /* data_checksum */
|
|
fu_byte_array_append_uint8 (buf, priv->attrib); /* data_checksum */
|
|
fu_byte_array_append_uint32 (buf, g_bytes_get_size (blob) + FU_EFI_FIRMWARE_FILE_SIZE, G_LITTLE_ENDIAN);
|
|
buf->data[FU_EFI_FIRMWARE_FILE_OFFSET_STATE] = 0xF8; /* overwrite the LSB of size */
|
|
|
|
/* fix up header checksum */
|
|
hdr_blob = g_bytes_new (buf->data, buf->len);
|
|
buf->data[FU_EFI_FIRMWARE_FILE_OFFSET_HDR_CHECKSUM] = fu_efi_firmware_file_hdr_checksum8 (hdr_blob);
|
|
|
|
/* payload */
|
|
fu_byte_array_append_bytes (buf, blob);
|
|
|
|
/* success */
|
|
return g_byte_array_free_to_bytes (g_steal_pointer (&buf));
|
|
}
|
|
|
|
static gboolean
|
|
fu_efi_firmware_file_build (FuFirmware *firmware, XbNode *n, GError **error)
|
|
{
|
|
FuEfiFirmwareFile *self = FU_EFI_FIRMWARE_FILE (firmware);
|
|
FuEfiFirmwareFilePrivate *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;
|
|
tmp = xb_node_query_text_as_uint (n, "attrib", NULL);
|
|
if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT8)
|
|
priv->attrib = tmp;
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
fu_efi_firmware_file_init (FuEfiFirmwareFile *self)
|
|
{
|
|
FuEfiFirmwareFilePrivate *priv = GET_PRIVATE (self);
|
|
priv->attrib = FU_EFI_FIRMWARE_FILE_ATTRIB_NONE;
|
|
priv->type = FU_EFI_FIRMWARE_FILE_TYPE_RAW;
|
|
fu_firmware_set_alignment (FU_FIRMWARE (self), 3);
|
|
}
|
|
|
|
static void
|
|
fu_efi_firmware_file_class_init (FuEfiFirmwareFileClass *klass)
|
|
{
|
|
FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS (klass);
|
|
klass_firmware->parse = fu_efi_firmware_file_parse;
|
|
klass_firmware->write = fu_efi_firmware_file_write;
|
|
klass_firmware->build = fu_efi_firmware_file_build;
|
|
klass_firmware->export = fu_efi_firmware_file_export;
|
|
}
|
|
|
|
/**
|
|
* fu_efi_firmware_file_new:
|
|
*
|
|
* Creates a new #FuFirmware
|
|
*
|
|
* Since: 1.6.0
|
|
**/
|
|
FuFirmware *
|
|
fu_efi_firmware_file_new (void)
|
|
{
|
|
return FU_FIRMWARE (g_object_new (FU_TYPE_EFI_FIRMWARE_FILE, NULL));
|
|
}
|