From 41400a8cc65c1fef4aa5b72c0d6642220788a482 Mon Sep 17 00:00:00 2001 From: Richard Hughes Date: Mon, 21 Sep 2020 13:43:15 +0100 Subject: [PATCH] Allow contructing a firmware with multiple images At the moment there are commands to convert one file format to another, but not to 'merge' or alter them. Some firmware files are containers which can store multiple images, each with optional id, idx and addresses. This would allow us to, for instance, create a DfuSe file with two different raw files that are flashed to different addresses on the SPI flash. It would also allow us to create very small complicated container formats for fuzzing. This can be used by writing a `firmware.builder.xml` file like: 1.2.3 4.5.6 header 456 0x456 header.bin 7.8.9 payload 789 0x789 aGVsbG8= ...and then using something like: # fwupdtool firmware-convert firmware.builder.xml firmware.dfu builder dfu --- libfwupdplugin/fu-firmware-image.c | 57 ++++++++++++++++ libfwupdplugin/fu-firmware-image.h | 4 ++ libfwupdplugin/fu-firmware.c | 50 ++++++++++++++ libfwupdplugin/fu-firmware.h | 8 ++- libfwupdplugin/fu-self-test.c | 70 +++++++++++++++++++ libfwupdplugin/fwupdplugin.map | 2 + plugins/dfu/meson.build | 1 + plugins/synaptics-prometheus/meson.build | 1 + plugins/synaptics-rmi/meson.build | 1 + src/fu-tool.c | 85 ++++++++++++++++++++---- src/meson.build | 1 + 11 files changed, 265 insertions(+), 15 deletions(-) diff --git a/libfwupdplugin/fu-firmware-image.c b/libfwupdplugin/fu-firmware-image.c index 63a28df39..bfef23a28 100644 --- a/libfwupdplugin/fu-firmware-image.c +++ b/libfwupdplugin/fu-firmware-image.c @@ -259,6 +259,63 @@ fu_firmware_image_parse (FuFirmwareImage *self, return TRUE; } +/** + * fu_firmware_image_build: + * @self: A #FuFirmwareImage + * @n: A #XbNode + * @error: A #GError, or %NULL + * + * Builds a firmware image from an XML manifest. + * + * Returns: %TRUE for success + * + * Since: 1.5.0 + **/ +gboolean +fu_firmware_image_build (FuFirmwareImage *self, XbNode *n, GError **error) +{ + guint64 tmpval; + const gchar *tmp; + + g_return_val_if_fail (FU_IS_FIRMWARE_IMAGE (self), FALSE); + g_return_val_if_fail (XB_IS_NODE (n), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + tmp = xb_node_query_text (n, "version", NULL); + if (tmp != NULL) + fu_firmware_image_set_version (self, tmp); + tmp = xb_node_query_text (n, "id", NULL); + if (tmp != NULL) + fu_firmware_image_set_id (self, tmp); + tmpval = xb_node_query_text_as_uint (n, "idx", NULL); + if (tmpval != G_MAXUINT64) + fu_firmware_image_set_idx (self, tmpval); + tmpval = xb_node_query_text_as_uint (n, "addr", NULL); + if (tmpval != G_MAXUINT64) + fu_firmware_image_set_addr (self, tmpval); + tmp = xb_node_query_text (n, "filename", NULL); + if (tmp != NULL) { + g_autoptr(GBytes) blob = NULL; + blob = fu_common_get_contents_bytes (tmp, error); + if (blob == NULL) + return FALSE; + fu_firmware_image_set_bytes (self, blob); + fu_firmware_image_set_filename (self, tmp); + } + tmp = xb_node_query_text (n, "data", NULL); + if (tmp != NULL) { + gsize bufsz = 0; + g_autofree guchar *buf = NULL; + g_autoptr(GBytes) blob = NULL; + buf = g_base64_decode (tmp, &bufsz); + blob = g_bytes_new (buf, bufsz); + fu_firmware_image_set_bytes (self, blob); + } + + /* success */ + return TRUE; +} + /** * fu_firmware_image_write: * @self: a #FuPlugin diff --git a/libfwupdplugin/fu-firmware-image.h b/libfwupdplugin/fu-firmware-image.h index 97be313d1..7310a3b8e 100644 --- a/libfwupdplugin/fu-firmware-image.h +++ b/libfwupdplugin/fu-firmware-image.h @@ -8,6 +8,7 @@ #include #include +#include #define FU_TYPE_FIRMWARE_IMAGE (fu_firmware_image_get_type ()) G_DECLARE_DERIVABLE_TYPE (FuFirmwareImage, fu_firmware_image, FU, FIRMWARE_IMAGE, GObject) @@ -56,6 +57,9 @@ gboolean fu_firmware_image_parse (FuFirmwareImage *self, GBytes *fw, FwupdInstallFlags flags, GError **error); +gboolean fu_firmware_image_build (FuFirmwareImage *self, + XbNode *n, + GError **error); GBytes *fu_firmware_image_write (FuFirmwareImage *self, GError **error); GBytes *fu_firmware_image_write_chunk (FuFirmwareImage *self, diff --git a/libfwupdplugin/fu-firmware.c b/libfwupdplugin/fu-firmware.c index 375d060c3..5e5b60bab 100644 --- a/libfwupdplugin/fu-firmware.c +++ b/libfwupdplugin/fu-firmware.c @@ -239,6 +239,56 @@ fu_firmware_parse (FuFirmware *self, GBytes *fw, FwupdInstallFlags flags, GError return fu_firmware_parse_full (self, fw, 0x0, 0x0, flags, error); } +/** + * fu_firmware_build: + * @self: A #FuFirmware + * @n: A #XbNode + * @error: A #GError, or %NULL + * + * Builds a firmware from an XML manifest. + * + * Returns: %TRUE for success + * + * Since: 1.5.0 + **/ +gboolean +fu_firmware_build (FuFirmware *self, XbNode *n, GError **error) +{ + FuFirmwareClass *klass = FU_FIRMWARE_GET_CLASS (self); + const gchar *tmp; + g_autoptr(GPtrArray) xb_images = NULL; + + g_return_val_if_fail (FU_IS_FIRMWARE (self), FALSE); + g_return_val_if_fail (XB_IS_NODE (n), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + /* set attributes */ + tmp = xb_node_query_text (n, "version", NULL); + if (tmp != NULL) + fu_firmware_set_version (self, tmp); + + /* parse images */ + xb_images = xb_node_query (n, "image", 0, NULL); + if (xb_images != NULL) { + for (guint i = 0; i < xb_images->len; i++) { + XbNode *xb_image = g_ptr_array_index (xb_images, i); + g_autoptr(FuFirmwareImage) img = fu_firmware_image_new (NULL); + if (!fu_firmware_image_build (img, xb_image, error)) + return FALSE; + fu_firmware_add_image (self, img); + } + } + + /* subclassed */ + if (klass->build != NULL) { + if (!klass->build (self, n, error)) + return FALSE; + } + + /* success */ + return TRUE; +} + /** * fu_firmware_parse_file: * @self: A #FuFirmware diff --git a/libfwupdplugin/fu-firmware.h b/libfwupdplugin/fu-firmware.h index 798787aef..119de9a82 100644 --- a/libfwupdplugin/fu-firmware.h +++ b/libfwupdplugin/fu-firmware.h @@ -32,8 +32,11 @@ struct _FuFirmwareClass GBytes *fw, FwupdInstallFlags flags, GError **error); + gboolean (*build) (FuFirmware *self, + XbNode *n, + GError **error); /*< private >*/ - gpointer padding[28]; + gpointer padding[27]; }; /** @@ -67,6 +70,9 @@ gboolean fu_firmware_tokenize (FuFirmware *self, GBytes *fw, FwupdInstallFlags flags, GError **error); +gboolean fu_firmware_build (FuFirmware *self, + XbNode *n, + GError **error); gboolean fu_firmware_parse (FuFirmware *self, GBytes *fw, FwupdInstallFlags flags, diff --git a/libfwupdplugin/fu-self-test.c b/libfwupdplugin/fu-self-test.c index 7db112b8e..238a5755a 100644 --- a/libfwupdplugin/fu-self-test.c +++ b/libfwupdplugin/fu-self-test.c @@ -1539,6 +1539,75 @@ fu_firmware_srec_tokenization_func (void) g_assert_cmpint (rcd->buf->data[0], ==, 0x50); } +static void +fu_firmware_build_func (void) +{ + gboolean ret; + g_autofree gchar *str = NULL; + g_autoptr(FuFirmware) firmware = fu_firmware_new (); + g_autoptr(FuFirmwareImage) img = NULL; + g_autoptr(GBytes) blob = NULL; + g_autoptr(GBytes) blob2 = NULL; + g_autoptr(GError) error = NULL; + g_autoptr(XbBuilder) builder = xb_builder_new (); + g_autoptr(XbBuilderSource) source = xb_builder_source_new (); + g_autoptr(XbNode) n = NULL; + g_autoptr(XbSilo) silo = NULL; + const gchar *buf = + "\n" + "\n" + " 1.2.3\n" + " \n" + " 4.5.6\n" + " header\n" + " 456\n" + " 0x456\n" + " aGVsbG8=\n" + " \n" + " \n" + " 7.8.9\n" + " header\n" + " 789\n" + " 0x789\n" + " \n" + "\n"; + blob = g_bytes_new_static (buf, strlen (buf)); + g_assert_no_error (error); + g_assert_nonnull (blob); + + /* parse XML */ + ret = xb_builder_source_load_bytes (source, blob, XB_BUILDER_SOURCE_FLAG_NONE, &error); + g_assert_no_error (error); + g_assert (ret); + xb_builder_import_source (builder, source); + silo = xb_builder_compile (builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (silo); + n = xb_silo_query_first (silo, "firmware", &error); + g_assert_no_error (error); + g_assert_nonnull (n); + + /* build object */ + ret = fu_firmware_build (firmware, n, &error); + g_assert_no_error (error); + g_assert (ret); + g_assert_cmpstr (fu_firmware_get_version (firmware), ==, "1.2.3"); + + /* verify image */ + img = fu_firmware_get_image_by_id (firmware, "header", &error); + g_assert_no_error (error); + g_assert_nonnull (img); + g_assert_cmpstr (fu_firmware_image_get_version (img), ==, "4.5.6"); + g_assert_cmpint (fu_firmware_image_get_idx (img), ==, 456); + g_assert_cmpint (fu_firmware_image_get_addr (img), ==, 0x456); + blob2 = fu_firmware_image_write (img, &error); + g_assert_no_error (error); + g_assert_nonnull (blob2); + g_assert_cmpint (g_bytes_get_size (blob2), ==, 5); + str = g_strndup (g_bytes_get_data (blob2, NULL), g_bytes_get_size (blob2)); + g_assert_cmpstr (str, ==, "hello"); +} + static void fu_firmware_dfu_func (void) { @@ -1976,6 +2045,7 @@ main (int argc, char **argv) g_test_add_func ("/fwupd/smbios3", fu_smbios3_func); g_test_add_func ("/fwupd/firmware", fu_firmware_func); g_test_add_func ("/fwupd/firmware{dedupe}", fu_firmware_dedupe_func); + g_test_add_func ("/fwupd/firmware{build}", fu_firmware_build_func); g_test_add_func ("/fwupd/firmware{ihex}", fu_firmware_ihex_func); g_test_add_func ("/fwupd/firmware{ihex-offset}", fu_firmware_ihex_offset_func); g_test_add_func ("/fwupd/firmware{ihex-signed}", fu_firmware_ihex_signed_func); diff --git a/libfwupdplugin/fwupdplugin.map b/libfwupdplugin/fwupdplugin.map index f79829e75..89353c287 100644 --- a/libfwupdplugin/fwupdplugin.map +++ b/libfwupdplugin/fwupdplugin.map @@ -618,9 +618,11 @@ LIBFWUPDPLUGIN_1.5.0 { fu_device_report_metadata_post; fu_device_report_metadata_pre; fu_firmware_add_flag; + fu_firmware_build; fu_firmware_flag_from_string; fu_firmware_flag_to_string; fu_firmware_has_flag; + fu_firmware_image_build; fu_firmware_image_get_filename; fu_firmware_image_parse; fu_firmware_image_set_filename; diff --git a/plugins/dfu/meson.build b/plugins/dfu/meson.build index 0a5a9a4e2..4fca2246f 100644 --- a/plugins/dfu/meson.build +++ b/plugins/dfu/meson.build @@ -24,6 +24,7 @@ dfu = static_library( dependencies : [ giounix, libm, + libxmlb, gusb, gudev, ], diff --git a/plugins/synaptics-prometheus/meson.build b/plugins/synaptics-prometheus/meson.build index b7b5d0a65..fed45214c 100644 --- a/plugins/synaptics-prometheus/meson.build +++ b/plugins/synaptics-prometheus/meson.build @@ -76,6 +76,7 @@ if get_option('tests') ], dependencies : [ gio, + libxmlb, ], link_with : [ fwupd, diff --git a/plugins/synaptics-rmi/meson.build b/plugins/synaptics-rmi/meson.build index 3e71f911c..3272fad87 100644 --- a/plugins/synaptics-rmi/meson.build +++ b/plugins/synaptics-rmi/meson.build @@ -48,6 +48,7 @@ if get_option('tests') ], dependencies : [ gio, + libxmlb, ], link_with : [ fwupd, diff --git a/src/fu-tool.c b/src/fu-tool.c index fdb78f27e..0694f3f9f 100644 --- a/src/fu-tool.c +++ b/src/fu-tool.c @@ -1639,12 +1639,16 @@ fu_util_get_firmware_types (FuUtilPrivate *priv, gchar **values, GError **error) } static gchar * -fu_util_prompt_for_firmware_type (FuUtilPrivate *priv, GError **error) +fu_util_prompt_for_firmware_type (FuUtilPrivate *priv, gboolean add_builder, GError **error) { g_autoptr(GPtrArray) firmware_types = NULL; guint idx; firmware_types = fu_engine_get_firmware_gtype_ids (priv->engine); + /* add fake entry */ + if (add_builder) + g_ptr_array_add (firmware_types, g_strdup ("builder")); + /* TRANSLATORS: get interactive prompt */ g_print ("%s\n", _("Choose a firmware type:")); /* TRANSLATORS: this is to abort the interactive prompt */ @@ -1697,7 +1701,7 @@ fu_util_firmware_parse (FuUtilPrivate *priv, gchar **values, GError **error) /* find the GType to use */ if (firmware_type == NULL) - firmware_type = fu_util_prompt_for_firmware_type (priv, error); + firmware_type = fu_util_prompt_for_firmware_type (priv, TRUE, error); if (firmware_type == NULL) return FALSE; gtype = fu_engine_get_firmware_gtype_by_id (priv->engine, firmware_type); @@ -1716,6 +1720,53 @@ fu_util_firmware_parse (FuUtilPrivate *priv, gchar **values, GError **error) return TRUE; } +static FuFirmware * +fu_util_firmware_builder_new (FuUtilPrivate *priv, GBytes *fw, GError **error) +{ + const gchar *tmp; + g_autoptr(FuFirmware) firmware = NULL; + g_autoptr(XbBuilder) builder = xb_builder_new (); + g_autoptr(XbBuilderSource) source = xb_builder_source_new (); + g_autoptr(XbNode) n = NULL; + g_autoptr(XbSilo) silo = NULL; + + /* parse XML */ + if (!xb_builder_source_load_bytes (source, fw, + XB_BUILDER_SOURCE_FLAG_NONE, + error)) { + g_prefix_error (error, "could not parse XML: "); + return NULL; + } + xb_builder_import_source (builder, source); + silo = xb_builder_compile (builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, error); + if (silo == NULL) + return NULL; + + /* create FuFirmware of specific GType */ + n = xb_silo_query_first (silo, "firmware", error); + if (n == NULL) + return FALSE; + tmp = xb_node_get_attr (n, "gtype"); + if (tmp != NULL) { + GType gtype = fu_engine_get_firmware_gtype_by_id (priv->engine, tmp); + if (gtype == G_TYPE_INVALID) { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_NOT_FOUND, + "GType %s not supported", tmp); + return NULL; + } + firmware = g_object_new (gtype, NULL); + } else { + firmware = fu_firmware_new (); + } + if (!fu_firmware_build (firmware, n, error)) + return NULL; + + /* success */ + return g_steal_pointer (&firmware); +} + static gboolean fu_util_firmware_convert (FuUtilPrivate *priv, gchar **values, GError **error) { @@ -1756,20 +1807,29 @@ fu_util_firmware_convert (FuUtilPrivate *priv, gchar **values, GError **error) /* find the GType to use */ if (firmware_type_src == NULL) - firmware_type_src = fu_util_prompt_for_firmware_type (priv, error); + firmware_type_src = fu_util_prompt_for_firmware_type (priv, TRUE, error); if (firmware_type_src == NULL) return FALSE; if (firmware_type_dst == NULL) - firmware_type_dst = fu_util_prompt_for_firmware_type (priv, error); + firmware_type_dst = fu_util_prompt_for_firmware_type (priv, FALSE, error); if (firmware_type_dst == NULL) return FALSE; - gtype_src = fu_engine_get_firmware_gtype_by_id (priv->engine, firmware_type_src); - if (gtype_src == G_TYPE_INVALID) { - g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_NOT_FOUND, - "GType %s not supported", firmware_type_src); - return FALSE; + if (g_strcmp0 (firmware_type_src, "builder") == 0) { + firmware_src = fu_util_firmware_builder_new (priv, blob_src, error); + if (firmware_src == NULL) + return FALSE; + } else { + gtype_src = fu_engine_get_firmware_gtype_by_id (priv->engine, firmware_type_src); + if (gtype_src == G_TYPE_INVALID) { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_NOT_FOUND, + "GType %s not supported", firmware_type_src); + return FALSE; + } + firmware_src = g_object_new (gtype_src, NULL); + if (!fu_firmware_parse (firmware_src, blob_src, priv->flags, error)) + return FALSE; } gtype_dst = fu_engine_get_firmware_gtype_by_id (priv->engine, firmware_type_dst); if (gtype_dst == G_TYPE_INVALID) { @@ -1779,9 +1839,6 @@ fu_util_firmware_convert (FuUtilPrivate *priv, gchar **values, GError **error) "GType %s not supported", firmware_type_dst); return FALSE; } - firmware_src = g_object_new (gtype_src, NULL); - if (!fu_firmware_parse (firmware_src, blob_src, priv->flags, error)) - return FALSE; str_src = fu_firmware_to_string (firmware_src); g_print ("%s", str_src); diff --git a/src/meson.build b/src/meson.build index d63a52092..8a07de033 100644 --- a/src/meson.build +++ b/src/meson.build @@ -345,6 +345,7 @@ if get_option('tests') fwupdplugin_incdir, ], dependencies : [ + libxmlb, gio, ], link_with : [