mirror of
https://git.proxmox.com/git/fwupd
synced 2025-04-29 18:33:13 +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.
247 lines
6.1 KiB
C
247 lines
6.1 KiB
C
/*
|
|
* Copyright (C) 2020 Benson Leung <bleung@chromium.org>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "fu-byte-array.h"
|
|
#include "fu-bytes.h"
|
|
#include "fu-common.h"
|
|
#include "fu-fmap-firmware.h"
|
|
#include "fu-mem.h"
|
|
|
|
/**
|
|
* FuFmapFirmware:
|
|
*
|
|
* A FMAP firmware image.
|
|
*
|
|
* See also: [class@FuFirmware]
|
|
*/
|
|
|
|
#define FMAP_SIGNATURE "__FMAP__"
|
|
#define FMAP_AREANAME "FMAP"
|
|
|
|
G_DEFINE_TYPE(FuFmapFirmware, fu_fmap_firmware, FU_TYPE_FIRMWARE)
|
|
|
|
static gboolean
|
|
fu_fmap_firmware_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error)
|
|
{
|
|
guint8 magic[8] = {0x0};
|
|
|
|
if (!fu_memcpy_safe(magic,
|
|
sizeof(magic),
|
|
0, /* dst */
|
|
g_bytes_get_data(fw, NULL),
|
|
g_bytes_get_size(fw),
|
|
offset,
|
|
sizeof(magic),
|
|
error)) {
|
|
g_prefix_error(error, "failed to read magic: ");
|
|
return FALSE;
|
|
}
|
|
if (memcmp(magic, FMAP_SIGNATURE, sizeof(magic)) != 0) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"invalid magic for file");
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_fmap_firmware_parse(FuFirmware *firmware,
|
|
GBytes *fw,
|
|
gsize offset,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
FuFmapFirmwareClass *klass_firmware = FU_FMAP_FIRMWARE_GET_CLASS(firmware);
|
|
gsize bufsz;
|
|
const guint8 *buf = g_bytes_get_data(fw, &bufsz);
|
|
FuFmap fmap;
|
|
|
|
/* corrupt */
|
|
if (g_bytes_get_size(fw) < sizeof(FuFmap)) {
|
|
g_set_error_literal(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"firmware too small for fmap");
|
|
return FALSE;
|
|
}
|
|
|
|
/* load header */
|
|
if (!fu_memcpy_safe((guint8 *)&fmap,
|
|
sizeof(fmap),
|
|
0x0, /* dst */
|
|
buf,
|
|
bufsz,
|
|
offset, /* src */
|
|
sizeof(fmap),
|
|
error))
|
|
return FALSE;
|
|
fu_firmware_set_addr(firmware, GUINT64_FROM_LE(fmap.base));
|
|
|
|
if (GUINT32_FROM_LE(fmap.size) != bufsz) {
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"file size incorrect, expected 0x%04x got 0x%04x",
|
|
(guint)fmap.size,
|
|
(guint)bufsz);
|
|
return FALSE;
|
|
}
|
|
if (GUINT16_FROM_LE(fmap.nareas) < 1) {
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"number of areas too small, got %" G_GUINT16_FORMAT,
|
|
GUINT16_FROM_LE(fmap.nareas));
|
|
return FALSE;
|
|
}
|
|
offset += sizeof(fmap);
|
|
|
|
for (gsize i = 0; i < GUINT16_FROM_LE(fmap.nareas); i++) {
|
|
FuFmapArea area;
|
|
g_autoptr(FuFirmware) img = NULL;
|
|
g_autoptr(GBytes) bytes = NULL;
|
|
g_autofree gchar *area_name = NULL;
|
|
|
|
/* load area */
|
|
if (!fu_memcpy_safe((guint8 *)&area,
|
|
sizeof(area),
|
|
0x0, /* dst */
|
|
buf,
|
|
bufsz,
|
|
offset, /* src */
|
|
sizeof(area),
|
|
error))
|
|
return FALSE;
|
|
|
|
/* skip */
|
|
if (area.size == 0)
|
|
continue;
|
|
|
|
bytes = fu_bytes_new_offset(fw,
|
|
(gsize)GUINT32_FROM_LE(area.offset),
|
|
(gsize)GUINT32_FROM_LE(area.size),
|
|
error);
|
|
if (bytes == NULL)
|
|
return FALSE;
|
|
area_name = g_strndup((const gchar *)area.name, FU_FMAP_FIRMWARE_STRLEN);
|
|
img = fu_firmware_new_from_bytes(bytes);
|
|
fu_firmware_set_id(img, area_name);
|
|
fu_firmware_set_idx(img, i + 1);
|
|
fu_firmware_set_addr(img, GUINT32_FROM_LE(area.offset));
|
|
fu_firmware_add_image(firmware, img);
|
|
|
|
if (g_strcmp0(area_name, FMAP_AREANAME) == 0) {
|
|
g_autofree gchar *version = NULL;
|
|
version = g_strdup_printf("%d.%d", fmap.ver_major, fmap.ver_minor);
|
|
fu_firmware_set_version(img, version);
|
|
}
|
|
offset += sizeof(area);
|
|
}
|
|
|
|
/* subclassed */
|
|
if (klass_firmware->parse != NULL) {
|
|
if (!klass_firmware->parse(firmware, fw, offset, flags, error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static GBytes *
|
|
fu_fmap_firmware_write(FuFirmware *firmware, GError **error)
|
|
{
|
|
gsize total_sz;
|
|
gsize offset;
|
|
g_autoptr(GPtrArray) images = fu_firmware_get_images(firmware);
|
|
g_autoptr(GByteArray) buf = g_byte_array_new();
|
|
FuFmap hdr = {
|
|
.signature = {FMAP_SIGNATURE},
|
|
.ver_major = 0x1,
|
|
.ver_minor = 0x1,
|
|
.base = GUINT64_TO_LE(fu_firmware_get_addr(firmware)),
|
|
.size = 0x0,
|
|
.name = "",
|
|
.nareas = GUINT16_TO_LE(images->len),
|
|
};
|
|
|
|
/* pad to offset */
|
|
if (fu_firmware_get_offset(firmware) > 0)
|
|
fu_byte_array_set_size(buf, fu_firmware_get_offset(firmware), 0x00);
|
|
|
|
/* add header */
|
|
total_sz = offset = sizeof(hdr) + (sizeof(FuFmapArea) * images->len);
|
|
for (guint i = 0; i < images->len; i++) {
|
|
FuFirmware *img = g_ptr_array_index(images, i);
|
|
g_autoptr(GBytes) fw = fu_firmware_get_bytes_with_patches(img, error);
|
|
if (fw == NULL)
|
|
return NULL;
|
|
total_sz += g_bytes_get_size(fw);
|
|
}
|
|
hdr.size = GUINT32_TO_LE(fu_firmware_get_offset(firmware) + total_sz);
|
|
g_byte_array_append(buf, (const guint8 *)&hdr, sizeof(hdr));
|
|
|
|
/* add each area */
|
|
for (guint i = 0; i < images->len; i++) {
|
|
FuFirmware *img = g_ptr_array_index(images, i);
|
|
const gchar *id = fu_firmware_get_id(img);
|
|
g_autoptr(GBytes) fw = fu_firmware_get_bytes_with_patches(img, NULL);
|
|
FuFmapArea area = {
|
|
.offset = GUINT32_TO_LE(fu_firmware_get_offset(firmware) + offset),
|
|
.size = GUINT32_TO_LE(g_bytes_get_size(fw)),
|
|
.name = {""},
|
|
.flags = 0x0,
|
|
};
|
|
if (id != NULL)
|
|
strncpy((gchar *)area.name, id, sizeof(area.name) - 1);
|
|
g_byte_array_append(buf, (const guint8 *)&area, sizeof(area));
|
|
offset += g_bytes_get_size(fw);
|
|
}
|
|
|
|
/* add the images */
|
|
for (guint i = 0; i < images->len; i++) {
|
|
FuFirmware *img = g_ptr_array_index(images, i);
|
|
g_autoptr(GBytes) fw = fu_firmware_get_bytes_with_patches(img, NULL);
|
|
fu_byte_array_append_bytes(buf, fw);
|
|
}
|
|
|
|
/* success */
|
|
return g_byte_array_free_to_bytes(g_steal_pointer(&buf));
|
|
}
|
|
|
|
static void
|
|
fu_fmap_firmware_init(FuFmapFirmware *self)
|
|
{
|
|
}
|
|
|
|
static void
|
|
fu_fmap_firmware_class_init(FuFmapFirmwareClass *klass)
|
|
{
|
|
FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass);
|
|
klass_firmware->check_magic = fu_fmap_firmware_check_magic;
|
|
klass_firmware->parse = fu_fmap_firmware_parse;
|
|
klass_firmware->write = fu_fmap_firmware_write;
|
|
}
|
|
|
|
/**
|
|
* fu_fmap_firmware_new
|
|
*
|
|
* Creates a new #FuFirmware of sub type fmap
|
|
*
|
|
* Since: 1.5.0
|
|
**/
|
|
FuFirmware *
|
|
fu_fmap_firmware_new(void)
|
|
{
|
|
return FU_FIRMWARE(g_object_new(FU_TYPE_FMAP_FIRMWARE, NULL));
|
|
}
|