mirror of
https://git.proxmox.com/git/fwupd
synced 2025-06-08 18:27:42 +00:00
SMBIOS: try reading from /sys/class/dmi if direct access fails
The raw SMBIOS tables that Linux exposes in /sys/firmware/dmi are restricted to being readable by root only. If running as non-root access is still permitted by fields that have been pre-parsed by the kernel in /sys/class/dmi, most of which are world-readable. This allows the daemon to load most HWIDs even if running as a non-root user, as is done on Chromium OS.
This commit is contained in:
parent
dc483a5110
commit
464425fb5c
1
data/tests/dmi/class/chassis_type
Normal file
1
data/tests/dmi/class/chassis_type
Normal file
@ -0,0 +1 @@
|
||||
16
|
1
data/tests/dmi/class/sys_vendor
Normal file
1
data/tests/dmi/class/sys_vendor
Normal file
@ -0,0 +1 @@
|
||||
FwupdTest
|
@ -376,6 +376,33 @@ fu_smbios_dt_func (void)
|
||||
g_assert_cmpstr (str, ==, "Hughski Limited");
|
||||
}
|
||||
|
||||
static void
|
||||
fu_smbios_class_func (void)
|
||||
{
|
||||
g_autofree gchar *path = g_build_filename (TESTDATADIR_SRC, "dmi", "class", NULL);
|
||||
g_autoptr(FuSmbios) smbios = fu_smbios_new ();
|
||||
g_autoptr(GError) error = NULL;
|
||||
gboolean ret;
|
||||
const gchar *str;
|
||||
guint8 byte;
|
||||
|
||||
ret = fu_smbios_setup_from_kernel (smbios, path, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert (ret);
|
||||
if (g_getenv ("VERBOSE") != NULL) {
|
||||
g_autofree gchar *dump = fu_smbios_to_string (smbios);
|
||||
g_debug ("%s", dump);
|
||||
}
|
||||
|
||||
str = fu_smbios_get_string (smbios, FU_SMBIOS_STRUCTURE_TYPE_SYSTEM, 4, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_cmpstr (str, ==, "FwupdTest");
|
||||
|
||||
byte = fu_smbios_get_integer (smbios, FU_SMBIOS_STRUCTURE_TYPE_CHASSIS, 5, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_cmpuint(byte, ==, 16);
|
||||
}
|
||||
|
||||
static void
|
||||
fu_common_strsafe_func (void)
|
||||
{
|
||||
@ -3220,6 +3247,7 @@ main (int argc, char **argv)
|
||||
g_test_add_func ("/fwupd/smbios", fu_smbios_func);
|
||||
g_test_add_func ("/fwupd/smbios3", fu_smbios3_func);
|
||||
g_test_add_func ("/fwupd/smbios{dt}", fu_smbios_dt_func);
|
||||
g_test_add_func ("/fwupd/smbios{class}", fu_smbios_class_func);
|
||||
g_test_add_func ("/fwupd/firmware", fu_firmware_func);
|
||||
g_test_add_func ("/fwupd/firmware{dedupe}", fu_firmware_dedupe_func);
|
||||
g_test_add_func ("/fwupd/firmware{build}", fu_firmware_build_func);
|
||||
|
@ -21,3 +21,7 @@ gboolean fu_smbios_setup_from_file (FuSmbios *self,
|
||||
const gchar *filename,
|
||||
GError **error)
|
||||
G_GNUC_WARN_UNUSED_RESULT;
|
||||
gboolean fu_smbios_setup_from_kernel (FuSmbios *self,
|
||||
const gchar *path,
|
||||
GError **error)
|
||||
G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
@ -341,6 +341,146 @@ fu_smbios_setup_from_file (FuSmbios *self, const gchar *filename, GError **error
|
||||
return fu_smbios_setup_from_data (self, (guint8 *) buf, sz, error);
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
fu_smbios_encode_string_from_kernel (FuSmbios *self, const gchar *file_contents,
|
||||
guint8 smbios_type, guint8 smbios_offset,
|
||||
GError **error)
|
||||
{
|
||||
FuSmbiosItem *item = g_ptr_array_index (self->items, smbios_type);
|
||||
|
||||
/* add value to string table */
|
||||
g_ptr_array_add (item->strings, g_strdup (file_contents));
|
||||
/* add string table index to SMBIOS table */
|
||||
fu_smbios_convert_dt_value (self, smbios_type, smbios_offset, item->strings->len);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_smbios_encode_byte_from_kernel (FuSmbios *self, const gchar *file_contents,
|
||||
guint8 smbios_type, guint8 smbios_offset,
|
||||
GError **error)
|
||||
{
|
||||
gchar *endp;
|
||||
gint64 value = g_ascii_strtoll (file_contents, &endp, 10);
|
||||
|
||||
if (*endp != 0) {
|
||||
g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"non-numeric values in numeric string: %s", endp);
|
||||
return FALSE;
|
||||
}
|
||||
if (value < 0 || value > G_MAXUINT8) {
|
||||
g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"value \"%s\" is not representable in a byte",
|
||||
file_contents);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
fu_smbios_convert_dt_value (self, smbios_type, smbios_offset, value);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/*
|
||||
* The mapping from SMBIOS field to sysfs name can be found by mapping
|
||||
* the field to a kernel property name in dmi_decode()
|
||||
* (drivers/firmware/dmi_scan.c), then the property name to sysfs entry
|
||||
* in dmi_id_init_attr_table() (drivers/firmware/dmi-id.c). This table
|
||||
* lists each attribute exposed in /sys/class/dmi when CONFIG_DMIID is
|
||||
* enabled, mapping to the SMBIOS field and a function that can convert
|
||||
* the textual version of the field back into the raw SMBIOS table
|
||||
* representation.
|
||||
*/
|
||||
#define SYSFS_DMI_FIELD(_name, _type, _offset, kind) \
|
||||
{ .name = _name, .type = _type, .offset = _offset, .encode = fu_smbios_encode_ ## kind ## _from_kernel }
|
||||
const struct kernel_dmi_field {
|
||||
const gchar *name;
|
||||
gboolean (*encode)(FuSmbios *, const gchar *, guint8, guint8, GError **);
|
||||
guint8 type;
|
||||
guint8 offset;
|
||||
} KERNEL_DMI_FIELDS[] = {
|
||||
SYSFS_DMI_FIELD("bios_vendor", 0, 4, string),
|
||||
SYSFS_DMI_FIELD("bios_version", 0, 5, string),
|
||||
SYSFS_DMI_FIELD("bios_date", 0, 8, string),
|
||||
SYSFS_DMI_FIELD("sys_vendor", 1, 4, string),
|
||||
SYSFS_DMI_FIELD("product_name", 1, 5, string),
|
||||
SYSFS_DMI_FIELD("product_version", 1, 6, string),
|
||||
SYSFS_DMI_FIELD("product_serial", 1, 7, string),
|
||||
/* SYSFS_DMI_FIELD("product_uuid", 1, 8, uuid) */
|
||||
SYSFS_DMI_FIELD("product_family", 1, 26, string),
|
||||
SYSFS_DMI_FIELD("product_sku", 1, 25, string),
|
||||
SYSFS_DMI_FIELD("board_vendor", 2, 4, string),
|
||||
SYSFS_DMI_FIELD("board_name", 2, 5, string),
|
||||
SYSFS_DMI_FIELD("board_version", 2, 6, string),
|
||||
SYSFS_DMI_FIELD("board_serial", 2, 7, string),
|
||||
SYSFS_DMI_FIELD("board_asset_tag", 2, 8, string),
|
||||
SYSFS_DMI_FIELD("chassis_vendor", 3, 4, string),
|
||||
SYSFS_DMI_FIELD("chassis_type", 3, 5, byte),
|
||||
SYSFS_DMI_FIELD("chassis_version", 3, 6, string),
|
||||
SYSFS_DMI_FIELD("chassis_serial", 3, 7, string),
|
||||
SYSFS_DMI_FIELD("chassis_asset_tag", 3, 8, string),
|
||||
};
|
||||
|
||||
/**
|
||||
* fu_smbios_setup_from_kernel:
|
||||
* @self: a #FuSmbios
|
||||
* @path: a directory path
|
||||
* @error: (nullable): optional return location for an error
|
||||
*
|
||||
* Reads SMBIOS value from DMI values provided by the kernel, such as in
|
||||
* /sys/class/dmi on Linux.
|
||||
*
|
||||
* Returns: %TRUE for success
|
||||
*
|
||||
* Since: 1.6.2
|
||||
**/
|
||||
gboolean
|
||||
fu_smbios_setup_from_kernel (FuSmbios *self, const gchar *path, GError **error)
|
||||
{
|
||||
gboolean any_success = FALSE;
|
||||
|
||||
/* add fake structures */
|
||||
for (guint i = 0; i < FU_SMBIOS_STRUCTURE_TYPE_LAST; i++) {
|
||||
FuSmbiosItem *item = g_new0 (FuSmbiosItem, 1);
|
||||
item->type = i;
|
||||
item->buf = g_byte_array_new ();
|
||||
item->strings = g_ptr_array_new_with_free_func (g_free);
|
||||
g_ptr_array_add (self->items, item);
|
||||
}
|
||||
|
||||
/* parse every known field from the corresponding file */
|
||||
for (gsize i = 0; i < G_N_ELEMENTS (KERNEL_DMI_FIELDS); i++) {
|
||||
const struct kernel_dmi_field *field = &KERNEL_DMI_FIELDS[i];
|
||||
gsize bufsz = 0;
|
||||
g_autofree gchar *buf = NULL;
|
||||
g_autofree gchar *fn = g_build_filename (path, field->name, NULL);
|
||||
g_autoptr(GError) local_error = NULL;
|
||||
|
||||
if (!g_file_get_contents (fn, &buf, &bufsz, &local_error)) {
|
||||
g_debug ("unable to read SMBIOS data from %s: %s", fn,
|
||||
local_error->message);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* trim trailing newline added by kernel */
|
||||
if (buf[bufsz - 1] == '\n')
|
||||
buf[bufsz - 1] = 0;
|
||||
|
||||
if (!field->encode (self, buf, field->type, field->offset, &local_error)) {
|
||||
g_warning ("failed to parse SMBIOS data from %s: %s", fn,
|
||||
local_error->message);
|
||||
continue;
|
||||
}
|
||||
|
||||
any_success = TRUE;
|
||||
}
|
||||
if (!any_success) {
|
||||
g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"failed to read any SMBIOS values from %s", path);
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_smbios_parse_ep32 (FuSmbios *self, const gchar *buf, gsize sz, GError **error)
|
||||
{
|
||||
@ -559,6 +699,7 @@ fu_smbios_setup (FuSmbios *self, GError **error)
|
||||
g_autofree gchar *path = NULL;
|
||||
g_autofree gchar *path_dt = NULL;
|
||||
g_autofree gchar *sysfsfwdir = NULL;
|
||||
const gchar *path_dmi_class = "/sys/class/dmi/id";
|
||||
|
||||
g_return_val_if_fail (FU_IS_SMBIOS (self), FALSE);
|
||||
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
||||
@ -567,8 +708,22 @@ fu_smbios_setup (FuSmbios *self, GError **error)
|
||||
|
||||
/* DMI */
|
||||
path = g_build_filename (sysfsfwdir, "dmi", "tables", NULL);
|
||||
if (g_file_test (path, G_FILE_TEST_EXISTS))
|
||||
return fu_smbios_setup_from_path (self, path, error);
|
||||
if (g_file_test (path, G_FILE_TEST_EXISTS)) {
|
||||
g_autoptr(GError) error_local = NULL;
|
||||
if (!fu_smbios_setup_from_path (self, path, &error_local)) {
|
||||
if (!g_error_matches (error_local, G_FILE_ERROR, G_FILE_ERROR_ACCES)) {
|
||||
g_propagate_error (error, g_steal_pointer (&error_local));
|
||||
return FALSE;
|
||||
}
|
||||
g_debug ("ignoring %s", error_local->message);
|
||||
}
|
||||
}
|
||||
|
||||
/* the values the kernel parsed; these are world-readable */
|
||||
if (g_file_test (path_dmi_class, G_FILE_TEST_IS_DIR)) {
|
||||
g_debug ("trying to read %s", path_dmi_class);
|
||||
return fu_smbios_setup_from_kernel (self, path_dmi_class, error);
|
||||
}
|
||||
|
||||
/* DT */
|
||||
path_dt = g_build_filename (sysfsfwdir, "devicetree", "base", NULL);
|
||||
|
@ -866,6 +866,7 @@ LIBFWUPDPLUGIN_1.6.2 {
|
||||
fu_ifd_region_to_name;
|
||||
fu_ifd_region_to_string;
|
||||
fu_plugin_add_udev_subsystem;
|
||||
fu_smbios_setup_from_kernel;
|
||||
fu_udev_device_get_children_with_subsystem;
|
||||
fu_udev_device_set_dev;
|
||||
local: *;
|
||||
|
Loading…
Reference in New Issue
Block a user