blob: 5a4af2e15f035059e601ec0d67b123fa582f700a [file] [log] [blame]
/*
* Copyright 2020 Mario Limonciello <mario.limonciello@dell.com>
* Copyright 2022 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#define G_LOG_DOMAIN "FuEngine"
#include "config.h"
#include <fwupdplugin.h>
#include <glib/gi18n.h>
#include "fu-cabinet.h"
#include "fu-context-private.h"
#include "fu-engine-helper.h"
#include "fu-engine.h"
#include "fu-usb-device-fw-ds20.h"
#include "fu-usb-device-ms-ds20.h"
void
fu_engine_add_firmware_gtypes(FuEngine *self)
{
FuContext *ctx = fu_engine_get_context(self);
fu_context_add_firmware_gtype(ctx, "raw", FU_TYPE_FIRMWARE);
fu_context_add_firmware_gtype(ctx, "cab", FU_TYPE_CAB_FIRMWARE);
fu_context_add_firmware_gtype(ctx, "cabinet", FU_TYPE_CABINET);
fu_context_add_firmware_gtype(ctx, "dfu", FU_TYPE_DFU_FIRMWARE);
fu_context_add_firmware_gtype(ctx, "fdt", FU_TYPE_FDT_FIRMWARE);
fu_context_add_firmware_gtype(ctx, "csv", FU_TYPE_CSV_FIRMWARE);
fu_context_add_firmware_gtype(ctx, "fit", FU_TYPE_FIT_FIRMWARE);
fu_context_add_firmware_gtype(ctx, "dfuse", FU_TYPE_DFUSE_FIRMWARE);
fu_context_add_firmware_gtype(ctx, "ifwi-cpd", FU_TYPE_IFWI_CPD_FIRMWARE);
fu_context_add_firmware_gtype(ctx, "ifwi-fpt", FU_TYPE_IFWI_FPT_FIRMWARE);
fu_context_add_firmware_gtype(ctx, "oprom", FU_TYPE_OPROM_FIRMWARE);
fu_context_add_firmware_gtype(ctx, "fmap", FU_TYPE_FMAP_FIRMWARE);
fu_context_add_firmware_gtype(ctx, "ihex", FU_TYPE_IHEX_FIRMWARE);
fu_context_add_firmware_gtype(ctx, "linear", FU_TYPE_LINEAR_FIRMWARE);
fu_context_add_firmware_gtype(ctx, "srec", FU_TYPE_SREC_FIRMWARE);
fu_context_add_firmware_gtype(ctx, "hid-descriptor", FU_TYPE_HID_DESCRIPTOR);
fu_context_add_firmware_gtype(ctx, "archive", FU_TYPE_ARCHIVE_FIRMWARE);
fu_context_add_firmware_gtype(ctx, "smbios", FU_TYPE_SMBIOS);
fu_context_add_firmware_gtype(ctx, "acpi-table", FU_TYPE_ACPI_TABLE);
fu_context_add_firmware_gtype(ctx, "sbatlevel", FU_TYPE_SBATLEVEL_SECTION);
fu_context_add_firmware_gtype(ctx, "edid", FU_TYPE_EDID);
fu_context_add_firmware_gtype(ctx, "efi-file", FU_TYPE_EFI_FILE);
fu_context_add_firmware_gtype(ctx, "efi-signature", FU_TYPE_EFI_SIGNATURE);
fu_context_add_firmware_gtype(ctx, "efi-signature-list", FU_TYPE_EFI_SIGNATURE_LIST);
fu_context_add_firmware_gtype(ctx,
"efi-variable-authentication2",
FU_TYPE_EFI_VARIABLE_AUTHENTICATION2);
fu_context_add_firmware_gtype(ctx, "efi-load-option", FU_TYPE_EFI_LOAD_OPTION);
fu_context_add_firmware_gtype(ctx, "efi-device-path-list", FU_TYPE_EFI_DEVICE_PATH_LIST);
fu_context_add_firmware_gtype(ctx, "efi-filesystem", FU_TYPE_EFI_FILESYSTEM);
fu_context_add_firmware_gtype(ctx, "efi-section", FU_TYPE_EFI_SECTION);
fu_context_add_firmware_gtype(ctx, "efi-volume", FU_TYPE_EFI_VOLUME);
fu_context_add_firmware_gtype(ctx, "efi-ftw-store", FU_TYPE_EFI_FTW_STORE);
fu_context_add_firmware_gtype(ctx,
"efi-vss2-variable-store",
FU_TYPE_EFI_VSS2_VARIABLE_STORE);
fu_context_add_firmware_gtype(ctx, "ifd-bios", FU_TYPE_IFD_BIOS);
fu_context_add_firmware_gtype(ctx, "ifd-firmware", FU_TYPE_IFD_FIRMWARE);
fu_context_add_firmware_gtype(ctx, "cfu-offer", FU_TYPE_CFU_OFFER);
fu_context_add_firmware_gtype(ctx, "cfu-payload", FU_TYPE_CFU_PAYLOAD);
fu_context_add_firmware_gtype(ctx, "uswid", FU_TYPE_USWID_FIRMWARE);
fu_context_add_firmware_gtype(ctx, "coswid", FU_TYPE_COSWID_FIRMWARE);
fu_context_add_firmware_gtype(ctx, "pefile", FU_TYPE_PEFILE_FIRMWARE);
fu_context_add_firmware_gtype(ctx, "elf", FU_TYPE_ELF_FIRMWARE);
fu_context_add_firmware_gtype(ctx, "x509-certificate", FU_TYPE_X509_CERTIFICATE);
fu_context_add_firmware_gtype(ctx, "intel-thunderbolt", FU_TYPE_INTEL_THUNDERBOLT_FIRMWARE);
fu_context_add_firmware_gtype(ctx, "intel-thunderbolt-nvm", FU_TYPE_INTEL_THUNDERBOLT_NVM);
fu_context_add_firmware_gtype(ctx, "usb-device-fw-ds20", FU_TYPE_USB_DEVICE_FW_DS20);
fu_context_add_firmware_gtype(ctx, "usb-device-ms-ds20", FU_TYPE_USB_DEVICE_MS_DS20);
}
static FwupdRelease *
fu_engine_get_release_with_tag(FuEngine *self,
FuEngineRequest *request,
FwupdDevice *dev,
const gchar *host_bkc,
GError **error)
{
g_autoptr(GPtrArray) rels = NULL;
g_auto(GStrv) host_bkcs = g_strsplit(host_bkc, ",", -1);
/* find the newest release that matches */
rels = fu_engine_get_releases(self, request, fwupd_device_get_id(dev), error);
if (rels == NULL)
return NULL;
for (guint i = 0; i < rels->len; i++) {
FwupdRelease *rel = g_ptr_array_index(rels, i);
for (guint j = 0; host_bkcs[j] != NULL; j++) {
if (fwupd_release_has_tag(rel, host_bkcs[j]))
return g_object_ref(rel);
}
}
/* no match */
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"no matching releases for device");
return NULL;
}
gboolean
fu_engine_update_motd(FuEngine *self, GError **error)
{
const gchar *host_bkc = fu_engine_get_host_bkc(self);
guint upgrade_count = 0;
guint sync_count = 0;
g_autoptr(FuEngineRequest) request = NULL;
g_autoptr(GPtrArray) devices = NULL;
g_autoptr(GString) str = g_string_new(NULL);
g_autofree gchar *target = NULL;
/* a subset of what fwupdmgr can do */
request = fu_engine_request_new(NULL);
fu_engine_request_set_feature_flags(request,
FWUPD_FEATURE_FLAG_DETACH_ACTION |
FWUPD_FEATURE_FLAG_UPDATE_ACTION);
/* get devices from daemon, we even want to know if it's nothing */
devices = fu_engine_get_devices(self, NULL);
if (devices != NULL) {
for (guint i = 0; i < devices->len; i++) {
FwupdDevice *dev = g_ptr_array_index(devices, i);
g_autoptr(GPtrArray) rels = NULL;
/* get the releases for this device */
if (!fu_device_has_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE))
continue;
rels =
fu_engine_get_upgrades(self, request, fwupd_device_get_id(dev), NULL);
if (rels == NULL)
continue;
upgrade_count++;
}
if (host_bkc != NULL) {
for (guint i = 0; i < devices->len; i++) {
FwupdDevice *dev = g_ptr_array_index(devices, i);
g_autoptr(FwupdRelease) rel = NULL;
if (!fu_device_has_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE))
continue;
rel = fu_engine_get_release_with_tag(self,
request,
dev,
host_bkc,
NULL);
if (rel == NULL)
continue;
if (g_strcmp0(fwupd_device_get_version(dev),
fwupd_release_get_version(rel)) != 0)
sync_count++;
}
}
}
/* if running under systemd unit, use the directory as a base */
if (g_getenv("RUNTIME_DIRECTORY") != NULL) {
target = g_build_filename(g_getenv("RUNTIME_DIRECTORY"), MOTD_FILE, NULL);
/* otherwise use the cache directory */
} else {
g_autofree gchar *directory = fu_path_from_kind(FU_PATH_KIND_CACHEDIR_PKG);
target = g_build_filename(directory, MOTD_DIR, MOTD_FILE, NULL);
}
/* create the directory and file, even if zero devices; we want an empty file then */
if (!fu_path_mkdir_parent(target, error))
return FALSE;
/* nag about syncing or updating, but never both */
if (sync_count > 0) {
g_string_append(str, "\n");
g_string_append_printf(str,
/* TRANSLATORS: this is shown in the MOTD */
ngettext("%u device is not the best known configuration.",
"%u devices are not the best known configuration.",
sync_count),
sync_count);
g_string_append(str, "\n");
g_string_append_printf(str,
/* TRANSLATORS: this is shown in the MOTD -- %1 is the
* command name, e.g. `fwupdmgr sync` */
_("Run `%s` to complete this action."),
"fwupdmgr sync");
g_string_append(str, "\n\n");
} else if (upgrade_count > 0) {
g_string_append(str, "\n");
g_string_append_printf(str,
/* TRANSLATORS: this is shown in the MOTD */
ngettext("%u device has a firmware upgrade available.",
"%u devices have a firmware upgrade available.",
upgrade_count),
upgrade_count);
g_string_append(str, "\n");
g_string_append_printf(str,
/* TRANSLATORS: this is shown in the MOTD -- %1 is the
* command name, e.g. `fwupdmgr get-upgrades` */
_("Run `%s` for more information."),
"fwupdmgr get-upgrades");
g_string_append(str, "\n\n");
}
/* success, with an empty file if nothing to say */
g_debug("writing motd target %s", target);
return g_file_set_contents(target, str->str, str->len, error);
}
gboolean
fu_engine_update_devices_file(FuEngine *self, GError **error)
{
FwupdCodecFlags flags = FWUPD_CODEC_FLAG_NONE;
gsize len;
g_autoptr(JsonBuilder) builder = NULL;
g_autoptr(JsonGenerator) generator = NULL;
g_autoptr(JsonNode) root = NULL;
g_autoptr(GPtrArray) devices = NULL;
g_autofree gchar *data = NULL;
g_autofree gchar *directory = NULL;
g_autofree gchar *target = NULL;
if (fu_engine_config_get_show_device_private(fu_engine_get_config(self)))
flags |= FWUPD_CODEC_FLAG_TRUSTED;
builder = json_builder_new();
json_builder_begin_object(builder);
devices = fu_engine_get_devices(self, NULL);
if (devices != NULL)
fwupd_codec_array_to_json(devices, "Devices", builder, flags);
root = json_builder_get_root(builder);
generator = json_generator_new();
json_generator_set_pretty(generator, TRUE);
json_generator_set_root(generator, root);
data = json_generator_to_data(generator, &len);
if (data == NULL) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"Failed to convert to JSON string");
return FALSE;
}
directory = fu_path_from_kind(FU_PATH_KIND_CACHEDIR_PKG);
target = g_build_filename(directory, "devices.json", NULL);
return g_file_set_contents(target, data, (gssize)len, error);
}
static void
fu_engine_integrity_add_measurement(GHashTable *self, const gchar *id, GBytes *blob)
{
g_autofree gchar *csum = g_compute_checksum_for_bytes(G_CHECKSUM_SHA256, blob);
g_hash_table_insert(self, g_strdup(id), g_steal_pointer(&csum));
}
static void
fu_engine_integrity_measure_acpi(FuContext *ctx, GHashTable *self)
{
g_autofree gchar *path = fu_path_from_kind(FU_PATH_KIND_ACPI_TABLES);
const gchar *tables[] = {
"SLIC",
"MSDM",
"TPM2",
};
for (guint i = 0; i < G_N_ELEMENTS(tables); i++) {
g_autofree gchar *fn = g_build_filename(path, tables[i], NULL);
g_autoptr(GBytes) blob = NULL;
blob = fu_bytes_get_contents(fn, NULL);
if (blob != NULL && g_bytes_get_size(blob) > 0) {
g_autofree gchar *id = g_strdup_printf("ACPI:%s", tables[i]);
fu_engine_integrity_add_measurement(self, id, blob);
}
}
}
static void
fu_engine_integrity_measure_uefi(FuContext *ctx, GHashTable *self)
{
FuEfivars *efivars = fu_context_get_efivars(ctx);
struct {
const gchar *guid;
const gchar *name;
} keys[] = {
{FU_EFIVARS_GUID_EFI_GLOBAL, "BootCurrent"},
{FU_EFIVARS_GUID_EFI_GLOBAL, "KEK"},
{FU_EFIVARS_GUID_EFI_GLOBAL, "KEKDefault"},
{FU_EFIVARS_GUID_EFI_GLOBAL, "OsIndications"},
{FU_EFIVARS_GUID_EFI_GLOBAL, "OsIndicationsSupported"},
{FU_EFIVARS_GUID_EFI_GLOBAL, "PK"},
{FU_EFIVARS_GUID_EFI_GLOBAL, "PKDefault"},
{FU_EFIVARS_GUID_EFI_GLOBAL, "SecureBoot"},
{FU_EFIVARS_GUID_EFI_GLOBAL, "SetupMode"},
{FU_EFIVARS_GUID_EFI_GLOBAL, "SignatureSupport"},
{FU_EFIVARS_GUID_EFI_GLOBAL, "VendorKeys"},
{FU_EFIVARS_GUID_SECURITY_DATABASE, "db"},
{FU_EFIVARS_GUID_SECURITY_DATABASE, "dbDefault"},
{FU_EFIVARS_GUID_SECURITY_DATABASE, "dbx"},
{FU_EFIVARS_GUID_SECURITY_DATABASE, "dbxDefault"},
};
/* important keys */
for (guint i = 0; i < G_N_ELEMENTS(keys); i++) {
g_autoptr(GBytes) blob =
fu_efivars_get_data_bytes(efivars, keys[i].guid, keys[i].name, NULL, NULL);
if (blob != NULL) {
g_autofree gchar *id = g_strdup_printf("UEFI:%s", keys[i].name);
fu_engine_integrity_add_measurement(self, id, blob);
}
}
/* UEFI Boot#### */
for (guint i = 0; i < 0xFF; i++) {
g_autoptr(GBytes) blob = fu_efivars_get_boot_data(efivars, i, NULL);
if (blob != NULL && g_bytes_get_size(blob) > 0) {
const guint8 needle[] = "f\0w\0u\0p\0d";
g_autofree gchar *id = g_strdup_printf("UEFI:Boot%04X", i);
if (fu_memmem_safe(g_bytes_get_data(blob, NULL),
g_bytes_get_size(blob),
needle,
sizeof(needle),
NULL,
NULL)) {
g_debug("skipping %s as fwupd found", id);
continue;
}
fu_engine_integrity_add_measurement(self, id, blob);
}
}
}
GHashTable *
fu_engine_integrity_new(FuContext *ctx, GError **error)
{
g_autoptr(GHashTable) self = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
g_return_val_if_fail(error == NULL || *error == NULL, NULL);
fu_engine_integrity_measure_uefi(ctx, self);
fu_engine_integrity_measure_acpi(ctx, self);
/* nothing of use */
if (g_hash_table_size(self) == 0) {
g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no measurements");
return NULL;
}
/* success */
return g_steal_pointer(&self);
}
gchar *
fu_engine_integrity_to_string(GHashTable *self)
{
GHashTableIter iter;
gpointer key, value;
g_autoptr(GPtrArray) array = g_ptr_array_new_with_free_func(g_free);
g_return_val_if_fail(self != NULL, NULL);
/* sanity check */
if (g_hash_table_size(self) == 0)
return NULL;
/* build into KV array */
g_hash_table_iter_init(&iter, self);
while (g_hash_table_iter_next(&iter, &key, &value)) {
g_ptr_array_add(array,
g_strdup_printf("%s=%s", (const gchar *)key, (const gchar *)value));
}
return fu_strjoin("\n", array);
}
static const GError *
fu_engine_error_array_find(GPtrArray *errors, FwupdError error_code)
{
for (guint j = 0; j < errors->len; j++) {
const GError *error = g_ptr_array_index(errors, j);
if (g_error_matches(error, FWUPD_ERROR, error_code))
return error;
}
return NULL;
}
static guint
fu_engine_error_array_count(GPtrArray *errors, FwupdError error_code)
{
guint cnt = 0;
for (guint j = 0; j < errors->len; j++) {
const GError *error = g_ptr_array_index(errors, j);
if (g_error_matches(error, FWUPD_ERROR, error_code))
cnt++;
}
return cnt;
}
static gboolean
fu_engine_error_array_matches_any(GPtrArray *errors, FwupdError *error_codes)
{
for (guint j = 0; j < errors->len; j++) {
const GError *error = g_ptr_array_index(errors, j);
gboolean matches_any = FALSE;
for (guint i = 0; error_codes[i] != FWUPD_ERROR_LAST; i++) {
if (g_error_matches(error, FWUPD_ERROR, error_codes[i])) {
matches_any = TRUE;
break;
}
}
if (!matches_any)
return FALSE;
}
return TRUE;
}
/**
* fu_engine_error_array_get_best:
* @errors: (element-type GError): array of errors
*
* Finds the 'best' error to show the user from a array of errors, creating a
* completely bespoke error where required.
*
* Returns: (transfer full): a #GError, never %NULL
**/
GError *
fu_engine_error_array_get_best(GPtrArray *errors)
{
FwupdError err_prio[] = {FWUPD_ERROR_INVALID_FILE,
FWUPD_ERROR_VERSION_SAME,
FWUPD_ERROR_VERSION_NEWER,
FWUPD_ERROR_NOT_SUPPORTED,
FWUPD_ERROR_INTERNAL,
FWUPD_ERROR_NOT_FOUND,
FWUPD_ERROR_LAST};
FwupdError err_all_uptodate[] = {FWUPD_ERROR_VERSION_SAME,
FWUPD_ERROR_NOT_FOUND,
FWUPD_ERROR_NOT_SUPPORTED,
FWUPD_ERROR_LAST};
FwupdError err_all_newer[] = {FWUPD_ERROR_VERSION_NEWER,
FWUPD_ERROR_VERSION_SAME,
FWUPD_ERROR_NOT_FOUND,
FWUPD_ERROR_NOT_SUPPORTED,
FWUPD_ERROR_LAST};
/* are all the errors either GUID-not-matched or version-same? */
if (fu_engine_error_array_count(errors, FWUPD_ERROR_VERSION_SAME) > 1 &&
fu_engine_error_array_matches_any(errors, err_all_uptodate)) {
return g_error_new(FWUPD_ERROR,
FWUPD_ERROR_NOTHING_TO_DO,
"All updatable firmware is already installed");
}
/* are all the errors either GUID-not-matched or version same or newer? */
if (fu_engine_error_array_count(errors, FWUPD_ERROR_VERSION_NEWER) > 1 &&
fu_engine_error_array_matches_any(errors, err_all_newer)) {
return g_error_new(FWUPD_ERROR,
FWUPD_ERROR_NOTHING_TO_DO,
"All updatable devices already have newer versions");
}
/* get the most important single error */
for (guint i = 0; err_prio[i] != FWUPD_ERROR_LAST; i++) {
const GError *error_tmp = fu_engine_error_array_find(errors, err_prio[i]);
if (error_tmp != NULL)
return g_error_copy(error_tmp);
}
/* fall back to something */
return g_error_new(FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "No supported devices found");
}
/**
* fu_engine_build_machine_id:
* @salt: (nullable): optional salt
* @error: (nullable): optional return location for an error
*
* Gets a salted hash of the /etc/machine-id contents. This can be used to
* identify a specific machine. It is not possible to recover the original
* machine-id from the machine-hash.
*
* Returns: the SHA256 machine hash, or %NULL if the ID is not present
**/
gchar *
fu_engine_build_machine_id(const gchar *salt, GError **error)
{
const gchar *machine_id;
gsize bufsz = 0;
g_autofree gchar *buf = NULL;
g_autoptr(GChecksum) csum = NULL;
g_return_val_if_fail(error == NULL || *error == NULL, NULL);
/* in test mode */
machine_id = g_getenv("FWUPD_MACHINE_ID");
if (machine_id != NULL) {
buf = g_strdup(machine_id);
bufsz = strlen(buf);
} else {
const gchar *fn = NULL;
g_autoptr(GPtrArray) fns = g_ptr_array_new_with_free_func(g_free);
/* one of these has to exist */
g_ptr_array_add(fns, g_build_filename(FWUPD_SYSCONFDIR, "machine-id", NULL));
g_ptr_array_add(
fns,
g_build_filename(FWUPD_LOCALSTATEDIR, "lib", "dbus", "machine-id", NULL));
g_ptr_array_add(fns, g_strdup("/etc/machine-id"));
g_ptr_array_add(fns, g_strdup("/var/lib/dbus/machine-id"));
g_ptr_array_add(fns, g_strdup("/var/db/dbus/machine-id"));
/* this is the hardcoded path for homebrew, e.g. `sudo dbus-uuidgen --ensure` */
g_ptr_array_add(fns, g_strdup("/usr/local/var/lib/dbus/machine-id"));
for (guint i = 0; i < fns->len; i++) {
const gchar *fn_tmp = g_ptr_array_index(fns, i);
if (g_file_test(fn_tmp, G_FILE_TEST_EXISTS)) {
fn = fn_tmp;
break;
}
}
if (fn == NULL) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_READ,
"The machine-id is not present");
return NULL;
}
if (!g_file_get_contents(fn, &buf, &bufsz, error))
return NULL;
if (bufsz == 0) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_READ,
"The machine-id is present but unset");
return NULL;
}
}
csum = g_checksum_new(G_CHECKSUM_SHA256);
if (salt != NULL)
g_checksum_update(csum, (const guchar *)salt, (gssize)strlen(salt));
g_checksum_update(csum, (const guchar *)buf, (gssize)bufsz);
return g_strdup(g_checksum_get_string(csum));
}