diff --git a/libfwupd/fwupd-device.c b/libfwupd/fwupd-device.c index 03ab314ce..135f48e35 100644 --- a/libfwupd/fwupd-device.c +++ b/libfwupd/fwupd-device.c @@ -39,6 +39,7 @@ typedef struct { GPtrArray *protocols; GPtrArray *instance_ids; GPtrArray *icons; + GPtrArray *issues; /* of utf-8 */ gchar *name; gchar *serial; gchar *summary; @@ -128,6 +129,47 @@ fwupd_device_add_checksum(FwupdDevice *self, const gchar *checksum) g_ptr_array_add(priv->checksums, g_strdup(checksum)); } +/** + * fwupd_device_get_issues: + * @self: a #FwupdDevice + * + * Gets the list of issues currently affecting this device. + * + * Returns: (element-type utf8) (transfer none): the issues, which may be empty + * + * Since: 1.7.6 + **/ +GPtrArray * +fwupd_device_get_issues(FwupdDevice *self) +{ + FwupdDevicePrivate *priv = GET_PRIVATE(self); + g_return_val_if_fail(FWUPD_IS_DEVICE(self), NULL); + return priv->issues; +} + +/** + * fwupd_device_add_issue: + * @self: a #FwupdDevice + * @issue: (not nullable): the update issue, e.g. `CVE-2019-12345` + * + * Adds an current issue to this device. + * + * Since: 1.7.6 + **/ +void +fwupd_device_add_issue(FwupdDevice *self, const gchar *issue) +{ + FwupdDevicePrivate *priv = GET_PRIVATE(self); + g_return_if_fail(FWUPD_IS_DEVICE(self)); + g_return_if_fail(issue != NULL); + for (guint i = 0; i < priv->issues->len; i++) { + const gchar *issue_tmp = g_ptr_array_index(priv->issues, i); + if (g_strcmp0(issue_tmp, issue) == 0) + return; + } + g_ptr_array_add(priv->issues, g_strdup(issue)); +} + /** * fwupd_device_get_children: * @self: a #FwupdDevice @@ -1841,6 +1883,15 @@ fwupd_device_to_variant_full(FwupdDevice *self, FwupdDeviceFlags flags) FWUPD_RESULT_KEY_PROTOCOL, g_variant_new_string(str->str)); } + if (priv->issues->len > 0) { + g_autofree const gchar **strv = g_new0(const gchar *, priv->issues->len + 1); + for (guint i = 0; i < priv->issues->len; i++) + strv[i] = (const gchar *)g_ptr_array_index(priv->issues, i); + g_variant_builder_add(&builder, + "{sv}", + FWUPD_RESULT_KEY_ISSUES, + g_variant_new_strv(strv, -1)); + } if (priv->version != NULL) { g_variant_builder_add(&builder, "{sv}", @@ -2086,6 +2137,12 @@ fwupd_device_from_key_value(FwupdDevice *self, const gchar *key, GVariant *value fwupd_device_add_protocol(self, protocols[i]); return; } + if (g_strcmp0(key, FWUPD_RESULT_KEY_ISSUES) == 0) { + g_autofree const gchar **strv = g_variant_get_strv(value, NULL); + for (guint i = 0; strv[i] != NULL; i++) + fwupd_device_add_issue(self, strv[i]); + return; + } if (g_strcmp0(key, FWUPD_RESULT_KEY_VERSION) == 0) { fwupd_device_set_version(self, g_variant_get_string(value, NULL)); return; @@ -2616,6 +2673,15 @@ fwupd_device_to_json(FwupdDevice *self, JsonBuilder *builder) } json_builder_end_array(builder); } + if (priv->issues->len > 0) { + json_builder_set_member_name(builder, FWUPD_RESULT_KEY_ISSUES); + json_builder_begin_array(builder); + for (guint i = 0; i < priv->issues->len; i++) { + const gchar *tmp = g_ptr_array_index(priv->issues, i); + json_builder_add_string_value(builder, tmp); + } + json_builder_end_array(builder); + } if (priv->flags != FWUPD_DEVICE_FLAG_NONE) { json_builder_set_member_name(builder, FWUPD_RESULT_KEY_FLAGS); json_builder_begin_array(builder); @@ -2821,6 +2887,10 @@ fwupd_device_to_string(FwupdDevice *self) const gchar *tmp = g_ptr_array_index(priv->protocols, i); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_PROTOCOL, tmp); } + for (guint i = 0; i < priv->issues->len; i++) { + const gchar *tmp = g_ptr_array_index(priv->issues, i); + fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_ISSUES, tmp); + } fwupd_pad_kv_dfl(str, FWUPD_RESULT_KEY_FLAGS, priv->flags); for (guint i = 0; i < priv->checksums->len; i++) { const gchar *checksum = g_ptr_array_index(priv->checksums, i); @@ -3116,6 +3186,7 @@ fwupd_device_init(FwupdDevice *self) priv->checksums = g_ptr_array_new_with_free_func(g_free); priv->vendor_ids = g_ptr_array_new_with_free_func(g_free); priv->protocols = g_ptr_array_new_with_free_func(g_free); + priv->issues = g_ptr_array_new_with_free_func(g_free); priv->children = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); priv->releases = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); } @@ -3159,6 +3230,7 @@ fwupd_device_finalize(GObject *object) g_ptr_array_unref(priv->checksums); g_ptr_array_unref(priv->children); g_ptr_array_unref(priv->releases); + g_ptr_array_unref(priv->issues); G_OBJECT_CLASS(fwupd_device_parent_class)->finalize(object); } diff --git a/libfwupd/fwupd-device.h b/libfwupd/fwupd-device.h index 3d63e5253..2ce621fbe 100644 --- a/libfwupd/fwupd-device.h +++ b/libfwupd/fwupd-device.h @@ -191,6 +191,10 @@ gboolean fwupd_device_has_icon(FwupdDevice *self, const gchar *icon); GPtrArray * fwupd_device_get_icons(FwupdDevice *self); +GPtrArray * +fwupd_device_get_issues(FwupdDevice *self); +void +fwupd_device_add_issue(FwupdDevice *self, const gchar *issue); FwupdUpdateState fwupd_device_get_update_state(FwupdDevice *self); diff --git a/libfwupd/fwupd.map b/libfwupd/fwupd.map index 67294f74d..026b21a3b 100644 --- a/libfwupd/fwupd.map +++ b/libfwupd/fwupd.map @@ -748,3 +748,10 @@ LIBFWUPD_1.7.4 { fwupd_device_get_root; local: *; } LIBFWUPD_1.7.3; + +LIBFWUPD_1.7.6 { + global: + fwupd_device_add_issue; + fwupd_device_get_issues; + local: *; +} LIBFWUPD_1.7.4; diff --git a/libfwupdplugin/fu-device.c b/libfwupdplugin/fu-device.c index 70b4640a6..c4bd57a71 100644 --- a/libfwupdplugin/fu-device.c +++ b/libfwupdplugin/fu-device.c @@ -1545,6 +1545,12 @@ fu_device_set_quirk_kv(FuDevice *self, const gchar *key, const gchar *value, GEr fu_device_add_protocol(self, sections[i]); return TRUE; } + if (g_strcmp0(key, FU_QUIRKS_ISSUE) == 0) { + g_auto(GStrv) sections = g_strsplit(value, ",", -1); + for (guint i = 0; sections[i] != NULL; i++) + fu_device_add_issue(self, sections[i]); + return TRUE; + } if (g_strcmp0(key, FU_QUIRKS_VERSION) == 0) { fu_device_set_version(self, value); return TRUE; diff --git a/libfwupdplugin/fu-device.h b/libfwupdplugin/fu-device.h index 3d6c0215d..406f2d5b3 100644 --- a/libfwupdplugin/fu-device.h +++ b/libfwupdplugin/fu-device.h @@ -148,6 +148,7 @@ fu_device_new_with_context(FuContext *ctx); #define fu_device_add_release(d, v) fwupd_device_add_release(FWUPD_DEVICE(d), v) #define fu_device_add_icon(d, v) fwupd_device_add_icon(FWUPD_DEVICE(d), v) #define fu_device_has_icon(d, v) fwupd_device_has_icon(FWUPD_DEVICE(d), v) +#define fu_device_add_issue(d, v) fwupd_device_add_issue(FWUPD_DEVICE(d), v) #define fu_device_set_created(d, v) fwupd_device_set_created(FWUPD_DEVICE(d), v) #define fu_device_set_description(d, v) fwupd_device_set_description(FWUPD_DEVICE(d), v) #define fu_device_set_flags(d, v) fwupd_device_set_flags(FWUPD_DEVICE(d), v) @@ -178,6 +179,7 @@ fu_device_new_with_context(FuContext *ctx); #define fu_device_get_guid_default(d) fwupd_device_get_guid_default(FWUPD_DEVICE(d)) #define fu_device_get_instance_ids(d) fwupd_device_get_instance_ids(FWUPD_DEVICE(d)) #define fu_device_get_icons(d) fwupd_device_get_icons(FWUPD_DEVICE(d)) +#define fu_device_get_issues(d) fwupd_device_get_issues(FWUPD_DEVICE(d)) #define fu_device_get_name(d) fwupd_device_get_name(FWUPD_DEVICE(d)) #define fu_device_get_serial(d) fwupd_device_get_serial(FWUPD_DEVICE(d)) #define fu_device_get_summary(d) fwupd_device_get_summary(FWUPD_DEVICE(d)) diff --git a/libfwupdplugin/fu-quirks.h b/libfwupdplugin/fu-quirks.h index f32c8fa3a..7019a6640 100644 --- a/libfwupdplugin/fu-quirks.h +++ b/libfwupdplugin/fu-quirks.h @@ -284,3 +284,11 @@ fu_quirks_add_possible_key(FuQuirks *self, const gchar *possible_key); * Since: 1.6.2 **/ #define FU_QUIRKS_INHIBIT "Inhibit" +/** + * FU_QUIRKS_ISSUE: + * + * The quirk key to add security issues affecting a specific device. + * + * Since: 1.7.6 + **/ +#define FU_QUIRKS_ISSUE "Issue" diff --git a/src/fu-tool.c b/src/fu-tool.c index ce2ae676e..34622aa39 100644 --- a/src/fu-tool.c +++ b/src/fu-tool.c @@ -2792,6 +2792,7 @@ fu_util_security(FuUtilPrivate *priv, gchar **values, GError **error) FuSecurityAttrToStringFlags flags = FU_SECURITY_ATTR_TO_STRING_FLAG_NONE; g_autoptr(FuSecurityAttrs) attrs = NULL; g_autoptr(FuSecurityAttrs) events = NULL; + g_autoptr(GPtrArray) devices = NULL; g_autoptr(GPtrArray) items = NULL; g_autoptr(GPtrArray) events_array = NULL; g_autofree gchar *str = NULL; @@ -2847,6 +2848,16 @@ fu_util_security(FuUtilPrivate *priv, gchar **values, GError **error) g_print("%s\n", estr); } + /* print the "also" */ + devices = fu_engine_get_devices(priv->engine, error); + if (devices == NULL) + return FALSE; + if (devices->len > 0) { + g_autofree gchar *estr = fu_util_security_issues_to_string(devices); + if (estr != NULL) + g_print("%s\n", estr); + } + /* success */ return TRUE; } diff --git a/src/fu-util-common.c b/src/fu-util-common.c index 92dbe6723..aa69de536 100644 --- a/src/fu-util-common.c +++ b/src/fu-util-common.c @@ -1293,6 +1293,7 @@ fu_util_device_to_string(FwupdDevice *dev, guint idt) { FwupdUpdateState state; GPtrArray *guids = fwupd_device_get_guids(dev); + GPtrArray *issues = fwupd_device_get_issues(dev); GPtrArray *vendor_ids = fwupd_device_get_vendor_ids(dev); GPtrArray *instance_ids = fwupd_device_get_instance_ids(dev); const gchar *tmp; @@ -1506,6 +1507,14 @@ fu_util_device_to_string(FwupdDevice *dev, guint idt) fu_common_string_append_kv(str, idt + 1, "", bullet); } } + for (guint i = 0; i < issues->len; i++) { + const gchar *issue = g_ptr_array_index(issues, i); + fu_common_string_append_kv(str, + idt + 1, + /* TRANSLATORS: issue fixed with the release, e.g. CVE */ + i == 0 ? ngettext("Issue", "Issues", issues->len) : "", + issue); + } return g_string_free(g_steal_pointer(&str), FALSE); } @@ -2252,6 +2261,41 @@ fu_util_security_events_to_string(GPtrArray *events, FuSecurityAttrToStringFlags return g_string_free(g_steal_pointer(&str), FALSE); } +gchar * +fu_util_security_issues_to_string(GPtrArray *devices) +{ + g_autoptr(GString) str = g_string_new(NULL); + + for (guint i = 0; i < devices->len; i++) { + FwupdDevice *device = g_ptr_array_index(devices, i); + GPtrArray *issues = fwupd_device_get_issues(device); + if (issues->len == 0) + continue; + if (str->len == 0) { + g_string_append_printf( + str, + "%s\n", + /* TRANSLATORS: now list devices with unfixed high-priority issues */ + _("There are devices with issues:")); + } + g_string_append_printf(str, + "\n %s — %s:\n", + fwupd_device_get_vendor(device), + fwupd_device_get_name(device)); + for (guint j = 0; j < issues->len; j++) { + const gchar *issue = g_ptr_array_index(issues, j); + g_string_append_printf(str, " • %s\n", issue); + } + } + + /* no output required */ + if (str->len == 0) + return NULL; + + /* success */ + return g_string_free(g_steal_pointer(&str), FALSE); +} + gchar * fu_util_security_attrs_to_string(GPtrArray *attrs, FuSecurityAttrToStringFlags strflags) { diff --git a/src/fu-util-common.h b/src/fu-util-common.h index 42e632c0d..414c2d33a 100644 --- a/src/fu-util-common.h +++ b/src/fu-util-common.h @@ -125,6 +125,8 @@ gchar * fu_util_security_attrs_to_string(GPtrArray *attrs, FuSecurityAttrToStringFlags flags); gchar * fu_util_security_events_to_string(GPtrArray *events, FuSecurityAttrToStringFlags flags); +gchar * +fu_util_security_issues_to_string(GPtrArray *devices); gboolean fu_util_send_report(FwupdClient *client, const gchar *report_uri, diff --git a/src/fu-util.c b/src/fu-util.c index 781483d75..e393ccdba 100644 --- a/src/fu-util.c +++ b/src/fu-util.c @@ -3206,9 +3206,15 @@ fu_util_upload_security(FuUtilPrivate *priv, GPtrArray *attrs, GError **error) } static gboolean -fu_util_security_as_json(FuUtilPrivate *priv, GPtrArray *attrs, GPtrArray *events, GError **error) +fu_util_security_as_json(FuUtilPrivate *priv, + GPtrArray *attrs, + GPtrArray *events, + GPtrArray *devices, + GError **error) { + g_autoptr(GPtrArray) devices_issues = NULL; g_autoptr(JsonBuilder) builder = json_builder_new(); + json_builder_begin_object(builder); /* attrs */ @@ -3235,6 +3241,27 @@ fu_util_security_as_json(FuUtilPrivate *priv, GPtrArray *attrs, GPtrArray *event json_builder_end_array(builder); } + /* devices */ + devices_issues = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); + for (guint i = 0; i < devices->len; i++) { + FwupdDevice *device = g_ptr_array_index(devices, i); + GPtrArray *issues = fwupd_device_get_issues(device); + if (issues->len == 0) + continue; + g_ptr_array_add(devices_issues, g_object_ref(device)); + } + if (devices_issues->len > 0) { + json_builder_set_member_name(builder, "Devices"); + json_builder_begin_array(builder); + for (guint i = 0; i < devices_issues->len; i++) { + FwupdDevice *device = g_ptr_array_index(devices_issues, i); + json_builder_begin_object(builder); + fwupd_device_to_json(device, builder); + json_builder_end_object(builder); + } + json_builder_end_array(builder); + } + json_builder_end_object(builder); return fu_util_print_builder(builder, error); } @@ -3327,6 +3354,7 @@ fu_util_security(FuUtilPrivate *priv, gchar **values, GError **error) { FuSecurityAttrToStringFlags flags = FU_SECURITY_ATTR_TO_STRING_FLAG_NONE; g_autoptr(GPtrArray) attrs = NULL; + g_autoptr(GPtrArray) devices = NULL; g_autoptr(GPtrArray) events = NULL; g_autoptr(GError) error_local = NULL; g_autofree gchar *str = NULL; @@ -3360,9 +3388,14 @@ fu_util_security(FuUtilPrivate *priv, gchar **values, GError **error) } } + /* the "also" */ + devices = fwupd_client_get_devices(priv->client, priv->cancellable, error); + if (devices == NULL) + return FALSE; + /* not for human consumption */ if (priv->as_json) - return fu_util_security_as_json(priv, attrs, events, error); + return fu_util_security_as_json(priv, attrs, events, devices, error); g_print("%s \033[1m%s\033[0m\n", /* TRANSLATORS: this is a string like 'HSI:2-U' */ @@ -3384,6 +3417,13 @@ fu_util_security(FuUtilPrivate *priv, gchar **values, GError **error) g_print("%s\n", estr); } + /* known CVEs */ + if (devices->len > 0) { + g_autofree gchar *estr = fu_util_security_issues_to_string(devices); + if (estr != NULL) + g_print("%s", estr); + } + /* opted-out */ if (priv->no_unreported_check) return TRUE;