From a043c2e3769bca8906fb4c71cf3e705e474564d0 Mon Sep 17 00:00:00 2001 From: Richard Hughes Date: Mon, 29 Jun 2015 08:43:18 +0100 Subject: [PATCH] Add a 'verify' command that verifies the cryptographic hash of device firmware Goes some way towards fixing https://github.com/hughsie/fwupd/issues/15 --- src/fu-device.h | 1 + src/fu-main.c | 30 +++++++++ src/fu-provider-chug.c | 72 ++++++++++++++++++++ src/fu-provider-udev.c | 49 ++++++++++++++ src/fu-provider.c | 15 +++++ src/fu-provider.h | 13 ++++ src/fu-util.c | 120 ++++++++++++++++++++++++++++++++++ src/org.freedesktop.fwupd.xml | 29 ++++++++ 8 files changed, 329 insertions(+) diff --git a/src/fu-device.h b/src/fu-device.h index c768f8672..232f1acb3 100644 --- a/src/fu-device.h +++ b/src/fu-device.h @@ -54,6 +54,7 @@ G_BEGIN_DECLS #define FU_DEVICE_KEY_PENDING_STATE "PendingState" /* s */ #define FU_DEVICE_KEY_PENDING_ERROR "PendingError" /* s */ #define FU_DEVICE_KEY_TRUSTED "Trusted" /* t */ +#define FU_DEVICE_KEY_FIRMWARE_HASH "FirmwareHash" /* s */ typedef struct _FuDevicePrivate FuDevicePrivate; typedef struct _FuDevice FuDevice; diff --git a/src/fu-main.c b/src/fu-main.c index 6deeac861..a063340db 100644 --- a/src/fu-main.c +++ b/src/fu-main.c @@ -782,6 +782,36 @@ fu_main_daemon_method_call (GDBusConnection *connection, const gchar *sender, return; } + /* return 's' */ + if (g_strcmp0 (method_name, "Verify") == 0) { + FuDeviceItem *item = NULL; + const gchar *id = NULL; + _cleanup_error_free_ GError *error = NULL; + const gchar *hash = NULL; + + /* check the id exists */ + g_variant_get (parameters, "(&s)", &id); + g_debug ("Called %s(%s)", method_name, id); + item = fu_main_get_item_by_id (priv, id); + if (item == NULL) { + g_dbus_method_invocation_return_error (invocation, + FWUPD_ERROR, + FWUPD_ERROR_NOT_FOUND, + "no such device %s", + id); + return; + } + if (!fu_provider_verify (item->provider, item->device, + FU_PROVIDER_VERIFY_FLAG_NONE, &error)) { + g_dbus_method_invocation_return_gerror (invocation, error); + return; + } + hash = fu_device_get_metadata (item->device, FU_DEVICE_KEY_FIRMWARE_HASH); + g_dbus_method_invocation_return_value (invocation, + g_variant_new ("(s)", hash)); + return; + } + /* return '' */ if (g_strcmp0 (method_name, "Update") == 0) { FuDeviceItem *item = NULL; diff --git a/src/fu-provider-chug.c b/src/fu-provider-chug.c index cdddd96ca..4ae797200 100644 --- a/src/fu-provider-chug.c +++ b/src/fu-provider-chug.c @@ -222,6 +222,77 @@ out: g_debug ("Failed to close: %s", error->message); } +/** + * fu_provider_chug_verify: + **/ +static gboolean +fu_provider_chug_verify (FuProvider *provider, + FuDevice *device, + FuProviderVerifyFlags flags, + GError **error) +{ + FuProviderChug *provider_chug = FU_PROVIDER_CHUG (provider); + FuProviderChugPrivate *priv = provider_chug->priv; + FuProviderChugItem *item; + gsize len; + _cleanup_error_free_ GError *error_local = NULL; + _cleanup_free_ gchar *hash = NULL; + _cleanup_free_ guint8 *data = NULL; + + /* find item */ + item = g_hash_table_lookup (priv->devices, fu_device_get_id (device)); + if (item == NULL) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_FOUND, + "cannot find: %s", + fu_device_get_id (device)); + return FALSE; + } + +#if !CD_CHECK_VERSION(1,2,12) + /* recompile colord */ + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "Cannot read firmware: colord too old"); + return FALSE; +#endif + + /* open */ + if (!fu_provider_chug_open (item, error)) + return FALSE; + + /* get the firmware from the device */ + g_debug ("ColorHug: Verifying firmware"); +#if CD_CHECK_VERSION(1,2,12) + ch_device_queue_read_firmware (priv->device_queue, item->usb_device, + &data, &len); +#endif + fu_provider_set_status (provider, FWUPD_STATUS_DEVICE_VERIFY); + if (!ch_device_queue_process (priv->device_queue, + CH_DEVICE_QUEUE_PROCESS_FLAGS_NONE, + NULL, &error_local)) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_WRITE, + "failed to dump firmware: %s", + error_local->message); + g_usb_device_close (item->usb_device, NULL); + return FALSE; + } + + /* get the SHA1 hash */ + hash = g_compute_checksum_for_data (G_CHECKSUM_SHA1, (guchar *) data, len); + fu_device_set_metadata (device, FU_DEVICE_KEY_FIRMWARE_HASH, hash); + + /* we're done here */ + if (!g_usb_device_close (item->usb_device, &error_local)) + g_debug ("Failed to close: %s", error_local->message); + + return TRUE; +} + /** * fu_provider_chug_update: **/ @@ -593,6 +664,7 @@ fu_provider_chug_class_init (FuProviderChugClass *klass) provider_class->get_name = fu_provider_chug_get_name; provider_class->coldplug = fu_provider_chug_coldplug; provider_class->update_online = fu_provider_chug_update; + provider_class->verify = fu_provider_chug_verify; object_class->finalize = fu_provider_chug_finalize; g_type_class_add_private (klass, sizeof (FuProviderChugPrivate)); diff --git a/src/fu-provider-udev.c b/src/fu-provider-udev.c index cb79e65e0..681e672ff 100644 --- a/src/fu-provider-udev.c +++ b/src/fu-provider-udev.c @@ -174,6 +174,52 @@ fu_provider_udev_get_rom_version (const gchar *rom_fn, GError **error) return NULL; } +/** + * fu_provider_udev_verify: + **/ +static gboolean +fu_provider_udev_verify (FuProvider *provider, + FuDevice *device, + FuProviderVerifyFlags flags, + GError **error) +{ + const gchar *rom_fn; + guint8 buffer[4096]; + _cleanup_checksum_free_ GChecksum *hash = NULL; + _cleanup_object_unref_ GFile *file = NULL; + _cleanup_object_unref_ GFileInputStream *input_stream = NULL; + + /* open the file */ + rom_fn = fu_device_get_metadata (device, "RomFilename"); + if (rom_fn == NULL) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "Unable to read firmware from device"); + return FALSE; + } + file = g_file_new_for_path (rom_fn); + input_stream = g_file_read (file, NULL, error); + if (input_stream == NULL) + return NULL; + + hash = g_checksum_new (G_CHECKSUM_SHA1); + fu_provider_set_status (provider, FWUPD_STATUS_DEVICE_VERIFY); + while (TRUE) { + gssize sz; + sz = g_input_stream_read (G_INPUT_STREAM (input_stream), + buffer, + sizeof (buffer), + NULL, NULL); + if (sz <= 0) + break; + g_checksum_update (hash, buffer, sz); + } + fu_device_set_metadata (device, FU_DEVICE_KEY_FIRMWARE_HASH, + g_checksum_get_string (hash)); + return TRUE; +} + /** * fu_provider_udev_client_add: **/ @@ -268,6 +314,8 @@ fu_provider_udev_client_add (FuProviderUdev *provider_udev, GUdevDevice *device) if (vendor != NULL) fu_device_set_metadata (dev, FU_DEVICE_KEY_VENDOR, vendor); fu_device_set_metadata (dev, FU_DEVICE_KEY_VERSION, version); + if (g_file_test (rom_fn, G_FILE_TEST_EXISTS)) + fu_device_set_metadata (dev, "RomFilename", rom_fn); /* insert to hash */ g_hash_table_insert (provider_udev->priv->devices, g_strdup (id), dev); @@ -353,6 +401,7 @@ fu_provider_udev_class_init (FuProviderUdevClass *klass) provider_class->get_name = fu_provider_udev_get_name; provider_class->coldplug = fu_provider_udev_coldplug; + provider_class->verify = fu_provider_udev_verify; object_class->finalize = fu_provider_udev_finalize; g_type_class_add_private (klass, sizeof (FuProviderUdevPrivate)); diff --git a/src/fu-provider.c b/src/fu-provider.c index 30ab46ae0..a0f717417 100644 --- a/src/fu-provider.c +++ b/src/fu-provider.c @@ -184,6 +184,21 @@ fu_provider_schedule_update (FuProvider *provider, return fu_provider_offline_setup (error); } +/** + * fu_provider_verify: + **/ +gboolean +fu_provider_verify (FuProvider *provider, + FuDevice *device, + FuProviderVerifyFlags flags, + GError **error) +{ + FuProviderClass *klass = FU_PROVIDER_GET_CLASS (provider); + if (klass->verify != NULL) + return klass->verify (provider, device, flags, error); + return TRUE; +} + /** * fu_provider_update: **/ diff --git a/src/fu-provider.h b/src/fu-provider.h index f1873c99a..a58912377 100644 --- a/src/fu-provider.h +++ b/src/fu-provider.h @@ -52,6 +52,11 @@ typedef enum { FU_PROVIDER_UPDATE_FLAG_LAST } FuProviderFlags; +typedef enum { + FU_PROVIDER_VERIFY_FLAG_NONE = 0, + FU_PROVIDER_VERIFY_FLAG_LAST +} FuProviderVerifyFlags; + struct _FuProviderClass { GObjectClass parent_class; @@ -60,6 +65,10 @@ struct _FuProviderClass const gchar *(*get_name) (FuProvider *provider); gboolean (*coldplug) (FuProvider *provider, GError **error); + gboolean (*verify) (FuProvider *provider, + FuDevice *device, + FuProviderVerifyFlags flags, + GError **error); gboolean (*update_online) (FuProvider *provider, FuDevice *device, gint fd, @@ -104,6 +113,10 @@ gboolean fu_provider_update (FuProvider *provider, gint fd_fw, FuProviderFlags flags, GError **error); +gboolean fu_provider_verify (FuProvider *provider, + FuDevice *device, + FuProviderVerifyFlags flags, + GError **error); gboolean fu_provider_clear_results (FuProvider *provider, FuDevice *device, GError **error); diff --git a/src/fu-util.c b/src/fu-util.c index a4fc7e2e6..feb77b13a 100644 --- a/src/fu-util.c +++ b/src/fu-util.c @@ -319,6 +319,7 @@ fu_util_get_devices (FuUtilPrivate *priv, gchar **values, GError **error) FU_DEVICE_KEY_FLAGS, FU_DEVICE_KEY_TRUSTED, FU_DEVICE_KEY_SIZE, + FU_DEVICE_KEY_FIRMWARE_HASH, NULL }; const gchar *flags_str[] = { "Internal", @@ -887,6 +888,119 @@ fu_util_get_results (FuUtilPrivate *priv, gchar **values, GError **error) return TRUE; } + +/** + * fu_util_verify_internal: + **/ +static gboolean +fu_util_verify_internal (FuUtilPrivate *priv, const gchar *id, GError **error) +{ + _cleanup_variant_unref_ GVariant *val = NULL; + g_dbus_proxy_call (priv->proxy, + "Verify", + g_variant_new ("(s)", id), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + fu_util_get_devices_cb, priv); + g_main_loop_run (priv->loop); + if (priv->val == NULL) { + g_dbus_error_strip_remote_error (priv->error); + g_propagate_error (error, priv->error); + priv->error = NULL; + return FALSE; + } + return TRUE; + +} + +/** + * fu_util_verify_all: + **/ +static gboolean +fu_util_verify_all (FuUtilPrivate *priv, GError **error) +{ + AsApp *app; + FuDevice *dev; + const gchar *tmp; + guint i; + _cleanup_object_unref_ AsStore *store = NULL; + _cleanup_ptrarray_unref_ GPtrArray *devices = NULL; + + /* get devices from daemon */ + devices = fu_util_get_devices_internal (priv, error); + if (devices == NULL) + return FALSE; + + /* get results */ + for (i = 0; i < devices->len; i++) { + _cleanup_error_free_ GError *error_local = NULL; + dev = g_ptr_array_index (devices, i); + if (!fu_util_verify_internal (priv, fu_device_get_id (dev), &error_local)) { + g_print ("Failed to verify %s: %s\n", + fu_device_get_id (dev), + error_local->message); + } + } + + /* only load firmware from the system */ + store = as_store_new (); + as_store_add_filter (store, AS_ID_KIND_FIRMWARE); + if (!as_store_load (store, AS_STORE_LOAD_FLAG_APP_INFO_SYSTEM, NULL, error)) + return FALSE; + + /* print */ + for (i = 0; i < devices->len; i++) { + const gchar *hash = NULL; + const gchar *ver = NULL; + _cleanup_free_ gchar *status = NULL; + + dev = g_ptr_array_index (devices, i); + hash = fu_device_get_metadata (dev, FU_DEVICE_KEY_FIRMWARE_HASH); + if (hash == NULL) + continue; + app = as_store_get_app_by_id (store, fu_device_get_guid (dev)); + if (app == NULL) { + status = g_strdup ("No metadata"); + } else { + AsRelease *rel; + ver = fu_device_get_metadata (dev, FU_DEVICE_KEY_VERSION); + rel = as_app_get_release (app, ver); + if (rel == NULL) { + status = g_strdup_printf ("No version %s", ver); + } else { + tmp = as_release_get_checksum (rel, G_CHECKSUM_SHA1); + if (g_strcmp0 (tmp, hash) != 0) { + status = g_strdup_printf ("Failed: for v%s expected %s", ver, tmp); + } else { + status = g_strdup ("OK"); + } + } + } + g_print ("%s\t%s\t%s\n", fu_device_get_guid (dev), hash, status); + } + + return TRUE; +} + +/** + * fu_util_verify: + **/ +static gboolean +fu_util_verify (FuUtilPrivate *priv, gchar **values, GError **error) +{ + if (g_strv_length (values) == 0) + return fu_util_verify_all (priv, error); + if (g_strv_length (values) != 1) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "Invalid arguments: expected 'id'"); + return FALSE; + } + return fu_util_verify_internal (priv, values[0], error); +} + /** * fu_util_print_data: **/ @@ -1127,6 +1241,12 @@ main (int argc, char *argv[]) /* TRANSLATORS: command description */ _("Gets the list of updates for connected hardware"), fu_util_get_updates); + fu_util_add (priv->cmd_array, + "verify", + NULL, + /* TRANSLATORS: command description */ + _("Gets the cryptographic hash of the dumped firmware"), + fu_util_verify); fu_util_add (priv->cmd_array, "clear-results", NULL, diff --git a/src/org.freedesktop.fwupd.xml b/src/org.freedesktop.fwupd.xml index 781f3e981..6c5e1ebda 100644 --- a/src/org.freedesktop.fwupd.xml +++ b/src/org.freedesktop.fwupd.xml @@ -122,6 +122,35 @@ + + + + + + Verifies firmware on a device by reading it back and performing + a cryptographic hash, typically SHA1. + + + + + + + + An ID, typically a GUID of the hardware. + + + + + + + + The cryptographic hash of the firmware. + + + + + +