/* * Copyright (C) 2023 Richard Hughes * * 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, §_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, §_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)); }