ata: Include a vendor ID for ATA hardware

Some vendors want to ship updates for ATA hardware, but there are currently no
lock-down restrictions in place for these kind of devices.

There is the OUI from the WWN block which is supposed to identify the vendor,
but this is not always set and so we have to be a little creative. We can match
90% of hardware using the vendor name prefix, and the last 10% can be detected
with a heuristic that was the result of comparing over 900 drive models.

I'm not including very old drive models, media converters, raid controllers,
or external 'portable' drives as I don't think it is useful. Also, if the drive
contains a Dell vendor block just hardcode this as Dell rather than trying to
be clever.

Also ask the user to contribute OUI values if this data is found with no quirk
data as this is the only real sane way to manage this data long term.
The list of OUIs can be found here: http://standards-oui.ieee.org/oui.txt
This commit is contained in:
Richard Hughes 2020-02-13 10:27:13 +00:00
parent 52bbae8209
commit dbcc8e1137
9 changed files with 256 additions and 27 deletions

View File

@ -234,6 +234,7 @@ mkdir -p --mode=0700 $RPM_BUILD_ROOT%{_localstatedir}/lib/fwupd/gnupg
%files -f %{name}.lang
%doc README.md AUTHORS
%license COPYING
%config(noreplace)%{_sysconfdir}/fwupd/ata.conf
%config(noreplace)%{_sysconfdir}/fwupd/daemon.conf
%config(noreplace)%{_sysconfdir}/fwupd/upower.conf
%if 0%{?have_uefi}

4
plugins/ata/ata.conf Normal file
View File

@ -0,0 +1,4 @@
[ata]
# ask the user to report the missing OUI in the daemon logs
UnknownOuiReport=true

View File

@ -1,3 +1,11 @@
# match all devices with this udev subsystem
[DeviceInstanceId=BLOCK]
Plugin = ata
[DeviceInstanceId=OUI\000c50]
Vendor = Seagate
VendorId = ATA:0x1BB1
[DeviceInstanceId=OUI\002538]
Vendor = Samsung
VendorId = ATA:0x144D

View File

