mirror of
				https://git.proxmox.com/git/fwupd
				synced 2025-10-31 14:14:46 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			348 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			348 lines
		
	
	
		
			9.1 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-byte-array.h"
 | |
| #include "fu-bytes.h"
 | |
| #include "fu-common.h"
 | |
| #include "fu-coswid-firmware.h"
 | |
| #include "fu-mem.h"
 | |
| #include "fu-string.h"
 | |
| #include "fu-uswid-firmware.h"
 | |
| 
 | |
| /**
 | |
|  * FuUswidFirmware:
 | |
|  *
 | |
|  * A uSWID header with multiple optionally-compressed coSWID CBOR sections.
 | |
|  *
 | |
|  * See also: [class@FuCoswidFirmware]
 | |
|  */
 | |
| 
 | |
| typedef struct {
 | |
| 	guint8 hdrver;
 | |
| 	gboolean compressed;
 | |
| } FuUswidFirmwarePrivate;
 | |
| 
 | |
| G_DEFINE_TYPE_WITH_PRIVATE(FuUswidFirmware, fu_uswid_firmware, FU_TYPE_FIRMWARE)
 | |
| #define GET_PRIVATE(o) (fu_uswid_firmware_get_instance_private(o))
 | |
| 
 | |
| #define USWID_HEADER_VERSION_V1 1
 | |
| #define USWID_HEADER_SIZE_V1	23
 | |
| #define USWID_HEADER_SIZE_V2	24
 | |
| 
 | |
| #define USWID_HEADER_FLAG_COMPRESSED 0x01
 | |
| 
 | |
| typedef struct __attribute__((packed)) {
 | |
| 	guint8 magic[16];
 | |
| 	guint8 hdrver;
 | |
| 	guint16 hdrsz;
 | |
| 	guint32 payloadsz;
 | |
| 	guint8 flags;
 | |
| } FuUswidFirmwareHdr;
 | |
| 
 | |
| const guint8 USWID_HEADER_MAGIC[] = {0x53,
 | |
| 				     0x42,
 | |
| 				     0x4F,
 | |
| 				     0x4D,
 | |
| 				     0xD6,
 | |
| 				     0xBA,
 | |
| 				     0x2E,
 | |
| 				     0xAC,
 | |
| 				     0xA3,
 | |
| 				     0xE6,
 | |
| 				     0x7A,
 | |
| 				     0x52,
 | |
| 				     0xAA,
 | |
| 				     0xEE,
 | |
| 				     0x3B,
 | |
| 				     0xAF};
 | |
| 
 | |
| static void
 | |
| fu_uswid_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn)
 | |
| {
 | |
| 	FuUswidFirmware *self = FU_USWID_FIRMWARE(firmware);
 | |
| 	FuUswidFirmwarePrivate *priv = GET_PRIVATE(self);
 | |
| 	fu_xmlb_builder_insert_kx(bn, "hdrver", priv->hdrver);
 | |
| 	fu_xmlb_builder_insert_kb(bn, "compressed", priv->compressed);
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| fu_uswid_firmware_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error)
 | |
