mirror of
https://git.proxmox.com/git/fwupd
synced 2025-06-08 23:36:26 +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");
|
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
|
static void
|
||||||
fu_common_strsafe_func (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/smbios", fu_smbios_func);
|
||||||
g_test_add_func ("/fwupd/smbios3", fu_smbios3_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{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", fu_firmware_func);
|
||||||
g_test_add_func ("/fwupd/firmware{dedupe}", fu_firmware_dedupe_func);
|
g_test_add_func ("/fwupd/firmware{dedupe}", fu_firmware_dedupe_func);
|
||||||
g_test_add_func ("/fwupd/firmware{build}", fu_firmware_build_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,
|
const gchar *filename,
|
||||||
GError **error)
|
GError **error)
|
||||||
G_GNUC_WARN_UNUSED_RESULT;
|
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);
|
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
|
static gboolean
|
||||||
fu_smbios_parse_ep32 (FuSmbios *self, const gchar *buf, gsize sz, GError **error)
|
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 = NULL;
|
||||||
g_autofree gchar *path_dt = NULL;
|
g_autofree gchar *path_dt = NULL;
|
||||||
g_autofree gchar *sysfsfwdir = 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 (FU_IS_SMBIOS (self), FALSE);
|
||||||
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
||||||
@ -567,8 +708,22 @@ fu_smbios_setup (FuSmbios *self, GError **error)
|
|||||||
|
|
||||||
/* DMI */
|
/* DMI */
|
||||||
path = g_build_filename (sysfsfwdir, "dmi", "tables", NULL);
|
path = g_build_filename (sysfsfwdir, "dmi", "tables", NULL);
|
||||||
if (g_file_test (path, G_FILE_TEST_EXISTS))
|
if (g_file_test (path, G_FILE_TEST_EXISTS)) {
|
||||||
return fu_smbios_setup_from_path (self, path, error);
|
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 */
|
/* DT */
|
||||||
path_dt = g_build_filename (sysfsfwdir, "devicetree", "base", NULL);
|
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_name;
|
||||||
fu_ifd_region_to_string;
|
fu_ifd_region_to_string;
|
||||||
fu_plugin_add_udev_subsystem;
|
fu_plugin_add_udev_subsystem;
|
||||||
|
fu_smbios_setup_from_kernel;
|
||||||
fu_udev_device_get_children_with_subsystem;
|
fu_udev_device_get_children_with_subsystem;
|
||||||
fu_udev_device_set_dev;
|
fu_udev_device_set_dev;
|
||||||
local: *;
|
local: *;
|
||||||
|
Loading…
Reference in New Issue
Block a user