From 196c6c69dbc21660b5af7399ebaa9d0bbf5f77a0 Mon Sep 17 00:00:00 2001 From: Richard Hughes Date: Mon, 11 May 2020 19:42:47 +0100 Subject: [PATCH] Add support for the Host Security ID The HSI specification assigns a simple text ID to the current state of firmware security. As new vulnerabilities are found, and as protection measures are updated, new requirements will be added to the required firmware behaviours for each HSI value. The HSI specification is currently incomplete and in active development, and so the --force flag is required in all command line tools. The current ID value will probably change on a given platform so please do not start using the result for any kind of compliance requirements. --- data/bash-completion/fwupdagent | 1 + data/bash-completion/fwupdmgr.in | 1 + data/bash-completion/fwupdtool.in | 1 + libfwupd/fwupd-client.c | 96 ++++++++++++++++++++++++ libfwupd/fwupd-client.h | 4 + libfwupd/fwupd.map | 2 + libfwupdplugin/fu-plugin-private.h | 3 + libfwupdplugin/fu-plugin-vfuncs.h | 13 ++++ libfwupdplugin/fu-plugin.c | 20 +++++ libfwupdplugin/fwupdplugin.map | 1 + src/fu-agent.c | 80 ++++++++++++++++++++ src/fu-engine.c | 55 ++++++++++++++ src/fu-engine.h | 3 + src/fu-main.c | 32 ++++++++ src/fu-security-attrs.c | 115 +++++++++++++++++++++++++++++ src/fu-security-attrs.h | 12 +++ src/fu-self-test.c | 72 ++++++++++++++++++ src/fu-tool.c | 38 ++++++++++ src/fu-util-common.c | 69 +++++++++++++++++ src/fu-util-common.h | 1 + src/fu-util.c | 39 +++++++++- src/meson.build | 3 + src/org.freedesktop.fwupd.xml | 29 ++++++++ 23 files changed, 689 insertions(+), 1 deletion(-) create mode 100644 src/fu-security-attrs.c create mode 100644 src/fu-security-attrs.h diff --git a/data/bash-completion/fwupdagent b/data/bash-completion/fwupdagent index f4c3fd582..5234625da 100644 --- a/data/bash-completion/fwupdagent +++ b/data/bash-completion/fwupdagent @@ -2,6 +2,7 @@ _fwupdagent_cmd_list=( 'get-devices' 'get-updates' 'get-upgrades' + 'security' ) _fwupdagent_opts=( diff --git a/data/bash-completion/fwupdmgr.in b/data/bash-completion/fwupdmgr.in index 2fe070f9b..ccdd89c55 100644 --- a/data/bash-completion/fwupdmgr.in +++ b/data/bash-completion/fwupdmgr.in @@ -22,6 +22,7 @@ _fwupdmgr_cmd_list=( 'reinstall' 'refresh' 'report-history' + 'security' 'set-approved-firmware' 'unlock' 'update' diff --git a/data/bash-completion/fwupdtool.in b/data/bash-completion/fwupdtool.in index 2d2865802..4e9ce103e 100644 --- a/data/bash-completion/fwupdtool.in +++ b/data/bash-completion/fwupdtool.in @@ -20,6 +20,7 @@ _fwupdtool_cmd_list=( 'install-blob' 'monitor' 'reinstall' + 'security' 'self-sign' 'smbios-dump' 'attach' diff --git a/libfwupd/fwupd-client.c b/libfwupd/fwupd-client.c index 18729c027..bfa0403e0 100644 --- a/libfwupd/fwupd-client.c +++ b/libfwupd/fwupd-client.c @@ -23,6 +23,7 @@ #include "fwupd-enums.h" #include "fwupd-error.h" #include "fwupd-device-private.h" +#include "fwupd-security-attr-private.h" #include "fwupd-release-private.h" #include "fwupd-remote-private.h" @@ -45,6 +46,7 @@ typedef struct { gchar *daemon_version; gchar *host_product; gchar *host_machine_id; + gchar *host_security_id; GDBusConnection *conn; GDBusProxy *proxy; } FwupdClientPrivate; @@ -66,6 +68,7 @@ enum { PROP_TAINTED, PROP_HOST_PRODUCT, PROP_HOST_MACHINE_ID, + PROP_HOST_SECURITY_ID, PROP_INTERACTIVE, PROP_LAST }; @@ -128,6 +131,15 @@ fwupd_client_set_host_machine_id (FwupdClient *client, const gchar *host_machine g_object_notify (G_OBJECT (client), "host-machine-id"); } +static void +fwupd_client_set_host_security_id (FwupdClient *client, const gchar *host_security_id) +{ + FwupdClientPrivate *priv = GET_PRIVATE (client); + g_free (priv->host_security_id); + priv->host_security_id = g_strdup (host_security_id); + g_object_notify (G_OBJECT (client), "host-security-id"); +} + static void fwupd_client_set_daemon_version (FwupdClient *client, const gchar *daemon_version) { @@ -201,6 +213,12 @@ fwupd_client_properties_changed_cb (GDBusProxy *proxy, if (val != NULL) fwupd_client_set_host_machine_id (client, g_variant_get_string (val, NULL)); } + if (g_variant_dict_contains (dict, "HostSecurityId")) { + g_autoptr(GVariant) val = NULL; + val = g_dbus_proxy_get_cached_property (proxy, "HostSecurityId"); + if (val != NULL) + fwupd_client_set_host_security_id (client, g_variant_get_string (val, NULL)); + } } static void @@ -304,6 +322,9 @@ fwupd_client_connect (FwupdClient *client, GCancellable *cancellable, GError **e val = g_dbus_proxy_get_cached_property (priv->proxy, "HostMachineId"); if (val != NULL) fwupd_client_set_host_machine_id (client, g_variant_get_string (val, NULL)); + val = g_dbus_proxy_get_cached_property (priv->proxy, "HostSecurityId"); + if (val != NULL) + fwupd_client_set_host_security_id (client, g_variant_get_string (val, NULL)); return TRUE; } @@ -341,6 +362,48 @@ fwupd_client_fixup_dbus_error (GError *error) g_dbus_error_strip_remote_error (error); } +/** + * fwupd_client_get_host_security_attrs: + * @client: A #FwupdClient + * @cancellable: the #GCancellable, or %NULL + * @error: the #GError, or %NULL + * + * Gets all the host security attributes from the daemon. + * + * Returns: (element-type FwupdSecurityAttr) (transfer container): attributes + * + * Since: 1.5.0 + **/ +GPtrArray * +fwupd_client_get_host_security_attrs (FwupdClient *client, GCancellable *cancellable, GError **error) +{ + FwupdClientPrivate *priv = GET_PRIVATE (client); + g_autoptr(GVariant) val = 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, + "GetHostSecurityAttrs", + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + error); + if (val == NULL) { + if (error != NULL) + fwupd_client_fixup_dbus_error (*error); + return NULL; + } + return fwupd_security_attr_array_from_variant (val); +} + /** * fwupd_client_get_devices: * @client: A #FwupdClient @@ -1314,6 +1377,24 @@ fwupd_client_get_host_machine_id (FwupdClient *client) return priv->host_machine_id; } +/** + * fwupd_client_get_host_security_id: + * @client: A #FwupdClient + * + * Gets the string that represents the host machine ID + * + * Returns: a string, or %NULL for unknown. + * + * Since: 1.5.0 + **/ +const gchar * +fwupd_client_get_host_security_id (FwupdClient *client) +{ + FwupdClientPrivate *priv = GET_PRIVATE (client); + g_return_val_if_fail (FWUPD_IS_CLIENT (client), NULL); + return priv->host_security_id; +} + /** * fwupd_client_get_status: * @client: A #FwupdClient @@ -1869,6 +1950,9 @@ fwupd_client_get_property (GObject *object, guint prop_id, case PROP_HOST_MACHINE_ID: g_value_set_string (value, priv->host_machine_id); break; + case PROP_HOST_SECURITY_ID: + g_value_set_string (value, priv->host_security_id); + break; case PROP_INTERACTIVE: g_value_set_boolean (value, priv->interactive); break; @@ -2069,6 +2153,17 @@ fwupd_client_class_init (FwupdClientClass *klass) pspec = g_param_spec_string ("host-machine-id", NULL, NULL, NULL, G_PARAM_READABLE | G_PARAM_STATIC_NAME); g_object_class_install_property (object_class, PROP_HOST_MACHINE_ID, pspec); + + /** + * FwupdClient:host-security-id: + * + * The host machine-id string + * + * Since: 1.5.0 + */ + pspec = g_param_spec_string ("host-security-id", NULL, NULL, + NULL, G_PARAM_READABLE | G_PARAM_STATIC_NAME); + g_object_class_install_property (object_class, PROP_HOST_SECURITY_ID, pspec); } static void @@ -2085,6 +2180,7 @@ fwupd_client_finalize (GObject *object) g_free (priv->daemon_version); g_free (priv->host_product); g_free (priv->host_machine_id); + g_free (priv->host_security_id); if (priv->conn != NULL) g_object_unref (priv->conn); if (priv->proxy != NULL) diff --git a/libfwupd/fwupd-client.h b/libfwupd/fwupd-client.h index 18b3b6336..077e22092 100644 --- a/libfwupd/fwupd-client.h +++ b/libfwupd/fwupd-client.h @@ -95,6 +95,9 @@ FwupdDevice *fwupd_client_get_results (FwupdClient *client, const gchar *device_id, GCancellable *cancellable, GError **error); +GPtrArray *fwupd_client_get_host_security_attrs (FwupdClient *client, + GCancellable *cancellable, + GError **error); FwupdDevice *fwupd_client_get_device_by_id (FwupdClient *client, const gchar *device_id, GCancellable *cancellable, @@ -134,6 +137,7 @@ guint fwupd_client_get_percentage (FwupdClient *client); const gchar *fwupd_client_get_daemon_version (FwupdClient *client); const gchar *fwupd_client_get_host_product (FwupdClient *client); const gchar *fwupd_client_get_host_machine_id (FwupdClient *client); +const gchar *fwupd_client_get_host_security_id (FwupdClient *client); GPtrArray *fwupd_client_get_remotes (FwupdClient *client, GCancellable *cancellable, diff --git a/libfwupd/fwupd.map b/libfwupd/fwupd.map index 94266d11e..4fb789aee 100644 --- a/libfwupd/fwupd.map +++ b/libfwupd/fwupd.map @@ -449,6 +449,8 @@ LIBFWUPD_1.4.1 { LIBFWUPD_1.5.0 { global: + fwupd_client_get_host_security_attrs; + fwupd_client_get_host_security_id; fwupd_security_attr_add_flag; fwupd_security_attr_add_obsolete; fwupd_security_attr_array_from_variant; diff --git a/libfwupdplugin/fu-plugin-private.h b/libfwupdplugin/fu-plugin-private.h index 0544c0f92..a5e51cd59 100644 --- a/libfwupdplugin/fu-plugin-private.h +++ b/libfwupdplugin/fu-plugin-private.h @@ -116,6 +116,9 @@ gboolean fu_plugin_runner_clear_results (FuPlugin *self, gboolean fu_plugin_runner_get_results (FuPlugin *self, FuDevice *device, GError **error); +gboolean fu_plugin_runner_add_security_attrs (FuPlugin *self, + GPtrArray *attrs, + GError **error); gint fu_plugin_name_compare (FuPlugin *plugin1, FuPlugin *plugin2); gint fu_plugin_order_compare (FuPlugin *plugin1, diff --git a/libfwupdplugin/fu-plugin-vfuncs.h b/libfwupdplugin/fu-plugin-vfuncs.h index 0858d8b01..287cf2e44 100644 --- a/libfwupdplugin/fu-plugin-vfuncs.h +++ b/libfwupdplugin/fu-plugin-vfuncs.h @@ -359,3 +359,16 @@ gboolean fu_plugin_device_created (FuPlugin *plugin, **/ void fu_plugin_device_registered (FuPlugin *plugin, FuDevice *dev); +/** + * fu_plugin_add_security_attrs + * @plugin: A #FuPlugin + * @attrs: A #GPtrArray of #FwupdSecurityAttr + * @error: A #GError or NULL + * + * Function that asks plugins to add Host Security Attributes. + * + * Since: 1.5.0 + **/ +gboolean fu_plugin_add_security_attrs (FuPlugin *plugin, + GPtrArray *attrs, + GError **error); diff --git a/libfwupdplugin/fu-plugin.c b/libfwupdplugin/fu-plugin.c index 810e8243d..29dc02c0a 100644 --- a/libfwupdplugin/fu-plugin.c +++ b/libfwupdplugin/fu-plugin.c @@ -1579,6 +1579,26 @@ fu_plugin_runner_update_reload (FuPlugin *self, FuDevice *device, GError **error return fu_device_reload (device, error); } +/** + * fu_plugin_runner_add_security_attrs: + * @self: a #FuPlugin + * @attrs: (element-type FwupdSecurityAttr): a #GPtrArray of attributes + * @error: a #GError or NULL + * + * Runs the composite_prepare routine for the plugin + * + * Returns: #TRUE for success, #FALSE for failure + * + * Since: 1.5.0 + **/ +gboolean +fu_plugin_runner_add_security_attrs (FuPlugin *self, GPtrArray *attrs, GError **error) +{ + return fu_plugin_runner_device_array_generic (self, attrs, + "fu_plugin_add_security_attrs", + error); +} + /** * fu_plugin_add_udev_subsystem: * @self: a #FuPlugin diff --git a/libfwupdplugin/fwupdplugin.map b/libfwupdplugin/fwupdplugin.map index e1962298c..7401131c8 100644 --- a/libfwupdplugin/fwupdplugin.map +++ b/libfwupdplugin/fwupdplugin.map @@ -585,6 +585,7 @@ LIBFWUPDPLUGIN_1.5.0 { global: fu_common_filename_glob; fu_common_is_cpu_intel; + fu_plugin_runner_add_security_attrs; fu_plugin_runner_device_added; fu_udev_device_get_parent_name; fu_udev_device_get_sysfs_attr; diff --git a/src/fu-agent.c b/src/fu-agent.c index 634831cc8..9329ec81b 100644 --- a/src/fu-agent.c +++ b/src/fu-agent.c @@ -21,6 +21,7 @@ #include "fu-util-common.h" #include "fwupd-device-private.h" #include "fwupd-enums-private.h" +#include "fwupd-security-attr-private.h" struct FuUtilPrivate { GCancellable *cancellable; @@ -113,6 +114,37 @@ fu_util_add_updates_json (FuUtilPrivate *priv, JsonBuilder *builder, GError **er return TRUE; } +static gboolean +fu_util_add_security_attributes_json (FuUtilPrivate *priv, JsonBuilder *builder, GError **error) +{ + g_autoptr(GPtrArray) attrs = NULL; + + /* not ready yet */ + if ((priv->flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "The HSI specification is not yet complete. " + "To ignore this warning, use --force"); + return FALSE; + } + + /* get attrs from daemon */ + attrs = fwupd_client_get_host_security_attrs (priv->client, NULL, error); + if (attrs == NULL) + return FALSE; + json_builder_set_member_name (builder, "HostSecurityAttributes"); + json_builder_begin_array (builder); + for (guint i = 0; i < attrs->len; i++) { + FwupdSecurityAttr *attr = g_ptr_array_index (attrs, i); + json_builder_begin_object (builder); + fwupd_security_attr_to_json (attr, builder); + json_builder_end_object (builder); + } + json_builder_end_array (builder); + return TRUE; +} + static gboolean fu_util_get_devices (FuUtilPrivate *priv, gchar **values, GError **error) { @@ -199,6 +231,49 @@ fu_util_get_updates (FuUtilPrivate *priv, gchar **values, GError **error) return TRUE; } +static gboolean +fu_util_security (FuUtilPrivate *priv, gchar **values, GError **error) +{ + g_autofree gchar *data = NULL; + g_autoptr(JsonBuilder) builder = NULL; + g_autoptr(JsonGenerator) json_generator = NULL; + g_autoptr(JsonNode) json_root = NULL; + + /* check args */ + if (g_strv_length (values) != 0) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_ARGS, + "Invalid arguments"); + return FALSE; + } + + /* create header */ + builder = json_builder_new (); + json_builder_begin_object (builder); + if (!fu_util_add_security_attributes_json (priv, builder, error)) + return FALSE; + json_builder_end_object (builder); + + /* export as a string */ + json_root = json_builder_get_root (builder); + json_generator = json_generator_new (); + json_generator_set_pretty (json_generator, TRUE); + json_generator_set_root (json_generator, json_root); + data = json_generator_to_data (json_generator, NULL); + if (data == NULL) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "Failed to convert to JSON string"); + return FALSE; + } + + /* just print */ + g_print ("%s\n", data); + return TRUE; +} + static void fu_util_ignore_cb (const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data) @@ -280,6 +355,11 @@ main (int argc, char *argv[]) /* TRANSLATORS: command description */ _("Gets the list of updates for connected hardware"), fu_util_get_updates); + fu_util_cmd_array_add (cmd_array, + "security", NULL, + /* TRANSLATORS: command description */ + _("Gets the host security attributes"), + fu_util_security); /* sort by command name */ fu_util_cmd_array_sort (cmd_array); diff --git a/src/fu-engine.c b/src/fu-engine.c index c5f9dd073..c33935a47 100644 --- a/src/fu-engine.c +++ b/src/fu-engine.c @@ -49,6 +49,7 @@ #include "fu-plugin-private.h" #include "fu-quirks.h" #include "fu-remote-list.h" +#include "fu-security-attrs.h" #include "fu-smbios-private.h" #include "fu-udev-device-private.h" #include "fu-usb-device-private.h" @@ -99,6 +100,8 @@ struct _FuEngine gchar *host_machine_id; JcatContext *jcat_context; gboolean loaded; + gchar *host_security_id; + gboolean host_security_id_valid; }; enum { @@ -133,6 +136,7 @@ fu_engine_emit_changed (FuEngine *self) static void fu_engine_emit_device_changed (FuEngine *self, FuDevice *device) { + self->host_security_id_valid = FALSE; g_signal_emit (self, signals[SIGNAL_DEVICE_CHANGED], 0, device); } @@ -5026,6 +5030,56 @@ fu_engine_get_host_machine_id (FuEngine *self) return self->host_machine_id; } +GPtrArray * +fu_engine_get_host_security_attrs (FuEngine *self, GError **error) +{ + GPtrArray *plugins = fu_plugin_list_get_all (self->plugin_list); + g_autoptr(GPtrArray) attrs = NULL; + + attrs = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); + for (guint j = 0; j < plugins->len; j++) { + FuPlugin *plugin_tmp = g_ptr_array_index (plugins, j); + g_autoptr(GError) error_local = NULL; + if (!fu_plugin_runner_add_security_attrs (plugin_tmp, + attrs, + &error_local)) { + FwupdSecurityAttr *attr; + g_autofree gchar *appstream_id = NULL; + g_autofree gchar *msg = NULL; + appstream_id = g_strdup_printf ("org.fwupd.plugin.%s", + fu_plugin_get_name (plugin_tmp)); + msg = g_strdup_printf ("Failed to add HSI attribute: %s", + error_local->message); + attr = fwupd_security_attr_new (appstream_id); + fwupd_security_attr_set_name (attr, "fwupd"); + fwupd_security_attr_add_flag (attr, FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE); + fwupd_security_attr_set_result (attr, msg); + g_ptr_array_add (attrs, attr); + continue; + } + } + + /* set the obsoletes flag for each attr */ + fu_security_attrs_depsolve (attrs); + return g_steal_pointer (&attrs); +} + +const gchar * +fu_engine_get_host_security_id (FuEngine *self) +{ + g_return_val_if_fail (FU_IS_ENGINE (self), NULL); + + /* rebuild */ + if (!self->host_security_id_valid) { + g_autoptr(GPtrArray) attrs = fu_engine_get_host_security_attrs (self, NULL); + g_free (self->host_security_id); + self->host_security_id = fu_security_attrs_calculate_hsi (attrs); + self->host_security_id_valid = TRUE; + } + + return self->host_security_id; +} + gboolean fu_engine_load_plugins (FuEngine *self, GError **error) { @@ -5745,6 +5799,7 @@ fu_engine_finalize (GObject *obj) g_source_remove (self->coldplug_id); g_free (self->host_machine_id); + g_free (self->host_security_id); g_object_unref (self->idle); g_object_unref (self->config); g_object_unref (self->remote_list); diff --git a/src/fu-engine.h b/src/fu-engine.h index 1829ee18c..028d778d2 100644 --- a/src/fu-engine.h +++ b/src/fu-engine.h @@ -49,6 +49,7 @@ gboolean fu_engine_load_plugins (FuEngine *self, gboolean fu_engine_get_tainted (FuEngine *self); const gchar *fu_engine_get_host_product (FuEngine *self); const gchar *fu_engine_get_host_machine_id (FuEngine *self); +const gchar *fu_engine_get_host_security_id (FuEngine *self); FwupdStatus fu_engine_get_status (FuEngine *self); XbSilo *fu_engine_get_silo_from_blob (FuEngine *self, GBytes *blob_cab, @@ -82,6 +83,8 @@ GPtrArray *fu_engine_get_upgrades (FuEngine *self, FwupdDevice *fu_engine_get_results (FuEngine *self, const gchar *device_id, GError **error); +GPtrArray *fu_engine_get_host_security_attrs (FuEngine *self, + GError **error); gboolean fu_engine_clear_results (FuEngine *self, const gchar *device_id, GError **error); diff --git a/src/fu-main.c b/src/fu-main.c index 8ff196e4c..c8d2b055d 100644 --- a/src/fu-main.c +++ b/src/fu-main.c @@ -20,6 +20,7 @@ #include #include "fwupd-device-private.h" +#include "fwupd-security-attr-private.h" #include "fwupd-release-private.h" #include "fwupd-remote-private.h" #include "fwupd-resources.h" @@ -254,6 +255,22 @@ fu_main_device_array_to_variant (FuMainPrivate *priv, const gchar *sender, return g_variant_new ("(aa{sv})", &builder); } +static GVariant * +fu_main_security_attr_array_to_variant (FuMainPrivate *priv, GPtrArray *attrs) +{ + GVariantBuilder builder; + + g_return_val_if_fail (attrs->len > 0, NULL); + g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY); + + for (guint i = 0; i < attrs->len; i++) { + FwupdSecurityAttr *security_attr = g_ptr_array_index (attrs, i); + GVariant *tmp = fwupd_security_attr_to_variant (security_attr); + g_variant_builder_add_value (&builder, tmp); + } + return g_variant_new ("(aa{sv})", &builder); +} + static GVariant * fu_main_release_array_to_variant (GPtrArray *results) { @@ -987,6 +1004,18 @@ fu_main_daemon_method_call (GDBusConnection *connection, const gchar *sender, g_dbus_method_invocation_return_value (invocation, val); return; } + if (g_strcmp0 (method_name, "GetHostSecurityAttrs") == 0) { + g_autoptr(GPtrArray) attrs = NULL; + g_debug ("Called %s()", method_name); + attrs = fu_engine_get_host_security_attrs (priv->engine, &error); + if (attrs == NULL) { + g_dbus_method_invocation_return_gerror (invocation, error); + return; + } + val = fu_main_security_attr_array_to_variant (priv, attrs); + g_dbus_method_invocation_return_value (invocation, val); + return; + } if (g_strcmp0 (method_name, "ClearResults") == 0) { const gchar *device_id; g_variant_get (parameters, "(&s)", &device_id); @@ -1388,6 +1417,9 @@ fu_main_daemon_get_property (GDBusConnection *connection_, const gchar *sender, if (g_strcmp0 (property_name, "HostMachineId") == 0) return g_variant_new_string (fu_engine_get_host_machine_id (priv->engine)); + if (g_strcmp0 (property_name, "HostSecurityId") == 0) + return g_variant_new_string (fu_engine_get_host_security_id (priv->engine)); + if (g_strcmp0 (property_name, "Interactive") == 0) return g_variant_new_boolean (isatty (fileno (stdout)) != 0); diff --git a/src/fu-security-attrs.c b/src/fu-security-attrs.c new file mode 100644 index 000000000..ff862f8cd --- /dev/null +++ b/src/fu-security-attrs.c @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2020 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include + +#include "fwupd-security-attr.h" + +#include "fu-security-attrs.h" + +gchar * +fu_security_attrs_calculate_hsi (GPtrArray *attrs) +{ + guint hsi_number = 0; + FwupdSecurityAttrFlags flags = FWUPD_SECURITY_ATTR_FLAG_NONE; + GString *str = g_string_new ("HSI:"); + const FwupdSecurityAttrFlags hpi_suffixes[] = { + FWUPD_SECURITY_ATTR_FLAG_RUNTIME_UPDATES, + FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ATTESTATION, + FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE, + FWUPD_SECURITY_ATTR_FLAG_NONE, + }; + + /* find the highest HSI number where there are no failures and at least + * one success */ + for (guint j = 1; j <= FWUPD_SECURITY_ATTR_LEVEL_LAST; j++) { + gboolean success_cnt = 0; + gboolean failure_cnt = 0; + for (guint i = 0; i < attrs->len; i++) { + FwupdSecurityAttr *attr = g_ptr_array_index (attrs, i); + if (fwupd_security_attr_get_level (attr) != j) + continue; + if (fwupd_security_attr_has_flag (attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS)) + success_cnt++; + else if (!fwupd_security_attr_has_flag (attr, FWUPD_SECURITY_ATTR_FLAG_OBSOLETED)) + failure_cnt++; + } + + /* abort */ + if (failure_cnt > 0) { + hsi_number = j - 1; + break; + } + + /* we matched at least one thing on this level */ + if (success_cnt > 0) + hsi_number = j; + } + + /* get a logical OR of the runtime flags */ + for (guint i = 0; i < attrs->len; i++) { + FwupdSecurityAttr *attr = g_ptr_array_index (attrs, i); + if (fwupd_security_attr_has_flag (attr, FWUPD_SECURITY_ATTR_FLAG_OBSOLETED)) + continue; + /* positive things */ + if (fwupd_security_attr_has_flag (attr, FWUPD_SECURITY_ATTR_FLAG_RUNTIME_UPDATES) || + fwupd_security_attr_has_flag (attr, FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ATTESTATION)) { + if (!fwupd_security_attr_has_flag (attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS)) + continue; + } + /* negative things */ + if (fwupd_security_attr_has_flag (attr, FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE)) { + if (fwupd_security_attr_has_flag (attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS)) + continue; + } + flags |= fwupd_security_attr_get_flags (attr); + } + + g_string_append_printf (str, "%u", hsi_number); + if (flags & (FWUPD_SECURITY_ATTR_FLAG_RUNTIME_UPDATES | + FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ATTESTATION | + FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE)) { + g_string_append (str, "+"); + for (guint j = 0; hpi_suffixes[j] != FWUPD_SECURITY_ATTR_FLAG_NONE; j++) { + if (flags & hpi_suffixes[j]) + g_string_append (str, fwupd_security_attr_flag_to_suffix (hpi_suffixes[j])); + } + } + return g_string_free (str, FALSE); +} + +void +fu_security_attrs_depsolve (GPtrArray *attrs) +{ + g_autoptr(GHashTable) attrs_by_id = NULL; + + /* make hash of ID -> object */ + attrs_by_id = g_hash_table_new (g_str_hash, g_str_equal); + for (guint i = 0; i < attrs->len; i++) { + FwupdSecurityAttr *attr = g_ptr_array_index (attrs, i); + g_hash_table_insert (attrs_by_id, + (gpointer) fwupd_security_attr_get_appstream_id (attr), + (gpointer) attr); + } + + /* set flat where required */ + for (guint i = 0; i < attrs->len; i++) { + FwupdSecurityAttr *attr = g_ptr_array_index (attrs, i); + GPtrArray *obsoletes = fwupd_security_attr_get_obsoletes (attr); + for (guint j = 0; j < obsoletes->len; j++) { + const gchar *obsolete = g_ptr_array_index (obsoletes, j); + FwupdSecurityAttr *attr_tmp = g_hash_table_lookup (attrs_by_id, obsolete); + if (attr_tmp != NULL) { + g_debug ("security attr %s obsoleted by %s", obsolete, + fwupd_security_attr_get_appstream_id (attr)); + fwupd_security_attr_add_flag (attr_tmp, + FWUPD_SECURITY_ATTR_FLAG_OBSOLETED); + } + } + } +} diff --git a/src/fu-security-attrs.h b/src/fu-security-attrs.h new file mode 100644 index 000000000..a0ab3ec18 --- /dev/null +++ b/src/fu-security-attrs.h @@ -0,0 +1,12 @@ +/* + * Copyright (C) 2020 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include + +gchar *fu_security_attrs_calculate_hsi (GPtrArray *attrs); +void fu_security_attrs_depsolve (GPtrArray *attrs); diff --git a/src/fu-self-test.c b/src/fu-self-test.c index f3cbe26ac..29c104de7 100644 --- a/src/fu-self-test.c +++ b/src/fu-self-test.c @@ -25,6 +25,7 @@ #include "fu-plugin-list.h" #include "fu-progressbar.h" #include "fu-hash.h" +#include "fu-security-attrs.h" #include "fu-smbios-private.h" typedef struct { @@ -151,6 +152,75 @@ fu_engine_generate_md_func (gconstpointer user_data) g_assert_cmpstr (tmp, ==, NULL); } +static void +fu_plugin_hsi_func (gconstpointer user_data) +{ + FwupdSecurityAttr *attr; + g_autofree gchar *hsi1 = NULL; + g_autofree gchar *hsi2 = NULL; + g_autofree gchar *hsi3 = NULL; + g_autofree gchar *hsi4 = NULL; + g_autofree gchar *hsi5 = NULL; + g_autofree gchar *hsi6 = NULL; + g_autofree gchar *hsi7 = NULL; + g_autoptr(FuEngine) engine = fu_engine_new (FU_APP_FLAGS_NONE); + g_autoptr(GPtrArray) attrs = NULL; + + /* no attrs */ + attrs = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); + hsi1 = fu_security_attrs_calculate_hsi (attrs); + g_assert_cmpstr (hsi1, ==, "HSI:0"); + + /* just success from HSI:1 */ + attr = fwupd_security_attr_new ("org.fwupd.Hsi.BIOSWE"); + fwupd_security_attr_set_level (attr, 1); + fwupd_security_attr_add_flag (attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); + g_ptr_array_add (attrs, attr); + hsi2 = fu_security_attrs_calculate_hsi (attrs); + g_assert_cmpstr (hsi2, ==, "HSI:1"); + + /* add failed from HSI:2, so still HSI:1 */ + attr = fwupd_security_attr_new ("org.fwupd.Hsi.PRX"); + fwupd_security_attr_set_level (attr, 2); + g_ptr_array_add (attrs, attr); + hsi3 = fu_security_attrs_calculate_hsi (attrs); + g_assert_cmpstr (hsi3, ==, "HSI:1"); + + /* add attr from HSI:3, obsoleting the failure */ + attr = fwupd_security_attr_new ("org.fwupd.Hsi.BIOSGuard"); + fwupd_security_attr_set_level (attr, 3); + fwupd_security_attr_add_flag (attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); + fwupd_security_attr_add_obsolete (attr, "org.fwupd.Hsi.PRX"); + g_ptr_array_add (attrs, attr); + fu_security_attrs_depsolve (attrs); + hsi4 = fu_security_attrs_calculate_hsi (attrs); + g_assert_cmpstr (hsi4, ==, "HSI:3"); + + /* add taint that was fine */ + attr = fwupd_security_attr_new ("org.fwupd.Hsi.PluginsTainted"); + fwupd_security_attr_add_flag (attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); + fwupd_security_attr_add_flag (attr, FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE); + g_ptr_array_add (attrs, attr); + hsi5 = fu_security_attrs_calculate_hsi (attrs); + g_assert_cmpstr (hsi5, ==, "HSI:3"); + + /* add updates and attestation */ + attr = fwupd_security_attr_new ("org.fwupd.Hsi.LVFS"); + fwupd_security_attr_add_flag (attr, FWUPD_SECURITY_ATTR_FLAG_RUNTIME_UPDATES); + fwupd_security_attr_add_flag (attr, FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ATTESTATION); + fwupd_security_attr_add_flag (attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); + g_ptr_array_add (attrs, attr); + hsi6 = fu_security_attrs_calculate_hsi (attrs); + g_assert_cmpstr (hsi6, ==, "HSI:3+UA"); + + /* add issue that was uncool */ + attr = fwupd_security_attr_new ("org.fwupd.Hsi.Swap"); + fwupd_security_attr_add_flag (attr, FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE); + g_ptr_array_add (attrs, attr); + hsi7 = fu_security_attrs_calculate_hsi (attrs); + g_assert_cmpstr (hsi7, ==, "HSI:3+UA!"); +} + static void fu_plugin_hash_func (gconstpointer user_data) { @@ -2980,6 +3050,8 @@ main (int argc, char **argv) g_test_add_data_func ("/fwupd/progressbar", self, fu_progressbar_func); } + g_test_add_data_func ("/fwupd/plugin{hsi}", self, + fu_plugin_hsi_func); g_test_add_data_func ("/fwupd/plugin{build-hash}", self, fu_plugin_hash_func); g_test_add_data_func ("/fwupd/plugin{module}", self, diff --git a/src/fu-tool.c b/src/fu-tool.c index 268c8d5c5..52ed222f0 100644 --- a/src/fu-tool.c +++ b/src/fu-tool.c @@ -1937,6 +1937,38 @@ fu_util_get_remotes (FuUtilPrivate *priv, gchar **values, GError **error) return TRUE; } +static gboolean +fu_util_security (FuUtilPrivate *priv, gchar **values, GError **error) +{ + g_autoptr(GPtrArray) attrs = NULL; + g_autofree gchar *str = NULL; + + /* not ready yet */ + if ((priv->flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "The HSI specification is not yet complete. " + "To ignore this warning, use --force"); + return FALSE; + } + + if (!fu_util_start_engine (priv, FU_ENGINE_LOAD_FLAG_NONE, error)) + return FALSE; + + /* TRANSLATORS: this is a string like 'HSI:2-U' */ + g_print ("%s \033[1m%s\033[0m\n", _("Host Security ID:"), + fu_engine_get_host_security_id (priv->engine)); + + /* print the "why" */ + attrs = fu_engine_get_host_security_attrs (priv->engine, error); + if (attrs == NULL) + return FALSE; + str = fu_util_security_attrs_to_string (attrs); + g_print ("%s\n", str); + return TRUE; +} + int main (int argc, char *argv[]) { @@ -2176,6 +2208,12 @@ main (int argc, char *argv[]) /* TRANSLATORS: command description */ _("Refresh metadata from remote server"), fu_util_refresh); + fu_util_cmd_array_add (cmd_array, + "security", + NULL, + /* TRANSLATORS: command description */ + _("Gets the host security attributes."), + fu_util_security); /* do stuff on ctrl+c */ priv->cancellable = g_cancellable_new (); diff --git a/src/fu-util-common.c b/src/fu-util-common.c index abb282687..1eb128cea 100644 --- a/src/fu-util-common.c +++ b/src/fu-util-common.c @@ -16,6 +16,7 @@ #include "fu-common.h" #include "fu-util-common.h" #include "fu-device.h" +#include "fu-security-attrs.h" #ifdef HAVE_SYSTEMD #include "fu-systemd.h" @@ -1547,3 +1548,71 @@ fu_util_remote_to_string (FwupdRemote *remote, guint idt) return g_string_free (str, FALSE); } + +static void +fu_security_attr_append_str (FwupdSecurityAttr *attr, GString *str) +{ + if (fwupd_security_attr_has_flag (attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS)) { + g_string_append_printf (str, "\033[32m✔\033[0m "); + } else { + g_string_append_printf (str, "\033[31m✘\033[0m "); + } + g_string_append_printf (str, "%s", fwupd_security_attr_get_name (attr)); + if (fwupd_security_attr_get_result (attr) != NULL) { + g_string_append_printf (str, ": %s", + fwupd_security_attr_get_result (attr)); + } else { + g_string_append_printf (str, ": %s", + fwupd_security_attr_has_flag (attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS) + ? "OK" : "Failed"); + } + if (fwupd_security_attr_has_flag (attr, FWUPD_SECURITY_ATTR_FLAG_OBSOLETED)) + g_string_append (str, " (obsoleted)"); + g_string_append_printf (str, "\n"); +} + +gchar * +fu_util_security_attrs_to_string (GPtrArray *attrs) +{ + FwupdSecurityAttrFlags flags = FWUPD_SECURITY_ATTR_FLAG_NONE; + const FwupdSecurityAttrFlags hpi_suffixes[] = { + FWUPD_SECURITY_ATTR_FLAG_RUNTIME_UPDATES, + FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ATTESTATION, + FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE, + FWUPD_SECURITY_ATTR_FLAG_NONE, + }; + GString *str = g_string_new (NULL); + + for (guint j = 1; j <= FWUPD_SECURITY_ATTR_LEVEL_LAST; j++) { + gboolean has_header = FALSE; + for (guint i = 0; i < attrs->len; i++) { + FwupdSecurityAttr *attr = g_ptr_array_index (attrs, i); + if (fwupd_security_attr_get_level (attr) != j) + continue; + if (!has_header) { + g_string_append_printf (str, "\n\033[1mHSI-%u\033[0m\n", j); + has_header = TRUE; + } + fu_security_attr_append_str (attr, str); + } + } + for (guint i = 0; i < attrs->len; i++) { + FwupdSecurityAttr *attr = g_ptr_array_index (attrs, i); + flags |= fwupd_security_attr_get_flags (attr); + } + for (guint j = 0; hpi_suffixes[j] != FWUPD_SECURITY_ATTR_FLAG_NONE; j++) { + if (flags & hpi_suffixes[j]) { + g_string_append_printf (str, "\n\033[1m%s -%s\033[0m\n", + /* TRANSLATORS: this is the HSI suffix */ + _("Runtime Suffix"), + fwupd_security_attr_flag_to_suffix (hpi_suffixes[j])); + for (guint i = 0; i < attrs->len; i++) { + FwupdSecurityAttr *attr = g_ptr_array_index (attrs, i); + if (!fwupd_security_attr_has_flag (attr, hpi_suffixes[j])) + continue; + fu_security_attr_append_str (attr, str); + } + } + } + return g_string_free (str, FALSE); +} diff --git a/src/fu-util-common.h b/src/fu-util-common.h index 9013d159d..77ca43133 100644 --- a/src/fu-util-common.h +++ b/src/fu-util-common.h @@ -77,3 +77,4 @@ gchar *fu_util_release_to_string (FwupdRelease *rel, guint idt); gchar *fu_util_remote_to_string (FwupdRemote *remote, guint idt); +gchar *fu_util_security_attrs_to_string (GPtrArray *attrs); diff --git a/src/fu-util.c b/src/fu-util.c index da8d961ed..aa2bbde0f 100644 --- a/src/fu-util.c +++ b/src/fu-util.c @@ -26,6 +26,7 @@ #include "fu-history.h" #include "fu-plugin-private.h" #include "fu-progressbar.h" +#include "fu-security-attrs.h" #include "fu-util-common.h" #include "fwupd-common-private.h" @@ -2364,6 +2365,37 @@ fu_util_modify_config (FuUtilPrivate *priv, gchar **values, GError **error) return TRUE; } +static gboolean +fu_util_security (FuUtilPrivate *priv, gchar **values, GError **error) +{ + g_autoptr(GPtrArray) attrs = NULL; + g_autofree gchar *str = NULL; + + /* not ready yet */ + if ((priv->flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "The HSI specification is not yet complete. " + "To ignore this warning, use --force"); + return FALSE; + } + + /* TRANSLATORS: this is a string like 'HSI:2-U' */ + g_print ("%s \033[1m%s\033[0m\n", _("Host Security ID:"), + fwupd_client_get_host_security_id (priv->client)); + + /* print the "why" */ + attrs = fwupd_client_get_host_security_attrs (priv->client, + priv->cancellable, + error); + if (attrs == NULL) + return FALSE; + str = fu_util_security_attrs_to_string (attrs); + g_print ("%s\n", str); + return TRUE; +} + static void fu_util_ignore_cb (const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data) @@ -2678,7 +2710,12 @@ main (int argc, char *argv[]) /* TRANSLATORS: command description */ _("Reinstall current firmware on the device."), fu_util_reinstall); - + fu_util_cmd_array_add (cmd_array, + "security", + NULL, + /* TRANSLATORS: command description */ + _("Gets the host security attributes."), + fu_util_security); /* do stuff on ctrl+c */ priv->cancellable = g_cancellable_new (); diff --git a/src/meson.build b/src/meson.build index aae719fef..95bdc5967 100644 --- a/src/meson.build +++ b/src/meson.build @@ -131,6 +131,7 @@ fwupdtool = executable( 'fu-plugin-list.c', 'fu-progressbar.c', 'fu-remote-list.c', + 'fu-security-attrs.c', 'fu-util-common.c', systemd_src ], @@ -229,6 +230,7 @@ executable( 'fu-main.c', 'fu-plugin-list.c', 'fu-remote-list.c', + 'fu-security-attrs.c', systemd_src ], include_directories : [ @@ -285,6 +287,7 @@ if get_option('tests') 'fu-plugin-list.c', 'fu-progressbar.c', 'fu-remote-list.c', + 'fu-security-attrs.c', 'fu-self-test.c', systemd_src ], diff --git a/src/org.freedesktop.fwupd.xml b/src/org.freedesktop.fwupd.xml index 23cef8704..ea33f626f 100644 --- a/src/org.freedesktop.fwupd.xml +++ b/src/org.freedesktop.fwupd.xml @@ -44,6 +44,17 @@ + + + + + + The Host Security ID, for instance HSI:2UA + + + + + @@ -242,6 +253,24 @@ + + + + + + Gets a list of all the Host Security ID attributes. + + + + + + + An array of HSI attributes, with any properties set on each. + + + + +