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. + + + + +