fwupd/plugins/genesys/fu-genesys-usbhub-firmware.c
Richard Hughes 0c51630991 Check firmware magic in a more standard way
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.
2022-07-14 14:48:15 +01:00

460 lines
13 KiB
C

/*
* Copyright (C) 2021 Gaël PORTAY <gael.portay@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <fwupdplugin.h>
#include "fu-genesys-common.h"
#include "fu-genesys-usbhub-firmware.h"
struct _FuGenesysUsbhubFirmware {
FuFirmwareClass parent_instance;
FuGenesysStaticToolString static_ts;
FuGenesysChip chip;
};
G_DEFINE_TYPE(FuGenesysUsbhubFirmware, fu_genesys_usbhub_firmware, FU_TYPE_FIRMWARE)
static gboolean
fu_genesys_usbhub_firmware_get_chip(FuGenesysUsbhubFirmware *self,
const guint8 *buf,
gsize bufsz,
GError **error)
{
guint8 project_ic_type[6];
if (!fu_memcpy_safe(project_ic_type,
sizeof(project_ic_type),
0, /* dst */
buf,
bufsz,
GENESYS_USBHUB_STATIC_TOOL_STRING_OFFSET_GL3523 + 8, /* src */
sizeof(project_ic_type),
error))
return FALSE;
if (memcmp(project_ic_type, "3521", 4) == 0) {
self->chip.model = ISP_MODEL_HUB_GL3521;
self->chip.revision = 10 * (project_ic_type[4] - '0') + (project_ic_type[5] - '0');
return TRUE;
}
if (memcmp(project_ic_type, "3523", 4) == 0) {
self->chip.model = ISP_MODEL_HUB_GL3523;
self->chip.revision = 10 * (project_ic_type[4] - '0') + (project_ic_type[5] - '0');
return TRUE;
}
if (!fu_memcpy_safe(project_ic_type,
sizeof(project_ic_type),
0, /* dst */
buf,
bufsz,
GENESYS_USBHUB_STATIC_TOOL_STRING_OFFSET_GL3590 + 8, /* src */
sizeof(project_ic_type),
error))
return FALSE;
if (memcmp(project_ic_type, "3590", 4) == 0) {
self->chip.model = ISP_MODEL_HUB_GL3590;
self->chip.revision = 10 * (project_ic_type[4] - '0') + (project_ic_type[5] - '0');
return TRUE;
}
g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Unsupported IC");
return FALSE;
}
static gboolean
fu_genesys_usbhub_firmware_verify(const guint8 *buf, gsize bufsz, guint16 code_size, GError **error)
{
guint16 fw_checksum, checksum;
/* check code-size */
if (code_size < sizeof(checksum)) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"code-size is too small: %u bytes",
code_size);
return FALSE;
}
/* get checksum */
if (!fu_memread_uint16_safe(buf,
bufsz,
code_size - sizeof(checksum),
&fw_checksum,
G_BIG_ENDIAN,
error))
return FALSE;
/* calculate checksum */
checksum = fu_sum16(buf, code_size - sizeof(checksum));
if (checksum != fw_checksum) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"checksum mismatch, got 0x%04x, expected 0x%04x",
checksum,
fw_checksum);
return FALSE;
}
return TRUE;
}
static gboolean
fu_genesys_usbhub_firmware_check_magic(FuFirmware *firmware,
GBytes *fw,
gsize offset,
GError **error)
{
guint8 magic[4] = {0x0};
if (!fu_memcpy_safe(magic,
sizeof(magic),
0, /* dst */
g_bytes_get_data(fw, NULL),
g_bytes_get_size(fw),
offset + GENESYS_USBHUB_FW_SIG_OFFSET,
sizeof(magic),
error)) {
g_prefix_error(error, "failed to read magic: ");
return FALSE;
}
if (memcmp(magic, GENESYS_USBHUB_FW_SIG_TEXT_HUB, sizeof(magic)) != 0 &&
memcmp(magic, GENESYS_USBHUB_FW_SIG_TEXT_HUB_SIGN, sizeof(magic)) != 0) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"signature not supported");
return FALSE;
}
/* success */
return TRUE;
}
static gboolean
fu_genesys_usbhub_firmware_parse(FuFirmware *firmware,
GBytes *fw,
gsize offset,
FwupdInstallFlags flags,
GError **error)
{
FuGenesysUsbhubFirmware *self = FU_GENESYS_USBHUB_FIRMWARE(firmware);
gsize bufsz = 0;
const guint8 *buf = g_bytes_get_data(fw, &bufsz);
guint32 code_size = 0;
guint32 static_ts_offset = 0;
guint16 version_raw = 0;
g_autofree gchar *version = NULL;
/* get chip */
if (!fu_genesys_usbhub_firmware_get_chip(self, buf, bufsz, error))
return FALSE;
switch (self->chip.model) {
case ISP_MODEL_HUB_GL3521:
static_ts_offset = GENESYS_USBHUB_STATIC_TOOL_STRING_OFFSET_GL3521;
break;
case ISP_MODEL_HUB_GL3523:
static_ts_offset = GENESYS_USBHUB_STATIC_TOOL_STRING_OFFSET_GL3523;
break;
case ISP_MODEL_HUB_GL3590:
static_ts_offset = GENESYS_USBHUB_STATIC_TOOL_STRING_OFFSET_GL3590;
break;
default:
break;
}
/* get static tool string */
if (!fu_memcpy_safe((guint8 *)&self->static_ts,
sizeof(self->static_ts),
0, /* dst */
buf,
bufsz,
static_ts_offset, /* src */
sizeof(self->static_ts),
error))
return FALSE;
/* deduce code size */
switch (self->chip.model) {
case ISP_MODEL_HUB_GL3521:
code_size = 0x5000;
break;
case ISP_MODEL_HUB_GL3523: {
code_size = 0x6000;
if (self->chip.revision == 50) {
guint8 kbs = 0;
if (!fu_memread_uint8_safe(buf,
bufsz,
GENESYS_USBHUB_CODE_SIZE_OFFSET,
&kbs,
error))
return FALSE;
code_size = 1024 * kbs;
}
break;
}
case ISP_MODEL_HUB_GL3590: {
guint8 kbs = 0;
if (!fu_memread_uint8_safe(buf,
bufsz,
GENESYS_USBHUB_CODE_SIZE_OFFSET,
&kbs,
error))
return FALSE;
code_size = 1024 * kbs;
break;
}
default:
break;
}
fu_firmware_set_size(firmware, code_size);
/* calculate checksum */
if ((flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0)
if (!fu_genesys_usbhub_firmware_verify(buf, bufsz, code_size, error))
return FALSE;
/* get firmware version */
if (!fu_memread_uint16_safe(buf,
bufsz,
GENESYS_USBHUB_VERSION_OFFSET,
&version_raw,
G_LITTLE_ENDIAN,
error))
return FALSE;
fu_firmware_set_version_raw(firmware, version_raw);
version =
g_strdup_printf("%02x.%02x", (version_raw & 0xFF00U) >> 8, (version_raw & 0x00FFU));
fu_firmware_set_version(firmware, version);
/* success */
return TRUE;
}
static GBytes *
fu_genesys_usbhub_firmware_write(FuFirmware *firmware, GError **error)
{
FuGenesysUsbhubFirmware *self = FU_GENESYS_USBHUB_FIRMWARE(firmware);
g_autoptr(GByteArray) buf = g_byte_array_new();
guint16 code_size = 0x6000;
guint16 checksum;
/* fixed size */
fu_byte_array_set_size(buf, code_size, 0x00);
/* signature */
if (!fu_memcpy_safe(buf->data,
buf->len,
GENESYS_USBHUB_FW_SIG_OFFSET, /* dst */
(const guint8 *)GENESYS_USBHUB_FW_SIG_TEXT_HUB,
GENESYS_USBHUB_FW_SIG_LEN,
0x0, /* src */
GENESYS_USBHUB_FW_SIG_LEN,
error))
return NULL;
/* static tool string */
if (!fu_memcpy_safe(buf->data,
buf->len,
GENESYS_USBHUB_STATIC_TOOL_STRING_OFFSET_GL3523, /* dst */
(const guint8 *)&self->static_ts,
sizeof(self->static_ts),
0x0, /* src */
sizeof(self->static_ts),
error))
return NULL;
/* checksum */
checksum = fu_sum16(buf->data, code_size - sizeof(checksum));
if (!fu_memwrite_uint16_safe(buf->data,
buf->len,
code_size - sizeof(guint16),
checksum,
G_BIG_ENDIAN,
error))
return NULL;
/* version */
if (!fu_memwrite_uint16_safe(buf->data,
buf->len,
GENESYS_USBHUB_VERSION_OFFSET,
0x1234, // TODO: parse from firmware version string
G_BIG_ENDIAN,
error))
return NULL;
/* success */
return g_byte_array_free_to_bytes(g_steal_pointer(&buf));
}
static gchar *
fu_genesys_usbhub_firmware_get_project_ic_type_string(const gchar *tmp)
{
return g_strdup_printf("GL%c%c%c%c-%c%c", tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5]);
}
static void
fu_genesys_usbhub_firmware_export(FuFirmware *firmware,
FuFirmwareExportFlags flags,
XbBuilderNode *bn)
{
FuGenesysUsbhubFirmware *self = FU_GENESYS_USBHUB_FIRMWARE(firmware);
g_autofree gchar *tool_string_version = NULL;
g_autofree gchar *mask_project_code = NULL;
g_autofree gchar *mask_project_hardware = NULL;
g_autofree gchar *mask_project_firmware = NULL;
g_autofree gchar *mask_project_ic_type = NULL;
g_autofree gchar *running_project_code = NULL;
g_autofree gchar *running_project_hardware = NULL;
g_autofree gchar *running_project_firmware = NULL;
g_autofree gchar *running_project_ic_type = NULL;
tool_string_version = fu_strsafe((const gchar *)&self->static_ts.tool_string_version,
sizeof(self->static_ts.tool_string_version));
fu_xmlb_builder_insert_kv(bn, "tool_string_version", tool_string_version);
mask_project_code = fu_strsafe((const gchar *)&self->static_ts.mask_project_code,
sizeof(self->static_ts.mask_project_code));
fu_xmlb_builder_insert_kv(bn, "mask_project_code", mask_project_code);
mask_project_hardware = fu_strsafe((const gchar *)&self->static_ts.mask_project_hardware,
sizeof(self->static_ts.mask_project_hardware));
if (mask_project_hardware != NULL)
mask_project_hardware[0] += 0x10; /* '1' -> 'A'... */
fu_xmlb_builder_insert_kv(bn, "mask_project_hardware", mask_project_hardware);
mask_project_firmware = fu_strsafe((const gchar *)&self->static_ts.mask_project_firmware,
sizeof(self->static_ts.mask_project_firmware));
fu_xmlb_builder_insert_kv(bn, "mask_project_firmware", mask_project_firmware);
mask_project_ic_type = fu_genesys_usbhub_firmware_get_project_ic_type_string(
(const gchar *)self->static_ts.mask_project_ic_type);
fu_xmlb_builder_insert_kv(bn, "mask_project_ic_type", mask_project_ic_type);
running_project_code = fu_strsafe((const gchar *)&self->static_ts.running_project_code,
sizeof(self->static_ts.running_project_code));
fu_xmlb_builder_insert_kv(bn, "running_project_code", running_project_code);
running_project_hardware =
fu_strsafe((const gchar *)&self->static_ts.running_project_hardware,
sizeof(self->static_ts.running_project_hardware));
if (running_project_hardware != NULL)
running_project_hardware[0] += 0x10; /* '1' -> 'A'... */
fu_xmlb_builder_insert_kv(bn, "running_project_hardware", running_project_hardware);
running_project_firmware =
fu_strsafe((const gchar *)&self->static_ts.running_project_firmware,
sizeof(self->static_ts.running_project_firmware));
fu_xmlb_builder_insert_kv(bn, "running_project_firmware", running_project_firmware);
running_project_ic_type = fu_genesys_usbhub_firmware_get_project_ic_type_string(
(const gchar *)self->static_ts.running_project_ic_type);
fu_xmlb_builder_insert_kv(bn, "running_project_ic_type", running_project_ic_type);
}
static gboolean
fu_genesys_usbhub_firmware_build(FuFirmware *firmware, XbNode *n, GError **error)
{
FuGenesysUsbhubFirmware *self = FU_GENESYS_USBHUB_FIRMWARE(firmware);
const gchar *tmp;
guint64 tmp64;
/* optional properties */
tmp64 = xb_node_query_text_as_uint(n, "tool_string_version", NULL);
if (tmp64 != G_MAXUINT64) {
if (tmp64 > G_MAXUINT8) {
g_set_error_literal(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"invalid tool_string_version");
return FALSE;
}
self->static_ts.tool_string_version = tmp64;
}
/* mask_project_code */
tmp = xb_node_query_text(n, "mask_project_code", NULL);
if (tmp != NULL) {
gsize len = strlen(tmp);
if (len != 4) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"invalid mask_project_code %s, got 0x%x length",
tmp,
(guint)len);
return FALSE;
}
if (!fu_memcpy_safe((guint8 *)&self->static_ts.mask_project_code,
sizeof(self->static_ts.mask_project_code),
0x0, /* dst */
(const guint8 *)tmp,
len,
0x0, /* src */
len,
error))
return FALSE;
}
/* mask_project_ic_type */
tmp = xb_node_query_text(n, "mask_project_ic_type", NULL);
if (tmp != NULL) {
gsize len = strlen(tmp);
if (len != 6) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"invalid mask_project_ic_type %s, got 0x%x length",
tmp,
(guint)len);
return FALSE;
}
if (!fu_memcpy_safe((guint8 *)&self->static_ts.mask_project_ic_type,
sizeof(self->static_ts.mask_project_ic_type),
0x0, /* dst */
(const guint8 *)tmp,
len,
0x0, /* src */
len,
error))
return FALSE;
}
/* success */
return TRUE;
}
static void
fu_genesys_usbhub_firmware_init(FuGenesysUsbhubFirmware *self)
{
fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM);
}
static void
fu_genesys_usbhub_firmware_class_init(FuGenesysUsbhubFirmwareClass *klass)
{
FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass);
klass_firmware->check_magic = fu_genesys_usbhub_firmware_check_magic;
klass_firmware->parse = fu_genesys_usbhub_firmware_parse;
klass_firmware->export = fu_genesys_usbhub_firmware_export;
klass_firmware->build = fu_genesys_usbhub_firmware_build;
klass_firmware->write = fu_genesys_usbhub_firmware_write;
}
FuFirmware *
fu_genesys_usbhub_firmware_new(void)
{
return FU_FIRMWARE(g_object_new(FU_TYPE_GENESYS_USBHUB_FIRMWARE, NULL));
}