diff --git a/data/bash-completion/fwupdmgr.in b/data/bash-completion/fwupdmgr.in index ccdd89c55..623a75917 100644 --- a/data/bash-completion/fwupdmgr.in +++ b/data/bash-completion/fwupdmgr.in @@ -1,5 +1,6 @@ _fwupdmgr_cmd_list=( 'activate' + 'block-firmware' 'clear-history' 'clear-offline' 'clear-results' @@ -7,6 +8,7 @@ _fwupdmgr_cmd_list=( 'downgrade' 'enable-remote' 'get-approved-firmware' + 'get-blocked-firmware' 'get-details' 'get-devices' 'get-history' @@ -25,6 +27,7 @@ _fwupdmgr_cmd_list=( 'security' 'set-approved-firmware' 'unlock' + 'unblock-firmware' 'update' 'upgrade' 'verify' diff --git a/data/daemon.conf b/data/daemon.conf index 93777595c..6a576cfd5 100644 --- a/data/daemon.conf +++ b/data/daemon.conf @@ -32,3 +32,7 @@ EnumerateAllDevices=false # A list of firmware checksums that has been approved by the site admin # If unset, all firmware is approved ApprovedFirmware= + +# Allow blocking specific devices by their checksum, either SHA1 or SHA256 +# Uses semicolons as delimiter +BlockedFirmware= diff --git a/libfwupd/fwupd-client.c b/libfwupd/fwupd-client.c index eef46d0d9..bf96ec381 100644 --- a/libfwupd/fwupd-client.c +++ b/libfwupd/fwupd-client.c @@ -2128,6 +2128,98 @@ fwupd_client_set_approved_firmware (FwupdClient *client, return TRUE; } +/** + * fwupd_client_get_blocked_firmware: + * @client: A #FwupdClient + * @cancellable: the #GCancellable, or %NULL + * @error: the #GError, or %NULL + * + * Gets the list of blocked firmware. + * + * Returns: (transfer full): list of checksums, or %NULL + * + * Since: 1.4.6 + **/ +gchar ** +fwupd_client_get_blocked_firmware (FwupdClient *client, + GCancellable *cancellable, + GError **error) +{ + FwupdClientPrivate *priv = GET_PRIVATE (client); + g_autoptr(GVariant) val = NULL; + gchar **retval = NULL; + + g_return_val_if_fail (FWUPD_IS_CLIENT (client), NULL); + g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + /* connect */ + if (!fwupd_client_connect (client, cancellable, error)) + return NULL; + + /* call into daemon */ + val = g_dbus_proxy_call_sync (priv->proxy, + "GetBlockedFirmware", + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + error); + if (val == NULL) { + if (error != NULL) + fwupd_client_fixup_dbus_error (*error); + return NULL; + } + g_variant_get (val, "(^as)", &retval); + return retval; +} + +/** + * fwupd_client_set_blocked_firmware: + * @client: A #FwupdClient + * @checksums: Array of checksums + * @cancellable: the #GCancellable, or %NULL + * @error: the #GError, or %NULL + * + * Sets the list of blocked firmware. + * + * Returns: %TRUE for success + * + * Since: 1.4.6 + **/ +gboolean +fwupd_client_set_blocked_firmware (FwupdClient *client, + gchar **checksums, + GCancellable *cancellable, + GError **error) +{ + FwupdClientPrivate *priv = GET_PRIVATE (client); + g_autoptr(GVariant) val = NULL; + + g_return_val_if_fail (FWUPD_IS_CLIENT (client), FALSE); + g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + /* connect */ + if (!fwupd_client_connect (client, cancellable, error)) + return FALSE; + + /* call into daemon */ + val = g_dbus_proxy_call_sync (priv->proxy, + "SetBlockedFirmware", + g_variant_new ("(^as)", checksums), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + error); + if (val == NULL) { + if (error != NULL) + fwupd_client_fixup_dbus_error (*error); + return FALSE; + } + return TRUE; +} + /** * fwupd_client_set_feature_flags: * @client: A #FwupdClient diff --git a/libfwupd/fwupd-client.h b/libfwupd/fwupd-client.h index 43011c190..235568e68 100644 --- a/libfwupd/fwupd-client.h +++ b/libfwupd/fwupd-client.h @@ -205,6 +205,13 @@ gboolean fwupd_client_set_approved_firmware (FwupdClient *client, gchar **checksums, GCancellable *cancellable, GError **error); +gchar **fwupd_client_get_blocked_firmware (FwupdClient *client, + GCancellable *cancellable, + GError **error); +gboolean fwupd_client_set_blocked_firmware (FwupdClient *client, + gchar **checksums, + GCancellable *cancellable, + GError **error); gchar *fwupd_client_self_sign (FwupdClient *client, const gchar *value, FwupdSelfSignFlags flags, diff --git a/libfwupd/fwupd.map b/libfwupd/fwupd.map index a70b12756..1b669fe8a 100644 --- a/libfwupd/fwupd.map +++ b/libfwupd/fwupd.map @@ -469,6 +469,13 @@ LIBFWUPD_1.4.5 { local: *; } LIBFWUPD_1.4.1; +LIBFWUPD_1.4.6 { + global: + fwupd_client_get_blocked_firmware; + fwupd_client_set_blocked_firmware; + local: *; +} LIBFWUPD_1.4.5; + LIBFWUPD_1.5.0 { global: fwupd_client_get_host_security_attrs; @@ -508,4 +515,4 @@ LIBFWUPD_1.5.0 { fwupd_security_attr_to_string; fwupd_security_attr_to_variant; local: *; -} LIBFWUPD_1.4.5; +} LIBFWUPD_1.4.6; diff --git a/src/fu-config.c b/src/fu-config.c index b5bdf24ed..646940c03 100644 --- a/src/fu-config.c +++ b/src/fu-config.c @@ -30,6 +30,7 @@ struct _FuConfig GPtrArray *disabled_devices; /* (element-type utf-8) */ GPtrArray *disabled_plugins; /* (element-type utf-8) */ GPtrArray *approved_firmware; /* (element-type utf-8) */ + GPtrArray *blocked_firmware; /* (element-type utf-8) */ guint64 archive_size_max; guint idle_timeout; gchar *config_file; @@ -52,6 +53,7 @@ fu_config_reload (FuConfig *self, GError **error) guint64 archive_size_max; guint idle_timeout; g_auto(GStrv) approved_firmware = NULL; + g_auto(GStrv) blocked_firmware = NULL; g_auto(GStrv) devices = NULL; g_auto(GStrv) plugins = NULL; g_autofree gchar *domains = NULL; @@ -106,6 +108,20 @@ fu_config_reload (FuConfig *self, GError **error) } } + /* get blocked firmware */ + g_ptr_array_set_size (self->blocked_firmware, 0); + blocked_firmware = g_key_file_get_string_list (keyfile, + "fwupd", + "BlockedFirmware", + NULL, /* length */ + NULL); + if (blocked_firmware != NULL) { + for (guint i = 0; blocked_firmware[i] != NULL; i++) { + g_ptr_array_add (self->blocked_firmware, + g_strdup (blocked_firmware[i])); + } + } + /* get maximum archive size, defaulting to something sane */ archive_size_max = g_key_file_get_uint64 (keyfile, "fwupd", @@ -227,6 +243,13 @@ fu_config_get_disabled_devices (FuConfig *self) return self->disabled_devices; } +GPtrArray * +fu_config_get_blocked_firmware (FuConfig *self) +{ + g_return_val_if_fail (FU_IS_CONFIG (self), NULL); + return self->blocked_firmware; +} + guint64 fu_config_get_archive_size_max (FuConfig *self) { @@ -282,6 +305,7 @@ fu_config_init (FuConfig *self) self->disabled_devices = g_ptr_array_new_with_free_func (g_free); self->disabled_plugins = g_ptr_array_new_with_free_func (g_free); self->approved_firmware = g_ptr_array_new_with_free_func (g_free); + self->blocked_firmware = g_ptr_array_new_with_free_func (g_free); } static void @@ -294,6 +318,7 @@ fu_config_finalize (GObject *obj) g_ptr_array_unref (self->disabled_devices); g_ptr_array_unref (self->disabled_plugins); g_ptr_array_unref (self->approved_firmware); + g_ptr_array_unref (self->blocked_firmware); g_free (self->config_file); G_OBJECT_CLASS (fu_config_parent_class)->finalize (obj); diff --git a/src/fu-config.h b/src/fu-config.h index 72b2c28ab..6c8d6e340 100644 --- a/src/fu-config.h +++ b/src/fu-config.h @@ -26,5 +26,6 @@ guint fu_config_get_idle_timeout (FuConfig *self); GPtrArray *fu_config_get_disabled_devices (FuConfig *self); GPtrArray *fu_config_get_disabled_plugins (FuConfig *self); GPtrArray *fu_config_get_approved_firmware (FuConfig *self); +GPtrArray *fu_config_get_blocked_firmware (FuConfig *self); gboolean fu_config_get_update_motd (FuConfig *self); gboolean fu_config_get_enumerate_all_devices (FuConfig *self); diff --git a/src/fu-engine.c b/src/fu-engine.c index d0f7c5975..a87a9ce22 100644 --- a/src/fu-engine.c +++ b/src/fu-engine.c @@ -103,6 +103,7 @@ struct _FuEngine GHashTable *runtime_versions; GHashTable *compile_versions; GHashTable *approved_firmware; /* (nullable) */ + GHashTable *blocked_firmware; /* (nullable) */ GHashTable *firmware_gtypes; gchar *host_machine_id; JcatContext *jcat_context; @@ -527,6 +528,7 @@ fu_engine_modify_config (FuEngine *self, const gchar *key, const gchar *value, G const gchar *keys[] = { "ArchiveSizeMax", "DisabledDevices", + "BlockedFirmware", "DisabledPlugins", "IdleTimeout", "VerboseDomains", @@ -4178,6 +4180,21 @@ fu_engine_check_release_is_approved (FuEngine *self, FwupdRelease *rel) return FALSE; } +static gboolean +fu_engine_check_release_is_blocked (FuEngine *self, FwupdRelease *rel) +{ + GPtrArray *csums = fwupd_release_get_checksums (rel); + if (self->blocked_firmware == NULL) + return FALSE; + for (guint i = 0; i < csums->len; i++) { + const gchar *csum = g_ptr_array_index (csums, i); + g_debug ("checking %s against blocked list", csum); + if (g_hash_table_lookup (self->blocked_firmware, csum) != NULL) + return TRUE; + } + return FALSE; +} + static gboolean fu_engine_add_releases_for_device_component (FuEngine *self, FuEngineRequest *request, @@ -4258,6 +4275,10 @@ fu_engine_add_releases_for_device_component (FuEngine *self, fwupd_release_add_flag (rel, FWUPD_RELEASE_FLAG_BLOCKED_VERSION); } + /* manually blocked */ + if (fu_engine_check_release_is_blocked (self, rel)) + fwupd_release_add_flag (rel, FWUPD_RELEASE_FLAG_BLOCKED_APPROVAL); + /* check if remote is filtering firmware */ remote_id = fwupd_release_get_remote_id (rel); if (remote_id != NULL) { @@ -4545,6 +4566,56 @@ fu_engine_add_approved_firmware (FuEngine *self, const gchar *checksum) g_hash_table_add (self->approved_firmware, g_strdup (checksum)); } +GPtrArray * +fu_engine_get_blocked_firmware (FuEngine *self) +{ + GPtrArray *checksums = g_ptr_array_new_with_free_func (g_free); + if (self->blocked_firmware != NULL) { + g_autoptr(GList) keys = g_hash_table_get_keys (self->blocked_firmware); + for (GList *l = keys; l != NULL; l = l->next) { + const gchar *csum = l->data; + g_ptr_array_add (checksums, g_strdup (csum)); + } + } + return checksums; +} + +void +fu_engine_add_blocked_firmware (FuEngine *self, const gchar *checksum) +{ + if (self->blocked_firmware == NULL) { + self->blocked_firmware = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + NULL); + } + g_hash_table_add (self->blocked_firmware, g_strdup (checksum)); +} + +gboolean +fu_engine_set_blocked_firmware (FuEngine *self, GPtrArray *checksums, GError **error) +{ + /* update in-memory hash */ + if (self->blocked_firmware != NULL) { + g_hash_table_unref (self->blocked_firmware); + self->blocked_firmware = NULL; + } + for (guint i = 0; i < checksums->len; i++) { + const gchar *csum = g_ptr_array_index (checksums, i); + fu_engine_add_blocked_firmware (self, csum); + } + + /* save database */ + if (!fu_history_clear_blocked_firmware (self->history, error)) + return FALSE; + for (guint i = 0; i < checksums->len; i++) { + const gchar *csum = g_ptr_array_index (checksums, i); + if (!fu_history_add_blocked_firmware (self->history, csum, error)) + return FALSE; + } + return TRUE; +} + gchar * fu_engine_self_sign (FuEngine *self, const gchar *value, @@ -6123,7 +6194,8 @@ fu_engine_load (FuEngine *self, FuEngineLoadFlags flags, GError **error) { FuRemoteListLoadFlags remote_list_flags = FU_REMOTE_LIST_LOAD_FLAG_NONE; FuQuirksLoadFlags quirks_flags = FU_QUIRKS_LOAD_FLAG_NONE; - g_autoptr(GPtrArray) checksums = NULL; + g_autoptr(GPtrArray) checksums_approved = NULL; + g_autoptr(GPtrArray) checksums_blocked = NULL; #ifndef _WIN32 g_autoptr(GError) error_local = NULL; #endif @@ -6159,21 +6231,33 @@ fu_engine_load (FuEngine *self, FuEngineLoadFlags flags, GError **error) /* create client certificate */ fu_engine_ensure_client_certificate (self); - /* get hardcoded approved firmware */ - checksums = fu_config_get_approved_firmware (self->config); - for (guint i = 0; i < checksums->len; i++) { - const gchar *csum = g_ptr_array_index (checksums, i); + /* get hardcoded approved and blocked firmware */ + checksums_approved = fu_config_get_approved_firmware (self->config); + for (guint i = 0; i < checksums_approved->len; i++) { + const gchar *csum = g_ptr_array_index (checksums_approved, i); fu_engine_add_approved_firmware (self, csum); } + checksums_blocked = fu_config_get_blocked_firmware (self->config); + for (guint i = 0; i < checksums_blocked->len; i++) { + const gchar *csum = g_ptr_array_index (checksums_blocked, i); + fu_engine_add_blocked_firmware (self, csum); + } /* get extra firmware saved to the database */ - checksums = fu_history_get_approved_firmware (self->history, error); - if (checksums == NULL) + checksums_approved = fu_history_get_approved_firmware (self->history, error); + if (checksums_approved == NULL) return FALSE; - for (guint i = 0; i < checksums->len; i++) { - const gchar *csum = g_ptr_array_index (checksums, i); + for (guint i = 0; i < checksums_approved->len; i++) { + const gchar *csum = g_ptr_array_index (checksums_approved, i); fu_engine_add_approved_firmware (self, csum); } + checksums_blocked = fu_history_get_blocked_firmware (self->history, error); + if (checksums_blocked == NULL) + return FALSE; + for (guint i = 0; i < checksums_blocked->len; i++) { + const gchar *csum = g_ptr_array_index (checksums_blocked, i); + fu_engine_add_blocked_firmware (self, csum); + } /* set up idle exit */ if ((self->app_flags & FU_APP_FLAGS_NO_IDLE_SOURCES) == 0) @@ -6446,6 +6530,8 @@ fu_engine_finalize (GObject *obj) g_source_remove (self->coldplug_id); if (self->approved_firmware != NULL) g_hash_table_unref (self->approved_firmware); + if (self->blocked_firmware != NULL) + g_hash_table_unref (self->blocked_firmware); g_free (self->host_machine_id); g_free (self->host_security_id); diff --git a/src/fu-engine.h b/src/fu-engine.h index eaad096e8..08462198b 100644 --- a/src/fu-engine.h +++ b/src/fu-engine.h @@ -159,6 +159,14 @@ gboolean fu_engine_activate (FuEngine *self, GPtrArray *fu_engine_get_approved_firmware (FuEngine *self); void fu_engine_add_approved_firmware (FuEngine *self, const gchar *checksum); +void fu_engine_set_approved_firmware (FuEngine *self, + GPtrArray *checksums); +GPtrArray *fu_engine_get_blocked_firmware (FuEngine *self); +void fu_engine_add_blocked_firmware (FuEngine *self, + const gchar *checksum); +gboolean fu_engine_set_blocked_firmware (FuEngine *self, + GPtrArray *checksums, + GError **error); gchar *fu_engine_self_sign (FuEngine *self, const gchar *value, JcatSignFlags flags, diff --git a/src/fu-history.c b/src/fu-history.c index 96db6bd91..6de1970d5 100644 --- a/src/fu-history.c +++ b/src/fu-history.c @@ -20,7 +20,7 @@ #include "fu-history.h" #include "fu-mutex.h" -#define FU_HISTORY_CURRENT_SCHEMA_VERSION 5 +#define FU_HISTORY_CURRENT_SCHEMA_VERSION 6 static void fu_history_finalize (GObject *object); @@ -181,6 +181,8 @@ fu_history_create_database (FuHistory *self, GError **error) "protocol TEXT DEFAULT NULL);" "CREATE TABLE IF NOT EXISTS approved_firmware (" "checksum TEXT);" + "CREATE TABLE IF NOT EXISTS blocked_firmware (" + "checksum TEXT);" "COMMIT;", NULL, NULL, NULL); if (rc != SQLITE_OK) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, @@ -269,6 +271,22 @@ fu_history_migrate_database_v4 (FuHistory *self, GError **error) return TRUE; } +static gboolean +fu_history_migrate_database_v5 (FuHistory *self, GError **error) +{ + gint rc; + rc = sqlite3_exec (self->db, + "CREATE TABLE IF NOT EXISTS blocked_firmware (checksum TEXT);", + NULL, NULL, NULL); + if (rc != SQLITE_OK) { + g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, + "Failed to create table: %s", + sqlite3_errmsg (self->db)); + return FALSE; + } + return TRUE; +} + /* returns 0 if database is not initialised */ static guint fu_history_get_schema_version (FuHistory *self) @@ -315,16 +333,26 @@ fu_history_create_or_migrate (FuHistory *self, guint schema_ver, GError **error) return FALSE; if (!fu_history_migrate_database_v4 (self, error)) return FALSE; + if (!fu_history_migrate_database_v5 (self, error)) + return FALSE; } else if (schema_ver == 3) { g_debug ("migrating v%u database by altering", schema_ver); if (!fu_history_migrate_database_v3 (self, error)) return FALSE; if (!fu_history_migrate_database_v4 (self, error)) return FALSE; + if (!fu_history_migrate_database_v5 (self, error)) + return FALSE; } else if (schema_ver == 4) { g_debug ("migrating v%u database by altering", schema_ver); if (!fu_history_migrate_database_v4 (self, error)) return FALSE; + if (!fu_history_migrate_database_v5 (self, error)) + return FALSE; + } else if (schema_ver == 5) { + g_debug ("migrating v%u database by altering", schema_ver); + if (!fu_history_migrate_database_v5 (self, error)) + return FALSE; } else { /* this is probably okay, but return an error if we ever delete * or rename columns */ @@ -1066,6 +1094,139 @@ fu_history_add_approved_firmware (FuHistory *self, sqlite3_bind_text (stmt, 1, checksum, -1, SQLITE_STATIC); return fu_history_stmt_exec (self, stmt, NULL, error); } +/** + * fu_history_get_blocked_firmware: + * @self: A #FuHistory + * @error: A #GError or NULL + * + * Returns blocked firmware records. + * + * Returns: (transfer full) (element-type gchar *): records + * + * Since: 1.4.6 + **/ +GPtrArray * +fu_history_get_blocked_firmware (FuHistory *self, GError **error) +{ + gint rc; + g_autoptr(GRWLockReaderLocker) locker = NULL; + g_autoptr(GPtrArray) array = NULL; + g_autoptr(sqlite3_stmt) stmt = NULL; + + g_return_val_if_fail (FU_IS_HISTORY (self), NULL); + + /* lazy load */ + if (self->db == NULL) { + if (!fu_history_load (self, error)) + return NULL; + } + + /* get all the blocked firmware */ + locker = g_rw_lock_reader_locker_new (&self->db_mutex); + g_return_val_if_fail (locker != NULL, NULL); + rc = sqlite3_prepare_v2 (self->db, + "SELECT checksum FROM blocked_firmware;", + -1, &stmt, NULL); + if (rc != SQLITE_OK) { + g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, + "Failed to prepare SQL to get checksum: %s", + sqlite3_errmsg (self->db)); + return NULL; + } + array = g_ptr_array_new_with_free_func (g_free); + while ((rc = sqlite3_step (stmt)) == SQLITE_ROW) { + const gchar *tmp = (const gchar *) sqlite3_column_text (stmt, 0); + g_ptr_array_add (array, g_strdup (tmp)); + } + if (rc != SQLITE_DONE) { + g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_WRITE, + "failed to execute prepared statement: %s", + sqlite3_errmsg (self->db)); + return NULL; + } + return g_steal_pointer (&array); +} + +/** + * fu_history_clear_blocked_firmware: + * @self: A #FuHistory + * @error: A #GError or NULL + * + * Clear all blocked firmware records + * + * Returns: #TRUE for success, #FALSE for failure + * + * Since: 1.4.6 + **/ +gboolean +fu_history_clear_blocked_firmware (FuHistory *self, GError **error) +{ + gint rc; + g_autoptr(sqlite3_stmt) stmt = NULL; + g_autoptr(GRWLockWriterLocker) locker = NULL; + + g_return_val_if_fail (FU_IS_HISTORY (self), FALSE); + + /* lazy load */ + if (!fu_history_load (self, error)) + return FALSE; + + /* remove entries */ + locker = g_rw_lock_writer_locker_new (&self->db_mutex); + g_return_val_if_fail (locker != NULL, FALSE); + rc = sqlite3_prepare_v2 (self->db, + "DELETE FROM blocked_firmware;", + -1, &stmt, NULL); + if (rc != SQLITE_OK) { + g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, + "Failed to prepare SQL to delete blocked firmware: %s", + sqlite3_errmsg (self->db)); + return FALSE; + } + return fu_history_stmt_exec (self, stmt, NULL, error); +} + +/** + * fu_history_add_blocked_firmware: + * @self: A #FuHistory + * @checksum: a string + * @error: A #GError or NULL + * + * Add an blocked firmware record to the database + * + * Returns: #TRUE for success, #FALSE for failure + * + * Since: 1.4.6 + **/ +gboolean +fu_history_add_blocked_firmware (FuHistory *self, const gchar *checksum, GError **error) +{ + gint rc; + g_autoptr(sqlite3_stmt) stmt = NULL; + g_autoptr(GRWLockWriterLocker) locker = NULL; + + g_return_val_if_fail (FU_IS_HISTORY (self), FALSE); + g_return_val_if_fail (checksum != NULL, FALSE); + + /* lazy load */ + if (!fu_history_load (self, error)) + return FALSE; + + /* add */ + locker = g_rw_lock_writer_locker_new (&self->db_mutex); + g_return_val_if_fail (locker != NULL, FALSE); + rc = sqlite3_prepare_v2 (self->db, + "INSERT INTO blocked_firmware (checksum) " + "VALUES (?1)", -1, &stmt, NULL); + if (rc != SQLITE_OK) { + g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, + "Failed to prepare SQL to insert checksum: %s", + sqlite3_errmsg (self->db)); + return FALSE; + } + sqlite3_bind_text (stmt, 1, checksum, -1, SQLITE_STATIC); + return fu_history_stmt_exec (self, stmt, NULL, error); +} static void fu_history_class_init (FuHistoryClass *klass) diff --git a/src/fu-history.h b/src/fu-history.h index f5b92fa12..84e4ae935 100644 --- a/src/fu-history.h +++ b/src/fu-history.h @@ -47,3 +47,10 @@ gboolean fu_history_add_approved_firmware (FuHistory *self, GError **error); GPtrArray *fu_history_get_approved_firmware (FuHistory *self, GError **error); +gboolean fu_history_clear_blocked_firmware (FuHistory *self, + GError **error); +gboolean fu_history_add_blocked_firmware (FuHistory *self, + const gchar *checksum, + GError **error); +GPtrArray *fu_history_get_blocked_firmware (FuHistory *self, + GError **error); diff --git a/src/fu-main.c b/src/fu-main.c index fe4e7316c..3f5cce94b 100644 --- a/src/fu-main.c +++ b/src/fu-main.c @@ -430,6 +430,30 @@ fu_main_authorize_set_approved_firmware_cb (GObject *source, GAsyncResult *res, g_dbus_method_invocation_return_value (helper->invocation, NULL); } +static void +fu_main_authorize_set_blocked_firmware_cb (GObject *source, GAsyncResult *res, gpointer user_data) +{ + g_autoptr(FuMainAuthHelper) helper = (FuMainAuthHelper *) user_data; + g_autoptr(GError) error = NULL; + g_autoptr(PolkitAuthorizationResult) auth = NULL; + + /* get result */ + fu_main_set_status (helper->priv, FWUPD_STATUS_IDLE); + auth = polkit_authority_check_authorization_finish (POLKIT_AUTHORITY (source), + res, &error); + if (!fu_main_authorization_is_valid (auth, &error)) { + g_dbus_method_invocation_return_gerror (helper->invocation, error); + return; + } + + /* success */ + if (!fu_engine_set_blocked_firmware (helper->priv->engine, helper->checksums, &error)) { + g_dbus_method_invocation_return_gerror (helper->invocation, error); + return; + } + g_dbus_method_invocation_return_value (helper->invocation, NULL); +} + static void fu_main_authorize_self_sign_cb (GObject *source, GAsyncResult *res, gpointer user_data) { @@ -878,6 +902,19 @@ fu_main_daemon_method_call (GDBusConnection *connection, const gchar *sender, g_variant_new_tuple (&val, 1)); return; } + if (g_strcmp0 (method_name, "GetBlockedFirmware") == 0) { + GVariantBuilder builder; + GPtrArray *checksums = fu_engine_get_blocked_firmware (priv->engine); + g_variant_builder_init (&builder, G_VARIANT_TYPE ("as")); + for (guint i = 0; i < checksums->len; i++) { + const gchar *checksum = g_ptr_array_index (checksums, i); + g_variant_builder_add_value (&builder, g_variant_new_string (checksum)); + } + val = g_variant_builder_end (&builder); + g_dbus_method_invocation_return_value (invocation, + g_variant_new_tuple (&val, 1)); + return; + } if (g_strcmp0 (method_name, "GetReportMetadata") == 0) { GHashTableIter iter; GVariantBuilder builder; @@ -932,6 +969,35 @@ fu_main_daemon_method_call (GDBusConnection *connection, const gchar *sender, g_steal_pointer (&helper)); return; } + if (g_strcmp0 (method_name, "SetBlockedFirmware") == 0) { + g_autofree gchar *checksums_str = NULL; + g_auto(GStrv) checksums = NULL; + g_autoptr(FuMainAuthHelper) helper = NULL; + g_autoptr(PolkitSubject) subject = NULL; + + g_variant_get (parameters, "(^as)", &checksums); + checksums_str = g_strjoinv (",", checksums); + g_debug ("Called %s(%s)", method_name, checksums_str); + + /* authenticate */ + fu_main_set_status (priv, FWUPD_STATUS_WAITING_FOR_AUTH); + helper = g_new0 (FuMainAuthHelper, 1); + helper->priv = priv; + helper->request = g_steal_pointer (&request); + helper->invocation = g_object_ref (invocation); + helper->checksums = g_ptr_array_new_with_free_func (g_free); + for (guint i = 0; checksums[i] != NULL; i++) + g_ptr_array_add (helper->checksums, g_strdup (checksums[i])); + subject = polkit_system_bus_name_new (sender); + polkit_authority_check_authorization (priv->authority, subject, + "org.freedesktop.fwupd.set-approved-firmware", + NULL, + POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, + NULL, + fu_main_authorize_set_blocked_firmware_cb, + g_steal_pointer (&helper)); + return; + } if (g_strcmp0 (method_name, "SelfSign") == 0) { GVariant *prop_value; gchar *prop_key; diff --git a/src/fu-util.c b/src/fu-util.c index 11ae78fc2..bff5163a1 100644 --- a/src/fu-util.c +++ b/src/fu-util.c @@ -2311,6 +2311,162 @@ fu_util_check_polkit_actions (GError **error) G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuUtilPrivate, fu_util_private_free) #pragma clang diagnostic pop +static gchar * +fu_util_get_history_checksum (FuUtilPrivate *priv, GError **error) +{ + const gchar *csum; + g_autoptr(FwupdDevice) device = NULL; + g_autoptr(FwupdRelease) release = NULL; + g_autoptr(GPtrArray) devices = NULL; + + devices = fwupd_client_get_history (priv->client, NULL, error); + if (devices == NULL) + return NULL; + device = fu_util_prompt_for_device (priv, devices, error); + if (device == NULL) + return NULL; + release = fu_util_prompt_for_release (priv, fwupd_device_get_releases (device), error); + if (release == NULL) + return NULL; + csum = fwupd_checksum_get_best (fwupd_release_get_checksums (release)); + if (csum == NULL) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_NOTHING_TO_DO, + "No suitable checksums"); + return NULL; + } + return g_strdup (csum); +} + +static gboolean +fu_util_block_firmware (FuUtilPrivate *priv, gchar **values, GError **error) +{ + guint idx = 0; + g_autofree gchar *csum = NULL; + g_auto(GStrv) csums_new = NULL; + g_auto(GStrv) csums = NULL; + + /* get existing checksums */ + csums = fwupd_client_get_blocked_firmware (priv->client, priv->cancellable, error); + if (csums == NULL) + return FALSE; + + /* get new value */ + if (g_strv_length (values) == 0) { + csum = fu_util_get_history_checksum (priv, error); + if (csum == NULL) + return FALSE; + } else { + csum = g_strdup (values[0]); + } + + /* ensure it's not already there */ + if (g_strv_contains ((const gchar * const *) csums, csum)) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_NOTHING_TO_DO, + /* TRANSLATORS: user selected something not possible */ + _("Firmware is already blocked")); + return FALSE; + } + + /* TRANSLATORS: we will not offer this firmware to the user */ + g_print ("%s %s\n", _("Blocking firmware:"), csum); + + /* remove it from the new list */ + csums_new = g_new0 (gchar *, g_strv_length (csums) + 2); + for (guint i = 0; csums[i] != NULL; i++) { + if (g_strcmp0 (csums[i], csum) != 0) + csums_new[idx++] = g_strdup (csums[i]); + } + csums_new[idx] = g_strdup (csum); + return fwupd_client_set_blocked_firmware (priv->client, csums_new, + priv->cancellable, error); +} + +static gboolean +fu_util_unblock_firmware (FuUtilPrivate *priv, gchar **values, GError **error) +{ + guint idx = 0; + g_auto(GStrv) csums = NULL; + g_auto(GStrv) csums_new = NULL; + g_autofree gchar *csum = NULL; + + /* get existing checksums */ + csums = fwupd_client_get_blocked_firmware (priv->client, priv->cancellable, error); + if (csums == NULL) + return FALSE; + + /* empty list */ + if (g_strv_length (csums) == 0) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_NOTHING_TO_DO, + /* TRANSLATORS: nothing to show */ + _("There are no blocked firmware files")); + return FALSE; + } + + /* get new value */ + if (g_strv_length (values) == 0) { + csum = fu_util_get_history_checksum (priv, error); + if (csum == NULL) + return FALSE; + } else { + csum = g_strdup (values[0]); + } + + /* ensure it's there */ + if (!g_strv_contains ((const gchar * const *) csums, csum)) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_NOTHING_TO_DO, + /* TRANSLATORS: user selected something not possible */ + _("Firmware is not already blocked")); + return FALSE; + } + + /* TRANSLATORS: we will not offer this firmware to the user */ + g_print ("%s %s\n", _("Unblocking firmware:"), csum); + + /* remove it from the new list */ + csums_new = g_new0 (gchar *, g_strv_length (csums)); + for (guint i = 0; csums[i] != NULL; i++) { + if (g_strcmp0 (csums[i], csum) != 0) + csums_new[idx++] = g_strdup (csums[i]); + } + return fwupd_client_set_blocked_firmware (priv->client, csums_new, + priv->cancellable, error); +} + +static gboolean +fu_util_get_blocked_firmware (FuUtilPrivate *priv, gchar **values, GError **error) +{ + g_auto(GStrv) csums = NULL; + + /* get checksums */ + csums = fwupd_client_get_blocked_firmware (priv->client, priv->cancellable, error); + if (csums == NULL) + return FALSE; + + /* empty list */ + if (g_strv_length (csums) == 0) { + /* TRANSLATORS: nothing to show */ + g_print ("%s\n", _("There are no blocked firmware files")); + return TRUE; + } + + /* TRANSLATORS: there follows a list of hashes */ + g_print ("%s\n", _("Blocked firmware files:")); + for (guint i = 0; csums[i] != NULL; i++) { + g_print ("%u.\t%s\n", i + 1, csums[i]); + } + + /* success */ + return TRUE; +} + int main (int argc, char *argv[]) { @@ -2557,6 +2713,24 @@ main (int argc, char *argv[]) /* TRANSLATORS: command description */ _("Gets the host security attributes."), fu_util_security); + fu_util_cmd_array_add (cmd_array, + "block-firmware", + "[CHECKSUM]", + /* TRANSLATORS: command description */ + _("Blocks a specific firmware from being installed."), + fu_util_block_firmware); + fu_util_cmd_array_add (cmd_array, + "unblock-firmware", + "[CHECKSUM]", + /* TRANSLATORS: command description */ + _("Blocks a specific firmware from being installed."), + fu_util_unblock_firmware); + fu_util_cmd_array_add (cmd_array, + "get-blocked-firmware", + NULL, + /* TRANSLATORS: command description */ + _("Gets the list of blocked firmware."), + fu_util_get_blocked_firmware); /* do stuff on ctrl+c */ priv->cancellable = g_cancellable_new (); diff --git a/src/org.freedesktop.fwupd.xml b/src/org.freedesktop.fwupd.xml index 79904c793..6b0f7448e 100644 --- a/src/org.freedesktop.fwupd.xml +++ b/src/org.freedesktop.fwupd.xml @@ -489,6 +489,42 @@ + + + + + + Gets the list of blocked firmware. + + + + + + + The checksums of the archives + + + + + + + + + + + Sets the list of blocked firmware that can be applied to devices. + + + + + + + The checksums of the archives + + + + +