@ -71,6 +71,8 @@ struct _FuAtaDevice {
guint usb_depth;
guint16 transfer_blocks;
guint8 transfer_mode;
guint32 oui;
gboolean unknown_oui_report;
};
G_DEFINE_TYPE (FuAtaDevice, fu_ata_device, FU_TYPE_UDEV_DEVICE)
@ -87,6 +89,12 @@ fu_ata_device_get_transfer_blocks (FuAtaDevice *self)
return self->transfer_blocks;
}
void
fu_ata_device_set_unknown_oui_report (FuAtaDevice *self, gboolean enabled)
{
self->unknown_oui_report = enabled;
}
static gchar *
fu_ata_device_get_string (const guint16 *buf, guint start, guint end)
{
@ -111,6 +119,8 @@ fu_ata_device_to_string (FuDevice *device, guint idt, GString *str)
FuAtaDevice *self = FU_ATA_DEVICE (device);
fu_common_string_append_kx (str, idt, "TransferMode", self->transfer_mode);
fu_common_string_append_kx (str, idt, "TransferBlocks", self->transfer_blocks);
if (self->oui != 0x0)
fu_common_string_append_kx (str, idt, "OUI", self->oui);
fu_common_string_append_ku (str, idt, "PciDepth", self->pci_depth);
fu_common_string_append_ku (str, idt, "UsbDepth", self->usb_depth);
}
@ -166,16 +176,153 @@ fu_ata_device_parse_id_maybe_dell (FuAtaDevice *self, const guint16 *buf)
guid_efi = fu_ata_device_get_guid_safe (buf, 129);
if (guid_efi != NULL)
fu_device_add_guid (FU_DEVICE (self), guid_efi);
/* owned by Dell */
fu_device_set_vendor (FU_DEVICE (self), "Dell");
fu_device_set_vendor_id (FU_DEVICE (self), "ATA:0x1028");
}
static void
fu_ata_device_parse_vendor_name (FuAtaDevice *self, const gchar *name)
{
struct {
const gchar *prefix; /* in CAPS */
guint16 vid;
const gchar *name;
} map_name[] = {
/* vendor matches */
{ "ADATA*", 0x1cc1, "ADATA" },
{ "APACER*", 0x0000, "Apacer" }, /* not in pci.ids */
{ "APPLE*", 0x106b, "Apple" },
{ "CORSAIR*", 0x1987, "Corsair" }, /* identifies as Phison */
{ "CRUCIAL*", 0xc0a9, "Crucial" },
{ "FUJITSU*", 0x10cf, "Fujitsu" },
{ "GIGABYTE*", 0x1458, "Gigabyte" },
{ "HGST*", 0x101c, "Western Digital" },
{ "HITACHI*", 0x101c, "Western Digital" }, /* was acquired by WD */
{ "HITACHI*", 0x1054, "Hitachi" },
{ "HP SSD*", 0x103c, "HP" },
{ "INTEL*", 0x8086, "Intel" },
{ "KINGSPEC*", 0x0000, "KingSpec" }, /* not in pci.ids */
{ "KINGSTON*", 0x2646, "Kingston" },
{ "LITEON*", 0x14a4, "LITE-ON" },
{ "MAXTOR*", 0x115f, "Maxtor" },
{ "MICRON*", 0x1344, "Micron" },
{ "OCZ*", 0x1179, "Toshiba" },
{ "PNY*", 0x196e, "PNY" },
{ "QEMU*", 0x1b36, "QEMU" }, /* identifies as Red Hat! */
{ "SAMSUNG*", 0x144d, "Samsung" },
{ "SANDISK*", 0x15b7, "SanDisk" },
{ "SEAGATE*", 0x1bb1, "Seagate" },
{ "SK HYNIX*", 0x1c5c, "SK hynix", },
{ "SUPERMICRO*",0x15d9, "SuperMicro" },
{ "TOSHIBA*", 0x1179, "Toshiba" },
{ "WDC*", 0x101c, "Western Digital" },
{ NULL, 0x0000, NULL }
};
struct {
const gchar *prefix; /* in CAPS */
guint16 vid;
const gchar *name;
} map_fuzzy[] = {
/* fuzzy name matches -- also see legacy list at:
* https://github.com/linuxhw/hw-probe/blob/master/hw-probe.pl#L647 */
{ "001-*", 0x1bb1, "Seagate" },
{ "726060*", 0x101c, "Western Digital" },
{ "CT*", 0xc0a9, "Crucial" },
{ "DT0*", 0x1179, "Toshiba" },
{ "EZEX*", 0x101c, "Western Digital" },
{ "GB0*", 0x1590, "HPE" },
{ "GOODRAM*", 0x1987, "Phison" },
{ "H??54*", 0x101c, "Western Digital" },
{ "H??72?0*", 0x101c, "Western Digital" },
{ "HDWG*", 0x1179, "Toshiba" },
{ "M?0??CA*", 0x1179, "Toshiba" }, /* enterprise */
{ "M4-CT*", 0xc0a9, "Crucial" },
{ "MA*", 0x10cf, "Fujitsu", },
{ "MB*", 0x10cf, "Fujitsu", },
{ "MK0*", 0x1590, "HPE" },
{ "MTFDDAK*", 0x1344, "Micron" },
{ "NIM*", 0x0000, "Nimbus", }, /* no PCI ID */
{ "SATADOM*", 0x0000, "Innodisk", }, /* no PCI ID */
{ "SSD 860*", 0x144d, "Samsung" },
{ "SSDPR*", 0x1987, "Phison" },
{ "SSDSC?K*", 0x8086, "Intel" },
{ "ST*", 0x1bb1, "Seagate", },
{ "TEAM*", 0x0000, "Team Group" }, /* not in pci.ids */
{ "TS*", 0x8564, "Transcend" },
{ "VK0*", 0x1590, "HPE" },
{ "WD*", 0x101c, "Western Digital" },
{ NULL, 0x0000, NULL }
};
struct {
const gchar *prefix; /* in CAPS */
guint16 vid;
const gchar *name;
} map_version[] = {
/* fuzzy version matches */
{ "CS2111*", 0x196e, "PNY" },
{ "S?FM*", 0x1987, "Phison" },
{ NULL, 0x0000, NULL }
};
g_autofree gchar *name_up = g_ascii_strup (name, -1);
g_autofree gchar *vendor_id = NULL;
/* find match */
for (guint i = 0; map_name[i].prefix != NULL; i++) {
if (fu_common_fnmatch (map_name[i].prefix, name_up)) {
name += strlen (map_name[i].prefix) - 1;
fu_device_set_vendor (FU_DEVICE (self), map_name[i].name);
vendor_id = g_strdup_printf ("ATA:0x%X", map_name[i].vid);
break;
}
}
/* fall back to fuzzy match */
if (vendor_id == NULL) {
for (guint i = 0; map_fuzzy[i].prefix != NULL; i++) {
if (fu_common_fnmatch (map_fuzzy[i].prefix, name_up)) {
fu_device_set_vendor (FU_DEVICE (self), map_fuzzy[i].name);
vendor_id = g_strdup_printf ("ATA:0x%X", map_fuzzy[i].vid);
break;
}
}
}
/* fall back to version */
if (vendor_id == NULL) {
g_autofree gchar *version_up = g_ascii_strup (fu_device_get_version (FU_DEVICE (self)), -1);
for (guint i = 0; map_version[i].prefix != NULL; i++) {
if (fu_common_fnmatch (map_version[i].prefix, version_up)) {
fu_device_set_vendor (FU_DEVICE (self), map_version[i].name);
vendor_id = g_strdup_printf ("ATA:0x%X", map_version[i].vid);
break;
}
}
}
/* devices without a vendor ID will not be UPGRADABLE */
if (vendor_id != NULL)
fu_device_set_vendor_id (FU_DEVICE (self), vendor_id);
/* remove leading junk */
while (name[0] == ' ' || name[0] == '_' || name[0] == '-')
name += 1;
/* if changed */
if (g_strcmp0 (fu_device_get_name (FU_DEVICE (self)), name) != 0)
fu_device_set_name (FU_DEVICE (self), name);
}
static gboolean
fu_ata_device_parse_id (FuAtaDevice *self, const guint8 *buf, gsize sz, GError **error)
{
FuDevice *device = FU_DEVICE (self);
gboolean has_oui_quirk = FALSE;
guint16 xfer_min = 1;
guint16 xfer_max = 0xffff;
guint16 id[FU_ATA_IDENTIFY_SIZE/2];
g_autofree gchar *name_pad = NULL;
g_autofree gchar *name = NULL;
g_autofree gchar *sku = NULL;
/* check size */
@ -231,12 +378,6 @@ fu_ata_device_parse_id (FuAtaDevice *self, const guint8 *buf, gsize sz, GError *
if (tmp != NULL)
fu_device_set_serial (device, tmp);
}
if (fu_device_get_name (device) == NULL) {
g_autofree gchar *tmp = NULL;
tmp = fu_ata_device_get_string (id, 27, 46);
if (tmp != NULL)
fu_device_set_name (device, tmp);
}
if (fu_device_get_version (device) == NULL) {
g_autofree gchar *tmp = NULL;
tmp = fu_ata_device_get_string (id, 23, 26);
@ -246,32 +387,61 @@ fu_ata_device_parse_id (FuAtaDevice *self, const guint8 *buf, gsize sz, GError *
fu_device_set_version_format (device, FWUPD_VERSION_FORMAT_PLAIN);
}
/* get OUI if set */
self->oui = ((guint32) (id[108] & 0x0fff)) << 12 |
((guint32) (id[109] & 0xfff0)) >> 4;
if (self->oui > 0x0) {
g_autofree gchar *tmp = NULL;
tmp = g_strdup_printf ("OUI\\%06x", self->oui);
fu_device_add_instance_id (device, tmp);
has_oui_quirk = fu_device_get_vendor (FU_DEVICE (self)) != NULL;
}
/* if not already set using the vendor block or a OUI quirk */
name = fu_ata_device_get_string (id, 27, 46);
if (name != NULL && !has_oui_quirk)
fu_ata_device_parse_vendor_name (self, name);
/* ask user to report data */
if (self->oui != 0x0 && !has_oui_quirk && self->unknown_oui_report) {
const gchar *url = "https://github.com/fwupd/fwupd/wiki/ATA-Disk:-OUI-Quirk-Required";
g_printerr ("OUI quirk required, please see %s!\n", url);
g_printerr ("---\n");
g_printerr ("[DeviceInstanceId=OUI\\%06x]\n", self->oui);
if (fu_device_get_vendor_id (FU_DEVICE (self)) != NULL) {
g_printerr ("Vendor = %s\n", fu_device_get_vendor (FU_DEVICE (self)));
g_printerr ("VendorId = %s\n", fu_device_get_vendor_id (FU_DEVICE (self)));
} else {
g_printerr ("Vendor = FIXME\n");
g_printerr ("VendorId = ATA:UNKNOWN\n");
}
g_printerr ("---\n");
}
/* 8 byte additional product identifier == SKU? */
sku = fu_ata_device_get_string (id, 170, 173);
if (sku != NULL)
g_debug ("SKU=%s", sku);
/* if we have vendor defined identify blocks don't add generic GUID */
if (fu_device_get_guids (device)->len != 0)
return TRUE;
/* add extra GUIDs if none detected from identify block */
name_pad = fu_ata_device_pad_string_for_id (fu_device_get_name (device));
if (name_pad != NULL &&
fu_device_get_version (device) != NULL) {
g_autofree gchar *tmp = NULL;
tmp = g_strdup_printf ("IDE\\%s%s", name_pad,
fu_device_get_version (device));
fu_device_add_instance_id (device, tmp);
}
if (name_pad != NULL) {
g_autofree gchar *tmp = NULL;
tmp = g_strdup_printf ("IDE\\0%s", name_pad);
fu_device_add_instance_id (device, tmp);
}
if (name != NULL && fu_device_get_guids (device)->len == 0) {
g_autofree gchar *name_pad = fu_ata_device_pad_string_for_id (name);
if (name_pad != NULL &&
fu_device_get_version (device) != NULL) {
g_autofree gchar *tmp = NULL;
tmp = g_strdup_printf ("IDE\\%s%s", name_pad,
fu_device_get_version (device));
fu_device_add_instance_id (device, tmp);
}
if (name_pad != NULL) {
g_autofree gchar *tmp = NULL;
tmp = g_strdup_printf ("IDE\\0%s", name_pad);
fu_device_add_instance_id (device, tmp);
}
/* add the name fallback */
fu_device_add_instance_id (device, fu_device_get_name (device));
/* add the name fallback */
fu_device_add_instance_id (device, name);
}
return TRUE;
}

View File

@ -18,3 +18,5 @@ FuAtaDevice *fu_ata_device_new_from_blob (const guint8 *buf,
/* for self tests */
guint8 fu_ata_device_get_transfer_mode (FuAtaDevice *self);
guint16 fu_ata_device_get_transfer_blocks (FuAtaDevice *self);
void fu_ata_device_set_unknown_oui_report (FuAtaDevice *self,
gboolean enabled);

View File

@ -18,3 +18,11 @@ fu_plugin_init (FuPlugin *plugin)
fu_plugin_add_udev_subsystem (plugin, "block");
fu_plugin_set_device_gtype (plugin, FU_TYPE_ATA_DEVICE);
}
gboolean
fu_plugin_device_created (FuPlugin *plugin, FuDevice *dev, GError **error)
{
gboolean tmp = fu_plugin_get_config_value_boolean (plugin, "UnknownOuiReport");
fu_ata_device_set_unknown_oui_report (FU_ATA_DEVICE (dev), tmp);
return TRUE;
}

View File

@ -9,6 +9,7 @@
#include <fwupd.h>
#include "fu-ata-device.h"
#include "fu-device-private.h"
static void
fu_ata_id_func (void)
@ -34,6 +35,36 @@ fu_ata_id_func (void)
g_assert_cmpstr (fu_device_get_version (FU_DEVICE (dev)), ==, "SBFM61.2");
}
static void
fu_ata_oui_func (void)
{
gboolean ret;
gsize sz;
g_autofree gchar *data = NULL;
g_autofree gchar *path = NULL;
g_autofree gchar *str = NULL;
g_autoptr(FuAtaDevice) dev = NULL;
g_autoptr(GError) error = NULL;
path = g_build_filename (TESTDATADIR, "Samsung SSD 860 EVO 500GB.bin", NULL);
ret = g_file_get_contents (path, &data, &sz, &error);
g_assert_no_error (error);
g_assert (ret);
dev = fu_ata_device_new_from_blob ((guint8 *)data, sz, &error);
g_assert_no_error (error);
g_assert_nonnull (dev);
fu_ata_device_set_unknown_oui_report (dev, FALSE);
fu_device_convert_instance_ids (FU_DEVICE (dev));
str = fu_device_to_string (FU_DEVICE (dev));
g_debug ("%s", str);
g_assert_true (fu_device_has_guid (FU_DEVICE (dev), "OUI\\002538"));
g_assert_cmpint (fu_ata_device_get_transfer_mode (dev), ==, 0xe);
g_assert_cmpint (fu_ata_device_get_transfer_blocks (dev), ==, 0x1);
g_assert_cmpstr (fu_device_get_serial (FU_DEVICE (dev)), ==, "S3Z1NB0K862928X");
g_assert_cmpstr (fu_device_get_name (FU_DEVICE (dev)), ==, "SSD 860 EVO 500GB");
g_assert_cmpstr (fu_device_get_version (FU_DEVICE (dev)), ==, "RVT01B6Q");
}
int
main (int argc, char **argv)
{
@ -43,6 +74,7 @@ main (int argc, char **argv)
g_log_set_fatal_mask (NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL);
/* tests go here */
g_test_add_func ("/fwupd/id", fu_ata_id_func);
g_test_add_func ("/fwupd/ata/id", fu_ata_id_func);
g_test_add_func ("/fwupd/ata/oui", fu_ata_oui_func);
return g_test_run ();
}

View File

@ -6,6 +6,10 @@ install_data([
install_dir: join_paths(datadir, 'fwupd', 'quirks.d')
)
install_data(['ata.conf'],
install_dir: join_paths(sysconfdir, 'fwupd')
)
shared_module('fu_plugin_ata',
fu_hash,
sources : [

Binary file not shown.