fwupd/libfwupdplugin/fu-pefile-firmware.c
Richard Hughes 1ca3cdd4d9 Add a pefile firmware parser
This will eventually allow us to reconstruct a SBoM using a UEFI file volume.

This can be used like this `fwupdtool firmware-parse efi/fwupdx64.efi pefile`
to produce:

    <firmware gtype="FuPefileFirmware">
      <data size="0xef59">MZ.</data>
      <firmware>
        <id>.text</id>
        <offset>0x2d0</offset>
        <size>0x7bab</size>
        <data size="0x7bab">H...QRH.=....H.5</data>
      </firmware>
      ...
      <firmware gtype="FuCoswidFirmware">
        <id>fwupd-efi:fwupdx64</id>
        <idx>0x7</idx>
        <version>1.4</version>
        <offset>0xc4e0</offset>
        <size>0xce</size>
        <data size="0xce">..een-US</data>
        <version_scheme>semver</version_scheme>
        <product>fwupdx64</product>
        <summary>EFI helpers to install system firmware</summary>
        <colloquial_version>1.3-7-g881a508</colloquial_version>
        <link>
          <href>https://spdx.org/licenses/LGPL-2.0.html</href>
          <rel>license</rel>
        </link>
        <entity>
          <name>Richard Hughes</name>
          <regid>hughsie.com</regid>
          <role>maintainer</role>
          <role>tag-creator</role>
        </entity>
      </firmware>
    </firmware>
2023-01-06 17:32:59 +00:00

213 lines
4.8 KiB
C

/*
* Copyright (C) 2023 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#define G_LOG_DOMAIN "FuFirmware"
#include "config.h"
#include "fu-bytes.h"
#include "fu-coswid-firmware.h"
#include "fu-dump.h"
#include "fu-mem.h"
#include "fu-pefile-firmware.h"
#include "fu-string.h"
/**
* FuPefileFirmware:
*
* A PE file consists of a Microsoft MS-DOS stub, the PE signature, the COFF file header, and an
* optional header, followed by section data.
*
* Documented:
* https://learn.microsoft.com/en-gb/windows/win32/debug/pe-format
*/
G_DEFINE_TYPE(FuPefileFirmware, fu_pefile_firmware, FU_TYPE_FIRMWARE)
static gboolean
fu_pefile_firmware_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error)
{
guint16 magic = 0x0;
if (!fu_memread_uint16_safe(g_bytes_get_data(fw, NULL),
g_bytes_get_size(fw),
offset,
&magic,
G_LITTLE_ENDIAN,
error)) {
g_prefix_error(error, "failed to read magic: ");
return FALSE;
}
if (magic != 0x5A4D) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"invalid magic for file");
return FALSE;
}
/* success */
return TRUE;
}
static gboolean
fu_pefile_firmware_parse(FuFirmware *firmware,
GBytes *fw,
gsize offset,
FwupdInstallFlags flags,
GError **error)
{
gsize bufsz = 0;
gsize hdr_offset = offset;
guint16 opt_hdrsz = 0x0;
guint32 nr_sections = 0x0;
guint32 offset_sig = 0x0;
guint32 signature = 0x0;
const guint8 *buf = g_bytes_get_data(fw, &bufsz);
/* we already checked the MS-DOS magic, check the signature now */
if (!fu_memread_uint32_safe(buf,
bufsz,
hdr_offset + 0x3C,
&offset_sig,
G_LITTLE_ENDIAN,
error))
return FALSE;
hdr_offset += offset_sig;
if (!fu_memread_uint32_safe(buf, bufsz, hdr_offset, &signature, G_LITTLE_ENDIAN, error))
return FALSE;
if (signature != 0x4550) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"invalid signature for file");
return FALSE;
}
hdr_offset += 4;
/* read number of sections */
if (!fu_memread_uint32_safe(buf,
bufsz,
hdr_offset + 0x02,
&nr_sections,
G_LITTLE_ENDIAN,
error))
return FALSE;
if (nr_sections == 0) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"invalid number of sections");
return FALSE;
}
/* read optional extra header size */
if (!fu_memread_uint16_safe(buf,
bufsz,
hdr_offset + 0x10,
&opt_hdrsz,
G_LITTLE_ENDIAN,
error))
return FALSE;
hdr_offset += 0x14 + opt_hdrsz;
/* read out COFF header */
for (guint idx = 0; idx < nr_sections; idx++) {
guint32 sect_length = 0x0;
guint32 sect_offset = 0x0;
guint8 name[8] = {0x0};
g_autofree gchar *sect_id = NULL;
g_autoptr(FuFirmware) fw_sect = NULL;
g_autoptr(GBytes) sect_blob = NULL;
/* read the section name */
if (!fu_memcpy_safe(name,
sizeof(name),
0x0,
buf,
bufsz,
hdr_offset,
sizeof(name),
error))
return FALSE;
sect_id = fu_strsafe((const gchar *)name, sizeof(name));
if (sect_id == NULL) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"no section name");
return FALSE;
}
/* create new firmware */
if (g_strcmp0(sect_id, ".sbom") == 0) {
fw_sect = fu_coswid_firmware_new();
} else {
fw_sect = fu_firmware_new();
}
fu_firmware_set_idx(fw_sect, idx);
fu_firmware_set_id(fw_sect, sect_id);
/* add data */
if (!fu_memread_uint32_safe(buf,
bufsz,
hdr_offset + 0x08,
&sect_length,
G_LITTLE_ENDIAN,
error))
return FALSE;
fu_firmware_set_size(fw_sect, sect_length);
if (!fu_memread_uint32_safe(buf,
bufsz,
hdr_offset + 0x14,
&sect_offset,
G_LITTLE_ENDIAN,
error))
return FALSE;
fu_firmware_set_offset(fw_sect, sect_offset);
sect_blob = fu_bytes_new_offset(fw, sect_offset, sect_length, error);
if (sect_blob == NULL) {
g_prefix_error(error, "failed to get raw data for %s: ", sect_id);
return FALSE;
}
if (!fu_firmware_parse(fw_sect, sect_blob, flags, error))
return FALSE;
fu_firmware_add_image(firmware, fw_sect);
/* next! */
hdr_offset += 0x28;
}
/* success */
return TRUE;
}
static void
fu_pefile_firmware_init(FuPefileFirmware *self)
{
}
static void
fu_pefile_firmware_class_init(FuPefileFirmwareClass *klass)
{
FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass);
klass_firmware->check_magic = fu_pefile_firmware_check_magic;
klass_firmware->parse = fu_pefile_firmware_parse;
}
/**
* fu_pefile_firmware_new:
*
* Creates a new #FuPefileFirmware
*
* Since: 1.8.10
**/
FuFirmware *
fu_pefile_firmware_new(void)
{
return FU_FIRMWARE(g_object_new(FU_TYPE_PEFILE_FIRMWARE, NULL));
}