| {
 | |
| 	guint8 magic[16] = {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, USWID_HEADER_MAGIC, 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_uswid_firmware_parse(FuFirmware *firmware,
 | |
| 			GBytes *fw,
 | |
| 			gsize offset,
 | |
| 			FwupdInstallFlags flags,
 | |
| 			GError **error)
 | |
| {
 | |
| 	FuUswidFirmware *self = FU_USWID_FIRMWARE(firmware);
 | |
| 	FuUswidFirmwarePrivate *priv = GET_PRIVATE(self);
 | |
| 	guint16 hdrsz = 0;
 | |
| 	guint32 payloadsz = 0;
 | |
| 	guint8 uswid_flags = 0;
 | |
| 	gsize bufsz;
 | |
| 	const guint8 *buf = g_bytes_get_data(fw, &bufsz);
 | |
| 	g_autoptr(GBytes) payload = NULL;
 | |
| 
 | |
| 	/* hdrver */
 | |
| 	if (!fu_memread_uint8_safe(buf,
 | |
| 				   bufsz,
 | |
| 				   offset + G_STRUCT_OFFSET(FuUswidFirmwareHdr, hdrver),
 | |
| 				   &priv->hdrver,
 | |
| 				   error))
 | |
| 		return FALSE;
 | |
| 	if (priv->hdrver < USWID_HEADER_VERSION_V1) {
 | |
| 		g_set_error_literal(error,
 | |
| 				    FWUPD_ERROR,
 | |
| 				    FWUPD_ERROR_NOT_SUPPORTED,
 | |
| 				    "header version was unsupported");
 | |
| 		return FALSE;
 | |
| 	}
 | |
| 
 | |
| 	/* hdrsz */
 | |
| 	if (!fu_memread_uint16_safe(buf,
 | |
| 				    bufsz,
 | |
| 				    offset + G_STRUCT_OFFSET(FuUswidFirmwareHdr, hdrsz),
 | |
| 				    &hdrsz,
 | |
| 				    G_LITTLE_ENDIAN,
 | |
| 				    error))
 | |
| 		return FALSE;
 | |
| 	if (hdrsz < USWID_HEADER_SIZE_V1) {
 | |
| 		g_set_error_literal(error,
 | |
| 				    FWUPD_ERROR,
 | |
| 				    FWUPD_ERROR_NOT_SUPPORTED,
 | |
| 				    "header size is invalid");
 | |
| 		return FALSE;
 | |
| 	}
 | |
| 
 | |
| 	/* payloadsz */
 | |
| 	if (!fu_memread_uint32_safe(buf,
 | |
| 				    bufsz,
 | |
| 				    offset + G_STRUCT_OFFSET(FuUswidFirmwareHdr, payloadsz),
 | |
| 				    &payloadsz,
 | |
| 				    G_LITTLE_ENDIAN,
 | |
| 				    error))
 | |
| 		return FALSE;
 | |
| 	if (payloadsz == 0x0) {
 | |
| 		g_set_error_literal(error,
 | |
| 				    FWUPD_ERROR,
 | |
| 				    FWUPD_ERROR_NOT_SUPPORTED,
 | |
| 				    "payload size is invalid");
 | |
| 		return FALSE;
 | |
| 	}
 | |
| 	fu_firmware_set_size(firmware, hdrsz + payloadsz);
 | |
| 
 | |
| 	/* flags */
 | |
| 	if (priv->hdrver >= 0x02) {
 | |
| 		if (!fu_memread_uint8_safe(buf,
 | |
| 					   bufsz,
 | |
| 					   offset + G_STRUCT_OFFSET(FuUswidFirmwareHdr, flags),
 | |
| 					   &uswid_flags,
 | |
| 					   error))
 | |
| 			return FALSE;
 | |
| 		priv->compressed = (uswid_flags & USWID_HEADER_FLAG_COMPRESSED) > 0;
 | |
| 	}
 | |
| 
 | |
| 	/* zlib stream */
 | |
| 	if (priv->compressed) {
 | |
| 		g_autoptr(GBytes) payload_tmp = NULL;
 | |
| 		g_autoptr(GConverter) conv = NULL;
 | |
| 		g_autoptr(GInputStream) istream1 = NULL;
 | |
| 		g_autoptr(GInputStream) istream2 = NULL;
 | |
| 
 | |
| 		payload_tmp = fu_bytes_new_offset(fw, offset + hdrsz, payloadsz, error);
 | |
| 		if (payload_tmp == NULL)
 | |
| 			return FALSE;
 | |
| 		istream1 = g_memory_input_stream_new_from_bytes(payload_tmp);
 | |
| 		conv = G_CONVERTER(g_zlib_decompressor_new(G_ZLIB_COMPRESSOR_FORMAT_ZLIB));
 | |
| 		istream2 = g_converter_input_stream_new(istream1, conv);
 | |
| 		payload = fu_bytes_get_contents_stream(istream2, G_MAXSIZE, error);
 | |
| 		if (payload == NULL)
 | |
| 			return FALSE;
 | |
| 		payloadsz = g_bytes_get_size(payload);
 | |
| 	} else {
 | |
| 		payload = fu_bytes_new_offset(fw, offset + hdrsz, payloadsz, error);
 | |
| 		if (payload == NULL)
 | |
| 			return FALSE;
 | |
| 	}
 | |
| 
 | |
| 	/* payload */
 | |
| 	for (gsize offset_tmp = 0; offset_tmp < payloadsz;) {
 | |
| 		g_autoptr(FuFirmware) firmware_coswid = fu_coswid_firmware_new();
 | |
| 		g_autoptr(GBytes) fw2 = NULL;
 | |
| 
 | |
| 		/* CBOR parse */
 | |
| 		fw2 = fu_bytes_new_offset(payload, offset_tmp, payloadsz - offset_tmp, error);
 | |
| 		if (fw2 == NULL)
 | |
| 			return FALSE;
 | |
| 		if (!fu_firmware_parse(firmware_coswid,
 | |
| 				       fw2,
 | |
| 				       flags | FWUPD_INSTALL_FLAG_NO_SEARCH,
 | |
| 				       error))
 | |
| 			return FALSE;
 | |
| 		fu_firmware_add_image(firmware, firmware_coswid);
 | |
| 		if (fu_firmware_get_size(firmware_coswid) == 0) {
 | |
| 			g_set_error_literal(error,
 | |
| 					    FWUPD_ERROR,
 | |
| 					    FWUPD_ERROR_NOT_SUPPORTED,
 | |
| 					    "coSWID read no bytes");
 | |
| 			return FALSE;
 | |
| 		}
 | |
| 		offset_tmp += fu_firmware_get_size(firmware_coswid);
 | |
| 	}
 | |
| 
 | |
| 	/* success */
 | |
| 	return TRUE;
 | |
| }
 | |
| 
 | |
| static guint8
 | |
| fu_uswid_firmware_calculate_hdrsz(FuUswidFirmware *self)
 | |
| {
 | |
| 	FuUswidFirmwarePrivate *priv = GET_PRIVATE(self);
 | |
| 	if (priv->hdrver == 0x1)
 | |
| 		return USWID_HEADER_SIZE_V1;
 | |
| 	return USWID_HEADER_SIZE_V2;
 | |
| }
 | |
| 
 | |
| static GBytes *
 | |
| fu_uswid_firmware_write(FuFirmware *firmware, GError **error)
 | |
| {
 | |
| 	FuUswidFirmware *self = FU_USWID_FIRMWARE(firmware);
 | |
| 	FuUswidFirmwarePrivate *priv = GET_PRIVATE(self);
 | |
| 	g_autoptr(GByteArray) buf = g_byte_array_new();
 | |
| 	g_autoptr(GByteArray) payload = g_byte_array_new();
 | |
| 	g_autoptr(GBytes) payload_blob = NULL;
 | |
| 	g_autoptr(GPtrArray) images = fu_firmware_get_images(firmware);
 | |
| 
 | |
| 	/* generate early so we know the size */
 | |
| 	for (guint i = 0; i < images->len; i++) {
 | |
| 		FuFirmware *firmware_coswid = g_ptr_array_index(images, i);
 | |
| 		g_autoptr(GBytes) fw = fu_firmware_write(firmware_coswid, error);
 | |
| 		if (fw == NULL)
 | |
| 			return NULL;
 | |
| 		fu_byte_array_append_bytes(payload, fw);
 | |
| 	}
 | |
| 
 | |
| 	/* zlibify */
 | |
| 	if (priv->compressed) {
 | |
| 		g_autoptr(GConverter) conv = NULL;
 | |
| 		g_autoptr(GInputStream) istream1 = NULL;
 | |
| 		g_autoptr(GInputStream) istream2 = NULL;
 | |
| 
 | |
| 		conv = G_CONVERTER(g_zlib_compressor_new(G_ZLIB_COMPRESSOR_FORMAT_ZLIB, -1));
 | |
| 		istream1 = g_memory_input_stream_new_from_data(payload->data, payload->len, NULL);
 | |
| 		istream2 = g_converter_input_stream_new(istream1, conv);
 | |
| 		payload_blob = fu_bytes_get_contents_stream(istream2, G_MAXSIZE, error);
 | |
| 		if (payload_blob == NULL)
 | |
| 			return NULL;
 | |
| 	} else {
 | |
| 		payload_blob = g_byte_array_free_to_bytes(g_steal_pointer(&payload));
 | |
| 	}
 | |
| 
 | |
| 	/* header then CBOR blob */
 | |
| 	g_byte_array_append(buf, USWID_HEADER_MAGIC, sizeof(USWID_HEADER_MAGIC));
 | |
| 	fu_byte_array_append_uint8(buf, priv->hdrver);
 | |
| 	fu_byte_array_append_uint16(buf, fu_uswid_firmware_calculate_hdrsz(self), G_LITTLE_ENDIAN);
 | |
| 	fu_byte_array_append_uint32(buf, g_bytes_get_size(payload_blob), G_LITTLE_ENDIAN);
 | |
| 	if (priv->hdrver >= 2) {
 | |
| 		guint8 flags = 0;
 | |
| 		if (priv->compressed)
 | |
| 			flags |= USWID_HEADER_FLAG_COMPRESSED;
 | |
| 		fu_byte_array_append_uint8(buf, flags);
 | |
| 	}
 | |
| 	fu_byte_array_append_bytes(buf, payload_blob);
 | |
| 
 | |
| 	/* success */
 | |
| 	return g_byte_array_free_to_bytes(g_steal_pointer(&buf));
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| fu_uswid_firmware_build(FuFirmware *firmware, XbNode *n, GError **error)
 | |
| {
 | |
| 	FuUswidFirmware *self = FU_USWID_FIRMWARE(firmware);
 | |
| 	FuUswidFirmwarePrivate *priv = GET_PRIVATE(self);
 | |
| 	const gchar *str;
 | |
| 	guint64 tmp;
 | |
| 
 | |
| 	/* simple properties */
 | |
| 	tmp = xb_node_query_text_as_uint(n, "hdrver", NULL);
 | |
| 	if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT8)
 | |
| 		priv->hdrver = tmp;
 | |
| 
 | |
| 	/* simple properties */
 | |
| 	str = xb_node_query_text(n, "compressed", NULL);
 | |
| 	if (str != NULL) {
 | |
| 		if (!fu_strtobool(str, &priv->compressed, error))
 | |
| 			return FALSE;
 | |
| 	}
 | |
| 
 | |
| 	/* success */
 | |
| 	return TRUE;
 | |
| }
 | |
| 
 | |
| static void
 | |
| fu_uswid_firmware_init(FuUswidFirmware *self)
 | |
| {
 | |
| 	FuUswidFirmwarePrivate *priv = GET_PRIVATE(self);
 | |
| 	priv->hdrver = USWID_HEADER_VERSION_V1;
 | |
| 	priv->compressed = FALSE;
 | |
| 	fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_STORED_SIZE);
 | |
| 	fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_ALWAYS_SEARCH);
 | |
| }
 | |
| 
 | |
| static void
 | |
| fu_uswid_firmware_class_init(FuUswidFirmwareClass *klass)
 | |
| {
 | |
| 	FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass);
 | |
| 	klass_firmware->check_magic = fu_uswid_firmware_check_magic;
 | |
| 	klass_firmware->parse = fu_uswid_firmware_parse;
 | |
| 	klass_firmware->write = fu_uswid_firmware_write;
 | |
| 	klass_firmware->build = fu_uswid_firmware_build;
 | |
| 	klass_firmware->export = fu_uswid_firmware_export;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * fu_uswid_firmware_new:
 | |
|  *
 | |
|  * Creates a new #FuFirmware of sub type uSWID
 | |
|  *
 | |
|  * Since: 1.8.0
 | |
|  **/
 | |
| FuFirmware *
 | |
| fu_uswid_firmware_new(void)
 | |
| {
 | |
| 	return FU_FIRMWARE(g_object_new(FU_TYPE_USWID_FIRMWARE, NULL));
 | |
| }
 | 
