Allow assigning issues to devices

This allows us to show in the tools if a device is currently affected
by a specific CVE. For instance, we could inform the user that a device
requires a critical firmware update that is being actively exploited.

Note, this also means we can show the user a firmware update is now
required, even though the firmware may not be available on the LVFS.

Also show the issue in the `fwupdmgr security` output, e.g.

    There are devices with issues:
      Samsung — MZVLB2T0HALB-000L7:
       • CVE-2022-12345
       • CVE-2022-54321
This commit is contained in:
Richard Hughes 2022-02-11 08:53:56 +00:00
parent cb9312744c
commit f63080fbe2
10 changed files with 198 additions and 2 deletions

View File

@ -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);
}

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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))

View File

@ -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"

View File

@ -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;
}

View File

@ -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)
{

View File

@ -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,

View File

@ -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;