mirror of
https://git.proxmox.com/git/fwupd
synced 2025-06-05 08:09:03 +00:00
369 lines
9.5 KiB
C
369 lines
9.5 KiB
C
/*
|
|
* Copyright (C) 2022 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-common.h"
|
|
#include "fu-crc.h"
|
|
#include "fu-dump.h"
|
|
#include "fu-fdt-image.h"
|
|
#include "fu-fit-firmware.h"
|
|
#include "fu-mem.h"
|
|
|
|
/**
|
|
* FuFitFirmware:
|
|
*
|
|
* A Flat Image Tree.
|
|
*
|
|
* Documented:
|
|
* https://github.com/u-boot/u-boot/blob/master/doc/uImage.FIT/source_file_format.txt
|
|
*
|
|
* See also: [class@FuFdtFirmware]
|
|
*/
|
|
|
|
G_DEFINE_TYPE(FuFitFirmware, fu_fit_firmware, FU_TYPE_FDT_FIRMWARE)
|
|
|
|
static FuFdtImage *
|
|
fu_fit_firmware_get_image_root(FuFitFirmware *self)
|
|
{
|
|
FuFirmware *img = fu_firmware_get_image_by_id(FU_FIRMWARE(self), NULL, NULL);
|
|
if (img != NULL)
|
|
return FU_FDT_IMAGE(img);
|
|
img = fu_fdt_image_new();
|
|
fu_fdt_image_set_attr_uint32(FU_FDT_IMAGE(img), FU_FIT_FIRMWARE_ATTR_TIMESTAMP, 0x0);
|
|
fu_fdt_image_set_attr_str(FU_FDT_IMAGE(img), "description", "Firmware image");
|
|
fu_fdt_image_set_attr_str(FU_FDT_IMAGE(img), "creator", "fwupd");
|
|
fu_firmware_add_image(FU_FIRMWARE(self), img);
|
|
return FU_FDT_IMAGE(img);
|
|
}
|
|
|
|
/**
|
|
* fu_fit_firmware_get_timestamp:
|
|
* @self: a #FuFitFirmware
|
|
*
|
|
* Gets the creation timestamp.
|
|
*
|
|
* Returns: integer
|
|
*
|
|
* Since: 1.8.2
|
|
**/
|
|
guint32
|
|
fu_fit_firmware_get_timestamp(FuFitFirmware *self)
|
|
{
|
|
guint32 tmp = 0;
|
|
g_autoptr(FuFdtImage) img_root = fu_fit_firmware_get_image_root(self);
|
|
|
|
g_return_val_if_fail(FU_IS_FIT_FIRMWARE(self), 0x0);
|
|
|
|
/* this has to exist */
|
|
(void)fu_fdt_image_get_attr_u32(img_root, FU_FIT_FIRMWARE_ATTR_TIMESTAMP, &tmp, NULL);
|
|
return tmp;
|
|
}
|
|
|
|
/**
|
|
* fu_fit_firmware_set_timestamp:
|
|
* @self: a #FuFitFirmware
|
|
* @timestamp: integer value
|
|
*
|
|
* Sets the creation timestamp.
|
|
*
|
|
* Since: 1.8.2
|
|
**/
|
|
void
|
|
fu_fit_firmware_set_timestamp(FuFitFirmware *self, guint32 timestamp)
|
|
{
|
|
g_autoptr(FuFdtImage) img_root = fu_fit_firmware_get_image_root(self);
|
|
g_return_if_fail(FU_IS_FIT_FIRMWARE(self));
|
|
fu_fdt_image_set_attr_uint32(img_root, FU_FIT_FIRMWARE_ATTR_TIMESTAMP, timestamp);
|
|
}
|
|
|
|
static gboolean
|
|
fu_fit_firmware_verify_crc32(FuFirmware *firmware,
|
|
FuFirmware *img,
|
|
FuFirmware *img_hash,
|
|
GBytes *blob,
|
|
GError **error)
|
|
{
|
|
guint32 value = 0;
|
|
guint32 value_calc;
|
|
|
|
/* get value and verify */
|
|
if (!fu_fdt_image_get_attr_u32(FU_FDT_IMAGE(img_hash),
|
|
FU_FIT_FIRMWARE_ATTR_VALUE,
|
|
&value,
|
|
error))
|
|
return FALSE;
|
|
value_calc = fu_crc32(g_bytes_get_data(blob, NULL), g_bytes_get_size(blob));
|
|
if (value_calc != value) {
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"%s CRC did not match, got 0x%x, expected 0x%x",
|
|
fu_firmware_get_id(img),
|
|
value,
|
|
value_calc);
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
fu_firmware_add_flag(firmware, FU_FIRMWARE_FLAG_HAS_CHECKSUM);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_fit_firmware_verify_checksum(FuFirmware *firmware,
|
|
FuFirmware *img,
|
|
FuFirmware *img_hash,
|
|
GChecksumType checksum_type,
|
|
GBytes *blob,
|
|
GError **error)
|
|
{
|
|
gsize digest_len = g_checksum_type_get_length(checksum_type);
|
|
g_autofree guint8 *buf = g_malloc0(digest_len);
|
|
g_autoptr(GBytes) value = NULL;
|
|
g_autoptr(GBytes) value_calc = NULL;
|
|
g_autoptr(GChecksum) checksum = g_checksum_new(checksum_type);
|
|
|
|
/* get value and verify */
|
|
value = fu_fdt_image_get_attr(FU_FDT_IMAGE(img_hash), FU_FIT_FIRMWARE_ATTR_VALUE, error);
|
|
if (value == NULL)
|
|
return FALSE;
|
|
if (g_bytes_get_size(value) != digest_len) {
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"%s invalid hash value size, got 0x%x, expected 0x%x",
|
|
fu_firmware_get_id(img),
|
|
(guint)g_bytes_get_size(value),
|
|
(guint)digest_len);
|
|
return FALSE;
|
|
}
|
|
g_checksum_update(checksum,
|
|
(const guchar *)g_bytes_get_data(value, NULL),
|
|
g_bytes_get_size(value));
|
|
g_checksum_get_digest(checksum, buf, &digest_len);
|
|
value_calc = g_bytes_new(buf, digest_len);
|
|
if (!fu_bytes_compare(value, value_calc, error))
|
|
return FALSE;
|
|
|
|
/* success */
|
|
fu_firmware_add_flag(firmware, FU_FIRMWARE_FLAG_HAS_CHECKSUM);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_fit_firmware_verify_hash(FuFirmware *firmware,
|
|
FuFirmware *img,
|
|
FuFirmware *img_hash,
|
|
GBytes *blob,
|
|
GError **error)
|
|
{
|
|
g_autofree gchar *algo = NULL;
|
|
|
|
/* what is this */
|
|
if (!fu_fdt_image_get_attr_str(FU_FDT_IMAGE(img_hash),
|
|
FU_FIT_FIRMWARE_ATTR_ALGO,
|
|
&algo,
|
|
error)) {
|
|
g_prefix_error(error, "cannot get algo for %s: ", fu_firmware_get_id(img));
|
|
return FALSE;
|
|
}
|
|
if (g_strcmp0(algo, "crc32") == 0)
|
|
return fu_fit_firmware_verify_crc32(firmware, img, img_hash, blob, error);
|
|
if (g_strcmp0(algo, "md5") == 0) {
|
|
return fu_fit_firmware_verify_checksum(firmware,
|
|
img,
|
|
img_hash,
|
|
G_CHECKSUM_MD5,
|
|
blob,
|
|
error);
|
|
}
|
|
if (g_strcmp0(algo, "sha1") == 0) {
|
|
return fu_fit_firmware_verify_checksum(firmware,
|
|
img,
|
|
img_hash,
|
|
G_CHECKSUM_SHA1,
|
|
blob,
|
|
error);
|
|
}
|
|
if (g_strcmp0(algo, "sha256") == 0) {
|
|
return fu_fit_firmware_verify_checksum(firmware,
|
|
img,
|
|
img_hash,
|
|
G_CHECKSUM_SHA256,
|
|
blob,
|
|
error);
|
|
}
|
|
|
|
/* ignore any hashes we do not support: success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_fit_firmware_verify_image(FuFirmware *firmware,
|
|
GBytes *fw,
|
|
FuFirmware *img,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GBytes) blob = NULL;
|
|
|
|
/* sanity check */
|
|
if (!fu_fdt_image_get_attr_str(FU_FDT_IMAGE(img), "type", NULL, error))
|
|
return FALSE;
|
|
if (!fu_fdt_image_get_attr_str(FU_FDT_IMAGE(img), "description", NULL, error))
|
|
return FALSE;
|
|
|
|
/* if has data data */
|
|
blob = fu_fdt_image_get_attr(FU_FDT_IMAGE(img), FU_FIT_FIRMWARE_ATTR_DATA, NULL);
|
|
if (blob == NULL) {
|
|
guint32 data_size = 0x0;
|
|
guint32 data_offset = 0x0;
|
|
|
|
/* extra data outside of FIT image */
|
|
if (!fu_fdt_image_get_attr_u32(FU_FDT_IMAGE(img),
|
|
FU_FIT_FIRMWARE_ATTR_DATA_OFFSET,
|
|
&data_offset,
|
|
error))
|
|
return FALSE;
|
|
if (!fu_fdt_image_get_attr_u32(FU_FDT_IMAGE(img),
|
|
FU_FIT_FIRMWARE_ATTR_DATA_SIZE,
|
|
&data_size,
|
|
error))
|
|
return FALSE;
|
|
blob = fu_bytes_new_offset(fw, data_offset, data_size, error);
|
|
if (blob == NULL)
|
|
return FALSE;
|
|
}
|
|
if (g_getenv("FU_FDT_FIRMWARE_VERBOSE") != NULL)
|
|
fu_dump_bytes(G_LOG_DOMAIN, "data", blob);
|
|
|
|
/* verify any hashes we recoginise */
|
|
if ((flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) {
|
|
g_autoptr(GPtrArray) img_hashes = fu_firmware_get_images(img);
|
|
for (guint i = 0; i < img_hashes->len; i++) {
|
|
FuFirmware *img_hash = g_ptr_array_index(img_hashes, i);
|
|
if (fu_firmware_get_id(img_hash) == NULL) {
|
|
g_set_error_literal(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"no ID for image hash");
|
|
return FALSE;
|
|
}
|
|
if (g_str_has_prefix(fu_firmware_get_id(img_hash), "hash")) {
|
|
if (!fu_fit_firmware_verify_hash(firmware,
|
|
img,
|
|
img_hash,
|
|
blob,
|
|
error))
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_fit_firmware_verify_configuration(FuFirmware *firmware,
|
|
FuFirmware *img,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
/* sanity check */
|
|
if (!fu_fdt_image_get_attr_strlist(FU_FDT_IMAGE(img),
|
|
FU_FIT_FIRMWARE_ATTR_COMPATIBLE,
|
|
NULL,
|
|
error))
|
|
return FALSE;
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_fit_firmware_parse(FuFirmware *firmware,
|
|
GBytes *fw,
|
|
gsize offset,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
g_autoptr(FuFirmware) img_cfgs = NULL;
|
|
g_autoptr(FuFirmware) img_images = NULL;
|
|
g_autoptr(FuFirmware) img_root = NULL;
|
|
g_autoptr(GPtrArray) img_images_array = NULL;
|
|
g_autoptr(GPtrArray) img_cfgs_array = NULL;
|
|
|
|
/* FuFdtFirmware->parse */
|
|
if (!FU_FIRMWARE_CLASS(fu_fit_firmware_parent_class)
|
|
->parse(firmware, fw, offset, flags, error))
|
|
return FALSE;
|
|
|
|
/* sanity check */
|
|
img_root = fu_firmware_get_image_by_id(firmware, NULL, error);
|
|
if (img_root == NULL)
|
|
return FALSE;
|
|
if (!fu_fdt_image_get_attr_u32(FU_FDT_IMAGE(img_root),
|
|
FU_FIT_FIRMWARE_ATTR_TIMESTAMP,
|
|
NULL,
|
|
error))
|
|
return FALSE;
|
|
|
|
/* check the checksums of each image */
|
|
img_images = fu_firmware_get_image_by_id(img_root, FU_FIT_FIRMWARE_ID_IMAGES, error);
|
|
if (img_images == NULL)
|
|
return FALSE;
|
|
img_images_array = fu_firmware_get_images(img_images);
|
|
for (guint i = 0; i < img_images_array->len; i++) {
|
|
FuFirmware *img = g_ptr_array_index(img_images_array, i);
|
|
if (!fu_fit_firmware_verify_image(firmware, fw, img, flags, error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* check the setup of each configuration */
|
|
img_cfgs = fu_firmware_get_image_by_id(img_root, FU_FIT_FIRMWARE_ID_CONFIGURATIONS, error);
|
|
if (img_cfgs == NULL)
|
|
return FALSE;
|
|
img_cfgs_array = fu_firmware_get_images(img_cfgs);
|
|
for (guint i = 0; i < img_cfgs_array->len; i++) {
|
|
FuFirmware *img = g_ptr_array_index(img_cfgs_array, i);
|
|
if (!fu_fit_firmware_verify_configuration(firmware, img, flags, error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
fu_fit_firmware_init(FuFitFirmware *self)
|
|
{
|
|
}
|
|
|
|
static void
|
|
fu_fit_firmware_class_init(FuFitFirmwareClass *klass)
|
|
{
|
|
FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass);
|
|
klass_firmware->parse = fu_fit_firmware_parse;
|
|
}
|
|
|
|
/**
|
|
* fu_fit_firmware_new:
|
|
*
|
|
* Creates a new #FuFirmware of sub type FIT
|
|
*
|
|
* Since: 1.8.2
|
|
**/
|
|
FuFirmware *
|
|
fu_fit_firmware_new(void)
|
|
{
|
|
return FU_FIRMWARE(g_object_new(FU_TYPE_FIT_FIRMWARE, NULL));
|
|
}
|