From 0eb123b986dd10d3cd6883662d19c1b39f43c5fb Mon Sep 17 00:00:00 2001 From: Richard Hughes Date: Thu, 19 Apr 2018 12:00:04 +0100 Subject: [PATCH] Allow requiring specific versions of libraries for firmware updates In some cases firmware can only be installed with an up to date GUsb (e.g. with some STM-DFU hardware) or with a new version of fwupdate (e.g. any UEFI UpdateCapsule without a capsule header). We should be able to match against other software versions like we can the fwupd version, e.g. org.freedesktop.fwupd com.redhat.fwupdate Also, rather than checking each requirement we know about on the component, check each requirement on the component about things we know. This ensures we don't allow firmware to be installs that requires for instance fwupdate 22 when the runtime version is only being added in fwupdate 12 and up. This means the following is now an error that will fail to allow the firmware to be installed: doesnotexist also_unknown Also add a lot of self tests to test the various new failure modes. Fixes https://github.com/hughsie/fwupd/issues/463 --- meson.build | 3 + plugins/uefi/fu-plugin-uefi.c | 7 + src/fu-engine.c | 237 +++++++++++++++++----------------- src/fu-engine.h | 7 + src/fu-plugin-private.h | 2 + src/fu-plugin.c | 29 +++++ src/fu-plugin.h | 4 + src/fu-self-test.c | 118 +++++++++++++++++ 8 files changed, 291 insertions(+), 116 deletions(-) diff --git a/meson.build b/meson.build index e3a982e32..51599f52d 100644 --- a/meson.build +++ b/meson.build @@ -200,6 +200,9 @@ if get_option('plugin_uefi') if fwup.version().version_compare('>= 11') conf.set('HAVE_FWUP_GET_ESP_MOUNTPOINT', '1') endif + if fwup.version().version_compare('>= 12') + conf.set('HAVE_FWUP_VERSION', '1') + endif efivar = dependency('efivar') conf.set_quoted('EFIVAR_LIBRARY_VERSION', efivar.version()) conf.set_quoted('LIBFWUP_LIBRARY_VERSION', fwup.version()) diff --git a/plugins/uefi/fu-plugin-uefi.c b/plugins/uefi/fu-plugin-uefi.c index 284949d99..09d3102d6 100644 --- a/plugins/uefi/fu-plugin-uefi.c +++ b/plugins/uefi/fu-plugin-uefi.c @@ -53,11 +53,18 @@ void fu_plugin_init (FuPlugin *plugin) { FuPluginData *data = fu_plugin_alloc_data (plugin, sizeof (FuPluginData)); +#ifdef HAVE_FWUP_VERSION + g_autofree gchar *version_str = NULL; +#endif data->ux_capsule = FALSE; data->esp_path = NULL; fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_RUN_AFTER, "upower"); fu_plugin_add_report_metadata (plugin, "FwupdateVersion", LIBFWUP_LIBRARY_VERSION); fu_plugin_add_report_metadata (plugin, "EfivarVersion", EFIVAR_LIBRARY_VERSION); +#ifdef HAVE_FWUP_VERSION + version_str = g_strdup_printf ("%i", fwup_version ()); + fu_plugin_add_runtime_version (plugin, "com.redhat.fwupdate", version_str); +#endif } void diff --git a/src/fu-engine.c b/src/fu-engine.c index 9a489c5f4..7d4406a92 100644 --- a/src/fu-engine.c +++ b/src/fu-engine.c @@ -79,6 +79,7 @@ struct _FuEngine FuSmbios *smbios; FuHwids *hwids; FuQuirks *quirks; + GHashTable *runtime_versions; }; enum { @@ -95,11 +96,6 @@ static guint signals[SIGNAL_LAST] = { 0 }; G_DEFINE_TYPE (FuEngine, fu_engine, G_TYPE_OBJECT) -#define FU_ENGINE_REQUIREMENT_FIRMWARE_RUNTIME NULL /* yes, NULL */ -#define FU_ENGINE_REQUIREMENT_FIRMWARE_BOOTLOADER "bootloader" -#define FU_ENGINE_REQUIREMENT_FIRMWARE_VENDOR "vendor-id" -#define FU_ENGINE_REQUIREMENT_ID_FWUPD "org.freedesktop.fwupd" - static void fu_engine_emit_changed (FuEngine *self) { @@ -898,35 +894,15 @@ _as_store_get_apps_by_provide (AsStore *store, AsProvideKind kind, const gchar * } static gboolean -fu_engine_check_version_requirement (AsApp *app, - AsRequireKind kind, - const gchar *id, - const gchar *version, - GError **error) +fu_engine_check_requirement_firmware (FuEngine *self, AsRequire *req, + FuDevice *device, GError **error) { - AsRequire *req; g_autoptr(GError) error_local = NULL; - /* check args */ - if (version == NULL) { - g_debug ("no parameter given for %s{%s}", - as_require_kind_to_string (kind), id); - return TRUE; - } - - /* does requirement exist */ - req = as_app_get_require_by_value (app, kind, id); - if (req == NULL) { - g_debug ("no requirement on %s{%s}", - as_require_kind_to_string (kind), id); - return TRUE; - } - - /* check version */ - if (!as_require_version_compare (req, version, &error_local)) { - - /* firmware */ - if (g_strcmp0 (id, FU_ENGINE_REQUIREMENT_FIRMWARE_RUNTIME) == 0) { + /* old firmware version */ + if (as_require_get_value (req) == NULL) { + const gchar *version = fu_device_get_version (device); + if (!as_require_version_compare (req, version, &error_local)) { if (as_require_get_compare (req) == AS_REQUIRE_COMPARE_GE) { g_set_error (error, FWUPD_ERROR, @@ -942,27 +918,13 @@ fu_engine_check_version_requirement (AsApp *app, } return FALSE; } + return TRUE; + } - /* fwupd */ - if (g_strcmp0 (id, FU_ENGINE_REQUIREMENT_ID_FWUPD) == 0) { - if (as_require_get_compare (req) == AS_REQUIRE_COMPARE_GE) { - g_set_error (error, - FWUPD_ERROR, - FWUPD_ERROR_INVALID_FILE, - "Not compatible with fwupd version %s, requires >= %s", - version, as_require_get_version (req)); - } else { - g_set_error (error, - FWUPD_ERROR, - FWUPD_ERROR_INVALID_FILE, - "Not compatible with fwupd version: %s", - error_local->message); - } - return FALSE; - } - - /* bootloader */ - if (g_strcmp0 (id, FU_ENGINE_REQUIREMENT_FIRMWARE_BOOTLOADER) == 0) { + /* bootloader version */ + if (g_strcmp0 (as_require_get_value (req), "bootloader") == 0) { + const gchar *version = fu_device_get_version_bootloader (device); + if (!as_require_version_compare (req, version, &error_local)) { if (as_require_get_compare (req) == AS_REQUIRE_COMPARE_GE) { g_set_error (error, FWUPD_ERROR, @@ -978,9 +940,13 @@ fu_engine_check_version_requirement (AsApp *app, } return FALSE; } + return TRUE; + } - /* vendor */ - if (g_strcmp0 (id, FU_ENGINE_REQUIREMENT_FIRMWARE_VENDOR) == 0) { + /* vendor ID */ + if (g_strcmp0 (as_require_get_value (req), "vendor-id") == 0) { + const gchar *version = fu_device_get_vendor_id (device); + if (!as_require_version_compare (req, version, &error_local)) { if (as_require_get_compare (req) == AS_REQUIRE_COMPARE_GE) { g_set_error (error, FWUPD_ERROR, @@ -996,88 +962,105 @@ fu_engine_check_version_requirement (AsApp *app, } return FALSE; } - - /* anything else */ - g_set_error (error, - FWUPD_ERROR, - FWUPD_ERROR_INVALID_FILE, - "Not compatible with %s: %s", - id, error_local->message); - return FALSE; + return TRUE; } - /* success */ - g_debug ("requirement %s %s %s on %s passed", - as_require_get_version (req), - as_require_compare_to_string (as_require_get_compare (req)), - version, id); - return TRUE; + /* not supported */ + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "cannot handle firmware requirement %s", + as_require_get_value (req)); + return FALSE; } static gboolean -fu_engine_check_hardware_requirement (FuEngine *self, AsApp *app, GError **error) +fu_engine_check_requirement_id (FuEngine *self, AsRequire *req, GError **error) { - GPtrArray *requires = as_app_get_requires (app); - - /* check each HWID requirement */ - for (guint i = 0; i < requires->len; i++) { - AsRequire *req = g_ptr_array_index (requires, i); - if (as_require_get_kind (req) != AS_REQUIRE_KIND_HARDWARE) - continue; - if (!fu_hwids_has_guid (self->hwids, as_require_get_value (req))) { + g_autoptr(GError) error_local = NULL; + const gchar *version = g_hash_table_lookup (self->runtime_versions, + as_require_get_value (req)); + if (version == NULL) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_FOUND, + "no version available for %s", + as_require_get_value (req)); + return FALSE; + } + if (!as_require_version_compare (req, version, &error_local)) { + if (as_require_get_compare (req) == AS_REQUIRE_COMPARE_GE) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, - "no HWIDs matched %s", - as_require_get_value (req)); - return FALSE; + "Not compatible with %s version %s, requires >= %s", + as_require_get_value (req), version, + as_require_get_version (req)); + } else { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "Not compatible with %s version: %s", + as_require_get_value (req), error_local->message); } - g_debug ("HWID provided %s", as_require_get_value (req)); + return FALSE; } - /* success */ + g_debug ("requirement %s %s %s on %s passed", + as_require_get_version (req), + as_require_compare_to_string (as_require_get_compare (req)), + version, as_require_get_value (req)); return TRUE; } static gboolean +fu_engine_check_requirement_hardware (FuEngine *self, AsRequire *req, GError **error) +{ + if (!fu_hwids_has_guid (self->hwids, as_require_get_value (req))) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "no HWIDs matched %s", + as_require_get_value (req)); + return FALSE; + } + g_debug ("HWID provided %s", as_require_get_value (req)); + return TRUE; +} + +static gboolean +fu_engine_check_requirement (FuEngine *self, AsRequire *req, FuDevice *device, GError **error) +{ + /* ensure component requirement */ + if (as_require_get_kind (req) == AS_REQUIRE_KIND_ID) + return fu_engine_check_requirement_id (self, req, error); + + /* ensure firmware requirement */ + if (device != NULL && as_require_get_kind (req) == AS_REQUIRE_KIND_FIRMWARE) + return fu_engine_check_requirement_firmware (self, req, device, error); + + /* ensure hardware requirement */ + if (as_require_get_kind (req) == AS_REQUIRE_KIND_HARDWARE) + return fu_engine_check_requirement_hardware (self, req, error); + + /* not supported */ + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "cannot handle requirement type %s", + as_require_kind_to_string (as_require_get_kind (req))); + return FALSE; +} + +gboolean fu_engine_check_requirements (FuEngine *self, AsApp *app, FuDevice *device, GError **error) { - /* make sure requirements are satisfied */ - if (!fu_engine_check_version_requirement (app, - AS_REQUIRE_KIND_ID, - FU_ENGINE_REQUIREMENT_ID_FWUPD, - VERSION, - error)) { - return FALSE; + GPtrArray *reqs = as_app_get_requires (app); + for (guint i = 0; i < reqs->len; i++) { + AsRequire *req = g_ptr_array_index (reqs, i); + if (!fu_engine_check_requirement (self, req, device, error)) + return FALSE; } - if (!fu_engine_check_hardware_requirement (self, app, error)) - return FALSE; - - if (device != NULL) { - if (!fu_engine_check_version_requirement (app, - AS_REQUIRE_KIND_FIRMWARE, - FU_ENGINE_REQUIREMENT_FIRMWARE_RUNTIME, - fu_device_get_version (device), - error)) { - return FALSE; - } - if (!fu_engine_check_version_requirement (app, - AS_REQUIRE_KIND_FIRMWARE, - FU_ENGINE_REQUIREMENT_FIRMWARE_BOOTLOADER, - fu_device_get_version_bootloader (device), - error)) { - return FALSE; - } - if (!fu_engine_check_version_requirement (app, - AS_REQUIRE_KIND_FIRMWARE, - FU_ENGINE_REQUIREMENT_FIRMWARE_VENDOR, - fu_device_get_vendor_id (device), - error)) { - return FALSE; - } - } - - /* success */ return TRUE; } @@ -3158,6 +3141,7 @@ fu_engine_load_plugins (FuEngine *self, GError **error) fu_plugin_set_smbios (plugin, self->smbios); fu_plugin_set_supported (plugin, self->supported_guids); fu_plugin_set_quirks (plugin, self->quirks); + fu_plugin_set_runtime_versions (plugin, self->runtime_versions); g_debug ("adding plugin %s", filename); if (!fu_plugin_open (plugin, filename, &error_local)) { g_warning ("failed to open plugin %s: %s", @@ -3570,6 +3554,16 @@ fu_engine_class_init (FuEngineClass *klass) G_TYPE_NONE, 1, G_TYPE_UINT); } +void +fu_engine_add_runtime_version (FuEngine *self, + const gchar *component_id, + const gchar *version) +{ + g_hash_table_insert (self->runtime_versions, + g_strdup (component_id), + g_strdup (version)); +} + static void fu_engine_init (FuEngine *self) { @@ -3585,6 +3579,16 @@ fu_engine_init (FuEngine *self) self->profile = as_profile_new (); self->store = as_store_new (); self->supported_guids = g_ptr_array_new_with_free_func (g_free); + self->runtime_versions = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + /* add some runtime versions of things the daemon depends on */ + fu_engine_add_runtime_version (self, "org.freedesktop.fwupd", VERSION); +#if AS_CHECK_VERSION(0,7,8) + fu_engine_add_runtime_version (self, "org.freedesktop.appstream-glib", as_version_string ()); +#endif +#if G_USB_CHECK_VERSION(0,3,1) + fu_engine_add_runtime_version (self, "org.freedesktop.gusb", g_usb_version_string ()); +#endif } static void @@ -3607,6 +3611,7 @@ fu_engine_finalize (GObject *obj) g_object_unref (self->store); g_object_unref (self->device_list); g_ptr_array_unref (self->supported_guids); + g_hash_table_unref (self->runtime_versions); G_OBJECT_CLASS (fu_engine_parent_class)->finalize (obj); } diff --git a/src/fu-engine.h b/src/fu-engine.h index 707bed965..0f418d054 100644 --- a/src/fu-engine.h +++ b/src/fu-engine.h @@ -111,6 +111,13 @@ void fu_engine_add_device (FuEngine *self, FuDevice *device); void fu_engine_add_plugin (FuEngine *self, FuPlugin *plugin); +void fu_engine_add_runtime_version (FuEngine *self, + const gchar *component_id, + const gchar *version); +gboolean fu_engine_check_requirements (FuEngine *self, + AsApp *app, + FuDevice *device, + GError **error); G_END_DECLS diff --git a/src/fu-plugin-private.h b/src/fu-plugin-private.h index a80ea5dd1..d89148f13 100644 --- a/src/fu-plugin-private.h +++ b/src/fu-plugin-private.h @@ -40,6 +40,8 @@ void fu_plugin_set_supported (FuPlugin *plugin, GPtrArray *supported_guids); void fu_plugin_set_quirks (FuPlugin *plugin, FuQuirks *quirks); +void fu_plugin_set_runtime_versions (FuPlugin *plugin, + GHashTable *runtime_versions); void fu_plugin_set_smbios (FuPlugin *plugin, FuSmbios *smbios); guint fu_plugin_get_order (FuPlugin *plugin); diff --git a/src/fu-plugin.c b/src/fu-plugin.c index f3cb3303a..af6222aba 100644 --- a/src/fu-plugin.c +++ b/src/fu-plugin.c @@ -57,6 +57,7 @@ typedef struct { gchar *name; FuHwids *hwids; FuQuirks *quirks; + GHashTable *runtime_versions; GPtrArray *supported_guids; FuSmbios *smbios; GHashTable *devices; /* platform_id:GObject */ @@ -660,6 +661,34 @@ fu_plugin_get_quirks (FuPlugin *plugin) return priv->quirks; } +void +fu_plugin_set_runtime_versions (FuPlugin *plugin, GHashTable *runtime_versions) +{ + FuPluginPrivate *priv = GET_PRIVATE (plugin); + priv->runtime_versions = g_hash_table_ref (runtime_versions); +} + +/** + * fu_plugin_add_runtime_version: + * @plugin: A #FuPlugin + * @component_id: An AppStream component id, e.g. "org.gnome.Software" + * @version: A version string, e.g. "1.2.3" + * + * Sets a runtime version of a specific dependancy. + * + * Since: 1.0.7 + **/ +void +fu_plugin_add_runtime_version (FuPlugin *plugin, + const gchar *component_id, + const gchar *version) +{ + FuPluginPrivate *priv = GET_PRIVATE (plugin); + g_hash_table_insert (priv->runtime_versions, + g_strdup (component_id), + g_strdup (version)); +} + /** * fu_plugin_lookup_quirk_by_id: * @plugin: A #FuPlugin diff --git a/src/fu-plugin.h b/src/fu-plugin.h index 700076fb3..3ceffaadb 100644 --- a/src/fu-plugin.h +++ b/src/fu-plugin.h @@ -149,6 +149,10 @@ void fu_plugin_add_report_metadata (FuPlugin *plugin, const gchar *value); gchar *fu_plugin_get_config_value (FuPlugin *plugin, const gchar *key); +void fu_plugin_add_runtime_version (FuPlugin *plugin, + const gchar *component_id, + const gchar *version); + G_END_DECLS #endif /* __FU_PLUGIN_H */ diff --git a/src/fu-self-test.c b/src/fu-self-test.c index d51cc1c7c..00e029642 100644 --- a/src/fu-self-test.c +++ b/src/fu-self-test.c @@ -52,6 +52,120 @@ #include "fu-keyring-pkcs7.h" #endif +static void +fu_engine_requirements_missing_func (void) +{ + gboolean ret; + g_autoptr(AsApp) app = as_app_new (); + g_autoptr(AsRequire) req = as_require_new (); + g_autoptr(FuEngine) engine = fu_engine_new (); + g_autoptr(GError) error = NULL; + + /* set up a dummy version */ + fu_engine_add_runtime_version (engine, "org.test.dummy", "1.2.3"); + + /* make the component require one thing */ + as_require_set_kind (req, AS_REQUIRE_KIND_ID); + as_require_set_compare (req, AS_REQUIRE_COMPARE_GE); + as_require_set_version (req, "1.2.3"); + as_require_set_value (req, "not.going.to.exist"); + as_app_add_require (app, req); + + /* check this fails */ + ret = fu_engine_check_requirements (engine, app, NULL, &error); + g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND); + g_assert (!ret); +} + +static void +fu_engine_requirements_unsupported_func (void) +{ + gboolean ret; + g_autoptr(AsApp) app = as_app_new (); + g_autoptr(AsRequire) req = as_require_new (); + g_autoptr(FuEngine) engine = fu_engine_new (); + g_autoptr(GError) error = NULL; + + /* set up a dummy version */ + fu_engine_add_runtime_version (engine, "org.test.dummy", "1.2.3"); + + /* make the component require one thing that we don't support */ + as_require_set_kind (req, AS_REQUIRE_KIND_LAST); + as_require_set_compare (req, AS_REQUIRE_COMPARE_GE); + as_require_set_version (req, "2.6.0"); + as_app_add_require (app, req); + + /* check this fails */ + ret = fu_engine_check_requirements (engine, app, NULL, &error); + g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); + g_assert (!ret); +} + +static void +fu_engine_requirements_func (void) +{ + gboolean ret; + g_autoptr(AsApp) app = as_app_new (); + g_autoptr(AsRequire) req = as_require_new (); + g_autoptr(FuEngine) engine = fu_engine_new (); + g_autoptr(GError) error = NULL; + + /* set up some dummy versions */ + fu_engine_add_runtime_version (engine, "org.test.dummy", "1.2.3"); + fu_engine_add_runtime_version (engine, "com.hughski.colorhug", "7.8.9"); + + /* make the component require one thing */ + as_require_set_kind (req, AS_REQUIRE_KIND_ID); + as_require_set_compare (req, AS_REQUIRE_COMPARE_GE); + as_require_set_version (req, "1.2.3"); + as_require_set_value (req, "org.test.dummy"); + as_app_add_require (app, req); + + /* check this passes */ + ret = fu_engine_check_requirements (engine, app, NULL, &error); + g_assert_no_error (error); + g_assert (ret); +} + +static void +fu_engine_requirements_device_func (void) +{ + gboolean ret; + g_autoptr(AsApp) app = as_app_new (); + g_autoptr(AsRequire) req1 = as_require_new (); + g_autoptr(AsRequire) req2 = as_require_new (); + g_autoptr(AsRequire) req3 = as_require_new (); + g_autoptr(FuDevice) device = fu_device_new (); + g_autoptr(FuEngine) engine = fu_engine_new (); + g_autoptr(GError) error = NULL; + + /* set up a dummy device */ + fu_device_set_version (device, "1.2.3"); + fu_device_set_version_bootloader (device, "4.5.6"); + fu_device_set_vendor_id (device, "FFFF"); + + /* make the component require three things */ + as_require_set_kind (req1, AS_REQUIRE_KIND_FIRMWARE); + as_require_set_compare (req1, AS_REQUIRE_COMPARE_GE); + as_require_set_version (req1, "1.2.3"); + as_app_add_require (app, req1); + as_require_set_kind (req2, AS_REQUIRE_KIND_FIRMWARE); + as_require_set_compare (req2, AS_REQUIRE_COMPARE_EQ); + as_require_set_version (req2, "4.5.6"); + as_require_set_value (req2, "bootloader"); + as_app_add_require (app, req3); + as_require_set_kind (req3, AS_REQUIRE_KIND_FIRMWARE); + as_require_set_compare (req3, AS_REQUIRE_COMPARE_EQ); + as_require_set_version (req3, "FFFF"); + as_require_set_value (req3, "vendor-id"); + as_app_add_require (app, req3); + + /* check this passes */ + ret = fu_engine_check_requirements (engine, app, device, &error); + g_assert_no_error (error); + g_assert (ret); +} + static void fu_engine_partial_hash_func (void) { @@ -2004,6 +2118,10 @@ main (int argc, char **argv) g_test_add_func ("/fwupd/engine{require-hwid}", fu_engine_require_hwid_func); g_test_add_func ("/fwupd/engine{partial-hash}", fu_engine_partial_hash_func); g_test_add_func ("/fwupd/engine{downgrade}", fu_engine_downgrade_func); + g_test_add_func ("/fwupd/engine{requirements-success}", fu_engine_requirements_func); + g_test_add_func ("/fwupd/engine{requirements-missing}", fu_engine_requirements_missing_func); + g_test_add_func ("/fwupd/engine{requirements-unsupported}", fu_engine_requirements_unsupported_func); + g_test_add_func ("/fwupd/engine{requirements-device}", fu_engine_requirements_device_func); g_test_add_func ("/fwupd/hwids", fu_hwids_func); g_test_add_func ("/fwupd/smbios", fu_smbios_func); g_test_add_func ("/fwupd/smbios3", fu_smbios3_func);