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.

    <requires>
      <id compare="ge" version="0.9.2">org.freedesktop.fwupd</id>
      <id compare="ge" version="11">com.redhat.fwupdate</id>
    </requires>

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:

    <requires>
      <firmware>doesnotexist</firmware>
      <some_future_tag>also_unknown</some_future_tag>
    </requires>

Also add a lot of self tests to test the various new failure modes.

Fixes https://github.com/hughsie/fwupd/issues/463
This commit is contained in:
Richard Hughes 2018-04-19 12:00:04 +01:00
parent 58fc7d7467
commit 0eb123b986
8 changed files with 291 additions and 116 deletions

View File

@ -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())

View File

@ -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

View File

@ -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);
}

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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 */

View File

@ -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);