diff --git a/contrib/fwupd.spec.in b/contrib/fwupd.spec.in index fe8064a97..f0420a7f4 100644 --- a/contrib/fwupd.spec.in +++ b/contrib/fwupd.spec.in @@ -358,6 +358,8 @@ done %{_bindir}/fwupdtool %{_bindir}/fwupdagent %dir %{_sysconfdir}/fwupd +%dir %{_sysconfdir}/fwupd/bios-settings.d +%config%(noreplace)%{_sysconfdir}/fwupd/bios-settings.d/README.md %dir %{_sysconfdir}/fwupd/remotes.d %if 0%{?have_dell} %config(noreplace)%{_sysconfdir}/fwupd/remotes.d/dell-esrt.conf diff --git a/data/bios-settings.d/README.md b/data/bios-settings.d/README.md new file mode 100644 index 000000000..0d9fba88a --- /dev/null +++ b/data/bios-settings.d/README.md @@ -0,0 +1,25 @@ +# BIOS Settings + +On supported machines fwupd can enforce BIOS settings policy so that a user's desired settings are configured at bootup +and prevent fwupd clients from changing them. + +## JSON policies + +A policy file can be created using `fwupdmgr`. First determine what settings you want to enforce by running: + +```shell +# fwupdmgr get-bios-settings +``` + +After you have identified settings, create a JSON payload by listing them on the command line. Any number of attributes can +be listed. +For example for the BIOS setting `WindowsUEFIFirmwareUpdate` you would create a policy file like this: + +```shell +# fwupdmgr get-bios-settings --json WindowsUEFIFirmwareUpdate > ~/foo.json +``` + +Now examine `~/foo.json` and modify the `BiosSettingCurrentValue` key to your desired value. + +Lastly place this policy file into `/etc/fwupd/bios-settings.d`. Any number of policies is supported, and they will be examined +in alphabetical order. The next time that fwupd is started it will load this policy and ensure that no fwupd clients change it. diff --git a/data/bios-settings.d/meson.build b/data/bios-settings.d/meson.build new file mode 100644 index 000000000..b0ff5b106 --- /dev/null +++ b/data/bios-settings.d/meson.build @@ -0,0 +1,5 @@ +if build_standalone and host_machine.system() == 'linux' +install_data('README.md', + install_dir: join_paths(sysconfdir, 'fwupd', 'bios-settings.d') +) +endif diff --git a/data/meson.build b/data/meson.build index d9a0d477b..3a77a7bfc 100644 --- a/data/meson.build +++ b/data/meson.build @@ -1,3 +1,4 @@ +subdir('bios-settings.d') subdir('pki') subdir('remotes.d') diff --git a/libfwupdplugin/fu-bios-settings-private.h b/libfwupdplugin/fu-bios-settings-private.h index cf54383a7..e0695b8ad 100644 --- a/libfwupdplugin/fu-bios-settings-private.h +++ b/libfwupdplugin/fu-bios-settings-private.h @@ -20,3 +20,7 @@ GVariant * fu_bios_settings_to_variant(FuBiosSettings *self, gboolean trusted); gboolean fu_bios_settings_from_json(FuBiosSettings *self, JsonNode *json_node, GError **error); +gboolean +fu_bios_settings_from_json_file(FuBiosSettings *self, const gchar *fn, GError **error); +GHashTable * +fu_bios_settings_to_hash_kv(FuBiosSettings *self); diff --git a/libfwupdplugin/fu-bios-settings.c b/libfwupdplugin/fu-bios-settings.c index d0a692565..9a2a2edcf 100644 --- a/libfwupdplugin/fu-bios-settings.c +++ b/libfwupdplugin/fu-bios-settings.c @@ -574,6 +574,8 @@ fu_bios_settings_to_variant(FuBiosSettings *self, gboolean trusted) /** * fu_bios_settings_from_json: * @self: a #FuBiosSettings + * @json_node: a Json node to parse from + * @error: (nullable): optional return location for an error * * Loads #FwupdBiosSetting objects from a JSON node. * @@ -615,6 +617,60 @@ fu_bios_settings_from_json(FuBiosSettings *self, JsonNode *json_node, GError **e return TRUE; } +/** + * fu_bios_settings_from_json_file: + * @self: A #FuBiosSettings + * @fn: a path to a JSON file + * @error: (nullable): optional return location for an error + * + * Adds all BIOS attributes from a JSON filename + * + * Returns: TRUE for success, FALSE for failure + * + * Since: 1.8.4 + **/ +gboolean +fu_bios_settings_from_json_file(FuBiosSettings *self, const gchar *fn, GError **error) +{ + g_autofree gchar *data = NULL; + g_autoptr(JsonParser) parser = json_parser_new(); + + if (!g_file_get_contents(fn, &data, NULL, error)) + return FALSE; + if (!json_parser_load_from_data(parser, data, -1, error)) { + g_prefix_error(error, "%s doesn't look like JSON data: ", fn); + return FALSE; + } + return fu_bios_settings_from_json(self, json_parser_get_root(parser), error); +} + +/** + * fu_bios_settings_to_hash_kv: + * @self: A #FuBiosSettings + * + * Creates a #GHashTable with the name and current value of + * all BIOS settings. + * + * Returns: (transfer full): name/value pairs + * Since: 1.8.4 + **/ +GHashTable * +fu_bios_settings_to_hash_kv(FuBiosSettings *self) +{ + GHashTable *bios_settings = NULL; + + g_return_val_if_fail(self != NULL, NULL); + + bios_settings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + for (guint i = 0; i < self->attrs->len; i++) { + FwupdBiosSetting *item_setting = g_ptr_array_index(self->attrs, i); + g_hash_table_insert(bios_settings, + g_strdup(fwupd_bios_setting_get_id(item_setting)), + g_strdup(fwupd_bios_setting_get_current_value(item_setting))); + } + return bios_settings; +} + /** * fu_bios_settings_new: * diff --git a/libfwupdplugin/fwupdplugin.map b/libfwupdplugin/fwupdplugin.map index 0a12f595c..6c9b30592 100644 --- a/libfwupdplugin/fwupdplugin.map +++ b/libfwupdplugin/fwupdplugin.map @@ -1072,12 +1072,14 @@ LIBFWUPDPLUGIN_1.8.4 { global: fu_backend_add_string; fu_bios_settings_from_json; + fu_bios_settings_from_json_file; fu_bios_settings_get_all; fu_bios_settings_get_attr; fu_bios_settings_get_pending_reboot; fu_bios_settings_get_type; fu_bios_settings_new; fu_bios_settings_setup; + fu_bios_settings_to_hash_kv; fu_bios_settings_to_variant; fu_context_get_bios_setting; fu_context_get_bios_setting_pending_reboot; diff --git a/src/fu-daemon.c b/src/fu-daemon.c index 2bd1ece3d..21909bd47 100644 --- a/src/fu-daemon.c +++ b/src/fu-daemon.c @@ -557,7 +557,10 @@ fu_daemon_authorize_set_bios_settings_cb(GObject *source, GAsyncResult *res, gpo #endif /* HAVE_POLKIT */ /* authenticated */ - if (!fu_engine_modify_bios_settings(helper->self->engine, helper->bios_settings, &error)) { + if (!fu_engine_modify_bios_settings(helper->self->engine, + helper->bios_settings, + FALSE, + &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } diff --git a/src/fu-engine.c b/src/fu-engine.c index 8e3df24e4..88dea5369 100644 --- a/src/fu-engine.c +++ b/src/fu-engine.c @@ -728,13 +728,19 @@ fu_engine_modify_remote(FuEngine *self, } static gboolean -fu_engine_update_bios_setting(FwupdBiosSetting *attr, const gchar *value, GError **error) +fu_engine_update_bios_setting(FwupdBiosSetting *attr, + const gchar *value, + gboolean force_ro, + GError **error) { int fd; g_autofree gchar *fn = g_build_filename(fwupd_bios_setting_get_path(attr), "current_value", NULL); g_autoptr(FuIOChannel) io = NULL; + if (force_ro) + fwupd_bios_setting_set_read_only(attr, TRUE); + if (g_strcmp0(fwupd_bios_setting_get_current_value(attr), value) == 0) { g_set_error(error, FWUPD_ERROR, @@ -868,6 +874,7 @@ static gboolean fu_engine_modify_single_bios_setting(FuEngine *self, const gchar *key, const gchar *value, + gboolean force_ro, GError **error) { FwupdBiosSetting *attr = fu_context_get_bios_setting(self->ctx, key); @@ -876,13 +883,14 @@ fu_engine_modify_single_bios_setting(FuEngine *self, if (!fu_engine_validate_bios_setting_input(attr, &tmp, error)) return FALSE; - return fu_engine_update_bios_setting(attr, tmp, error); + return fu_engine_update_bios_setting(attr, tmp, force_ro, error); } /** * fu_engine_modify_bios_settings: * @self: a #FuEngine * @settings: Hashtable of settings/values to configure + * @force_ro: a #gboolean indicating if BIOS settings should also be made read-only * @error: (nullable): optional return location for an error * * Use the kernel API to set one or more BIOS settings. @@ -890,7 +898,10 @@ fu_engine_modify_single_bios_setting(FuEngine *self, * Returns: %TRUE for success **/ gboolean -fu_engine_modify_bios_settings(FuEngine *self, GHashTable *settings, GError **error) +fu_engine_modify_bios_settings(FuEngine *self, + GHashTable *settings, + gboolean force_ro, + GError **error) { g_autoptr(FuBiosSettings) bios_settings = fu_context_get_bios_settings(self->ctx); gboolean changed = FALSE; @@ -912,7 +923,11 @@ fu_engine_modify_bios_settings(FuEngine *self, GHashTable *settings, GError **er (const gchar *)key); return FALSE; } - if (!fu_engine_modify_single_bios_setting(self, key, value, &error_local)) { + if (!fu_engine_modify_single_bios_setting(self, + key, + value, + force_ro, + &error_local)) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) { g_debug("%s", error_local->message); continue; @@ -6519,29 +6534,6 @@ fu_engine_security_attrs_from_json(FuEngine *self, JsonNode *json_node, GError * return TRUE; } -static gboolean -fu_engine_bios_settings_from_json(FuEngine *self, JsonNode *json_node, GError **error) -{ - JsonObject *obj; - g_autoptr(FuBiosSettings) bios_settings = fu_context_get_bios_settings(self->ctx); - - /* sanity check */ - if (!JSON_NODE_HOLDS_OBJECT(json_node)) { - g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "not JSON object"); - return FALSE; - } - - /* not supplied */ - obj = json_node_get_object(json_node); - if (!json_object_has_member(obj, "BiosSettings")) - return TRUE; - if (!fu_bios_settings_from_json(bios_settings, json_node, error)) - return FALSE; - - /* success */ - return TRUE; -} - static gboolean fu_engine_devices_from_json(FuEngine *self, JsonNode *json_node, GError **error) { @@ -6585,6 +6577,7 @@ fu_engine_load_host_emulation(FuEngine *self, const gchar *fn, GError **error) g_autoptr(GInputStream) istream_json = NULL; g_autoptr(GInputStream) istream_raw = NULL; g_autoptr(FwupdSecurityAttr) attr = NULL; + g_autoptr(FuBiosSettings) bios_settings = fu_context_get_bios_settings(self->ctx); /* add an attr so we know this is emulated and do not offer to upload results */ attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_HOST_EMULATION); @@ -6610,7 +6603,7 @@ fu_engine_load_host_emulation(FuEngine *self, const gchar *fn, GError **error) return FALSE; if (!fu_engine_security_attrs_from_json(self, json_parser_get_root(parser), error)) return FALSE; - if (!fu_engine_bios_settings_from_json(self, json_parser_get_root(parser), error)) + if (!fu_bios_settings_from_json(bios_settings, json_parser_get_root(parser), error)) return FALSE; #ifdef HAVE_HSI @@ -6896,6 +6889,34 @@ fu_engine_cleanup_state(GError **error) return TRUE; } +static gboolean +fu_engine_apply_default_bios_settings_policy(FuEngine *self, GError **error) +{ + const gchar *tmp; + g_autofree gchar *base = fu_path_from_kind(FU_PATH_KIND_SYSCONFDIR_PKG); + g_autofree gchar *dirname = g_build_filename(base, "bios-settings.d", NULL); + g_autofree gchar *data = NULL; + g_autoptr(FuBiosSettings) new_bios_settings = fu_bios_settings_new(); + g_autoptr(GHashTable) hashtable = NULL; + g_autoptr(GDir) dir = NULL; + + if (!g_file_test(dirname, G_FILE_TEST_EXISTS)) + return TRUE; + + dir = g_dir_open(dirname, 0, error); + while ((tmp = g_dir_read_name(dir)) != NULL) { + g_autofree gchar *fn = NULL; + if (g_strcmp0(tmp, "README.md") == 0) + continue; + fn = g_build_filename(dirname, tmp, NULL); + g_debug("Loading default BIOS settings policy from %s", fn); + if (!fu_bios_settings_from_json_file(new_bios_settings, fn, error)) + return FALSE; + } + hashtable = fu_bios_settings_to_hash_kv(new_bios_settings); + return fu_engine_modify_bios_settings(self, hashtable, TRUE, error); +} + static void fu_engine_check_firmware_attributes(FuEngine *self, FuDevice *device) { @@ -6908,8 +6929,14 @@ fu_engine_check_firmware_attributes(FuEngine *self, FuDevice *device) subsystem = fu_udev_device_get_subsystem(FU_UDEV_DEVICE(device)); if (g_strcmp0(subsystem, "firmware-attributes") == 0) { g_autoptr(GError) error = NULL; - if (!fu_context_reload_bios_settings(self->ctx, &error)) + if (!fu_context_reload_bios_settings(self->ctx, &error)) { g_debug("%s", error->message); + return; + } + if (!fu_engine_apply_default_bios_settings_policy(self, &error)) { + g_warning("Failed to apply BIOS settings policy: %s", error->message); + return; + } } } diff --git a/src/fu-engine.h b/src/fu-engine.h index 0969a1a26..8760a2d6b 100644 --- a/src/fu-engine.h +++ b/src/fu-engine.h @@ -244,4 +244,7 @@ fu_engine_schedule_update(FuEngine *self, GError * fu_engine_error_array_get_best(GPtrArray *errors); gboolean -fu_engine_modify_bios_settings(FuEngine *self, GHashTable *settings, GError **error); +fu_engine_modify_bios_settings(FuEngine *self, + GHashTable *settings, + gboolean force_ro, + GError **error); diff --git a/src/fu-self-test.c b/src/fu-self-test.c index e13411af0..96c7efa1d 100644 --- a/src/fu-self-test.c +++ b/src/fu-self-test.c @@ -4512,26 +4512,26 @@ fu_engine_modify_bios_settings_func(void) g_assert_nonnull(current); g_hash_table_insert(bios_settings, g_strdup("Absolute"), g_strdup("Disabled")); - ret = fu_engine_modify_bios_settings(engine, bios_settings, &error); + ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO); g_assert_false(ret); g_clear_error(&error); g_hash_table_remove_all(bios_settings); g_hash_table_insert(bios_settings, g_strdup("Absolute"), g_strdup("Enabled")); - ret = fu_engine_modify_bios_settings(engine, bios_settings, &error); + ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error); g_assert_no_error(error); g_assert_true(ret); g_hash_table_remove_all(bios_settings); g_hash_table_insert(bios_settings, g_strdup("Absolute"), g_strdup("off")); - ret = fu_engine_modify_bios_settings(engine, bios_settings, &error); + ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error); g_assert_no_error(error); g_assert_true(ret); g_hash_table_remove_all(bios_settings); g_hash_table_insert(bios_settings, g_strdup("Absolute"), g_strdup("FOO")); - ret = fu_engine_modify_bios_settings(engine, bios_settings, &error); + ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error); g_assert_false(ret); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); g_clear_error(&error); @@ -4539,7 +4539,7 @@ fu_engine_modify_bios_settings_func(void) /* use BiosSettingId instead */ g_hash_table_remove_all(bios_settings); g_hash_table_insert(bios_settings, g_strdup("com.fwupd-internal.Absolute"), g_strdup("on")); - ret = fu_engine_modify_bios_settings(engine, bios_settings, &error); + ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error); g_assert_no_error(error); g_assert_true(ret); @@ -4547,7 +4547,7 @@ fu_engine_modify_bios_settings_func(void) g_hash_table_insert(bios_settings, g_strdup("com.fwupd-internal.Absolute"), g_strdup("off")); - ret = fu_engine_modify_bios_settings(engine, bios_settings, &error); + ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error); g_assert_no_error(error); g_assert_true(ret); @@ -4561,13 +4561,13 @@ fu_engine_modify_bios_settings_func(void) g_hash_table_remove_all(bios_settings); g_hash_table_insert(bios_settings, g_strdup("Asset"), g_strdup("0")); - ret = fu_engine_modify_bios_settings(engine, bios_settings, &error); + ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error); g_assert_no_error(error); g_assert_true(ret); g_hash_table_remove_all(bios_settings); g_hash_table_insert(bios_settings, g_strdup("Asset"), g_strdup("1")); - ret = fu_engine_modify_bios_settings(engine, bios_settings, &error); + ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error); g_assert_no_error(error); g_assert_true(ret); @@ -4576,7 +4576,7 @@ fu_engine_modify_bios_settings_func(void) bios_settings, g_strdup("Absolute"), g_strdup("1234567891123456789112345678911234567891123456789112345678911111")); - ret = fu_engine_modify_bios_settings(engine, bios_settings, &error); + ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error); g_assert_false(ret); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); g_clear_error(&error); @@ -4591,26 +4591,27 @@ fu_engine_modify_bios_settings_func(void) g_hash_table_remove_all(bios_settings); g_hash_table_insert(bios_settings, g_strdup("CustomChargeStop"), g_strdup("75")); - ret = fu_engine_modify_bios_settings(engine, bios_settings, &error); + ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error); g_assert_true(ret); g_hash_table_remove_all(bios_settings); g_hash_table_insert(bios_settings, g_strdup("CustomChargeStop"), g_strdup("110")); - ret = fu_engine_modify_bios_settings(engine, bios_settings, &error); + ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error); g_assert_false(ret); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); g_clear_error(&error); g_hash_table_remove_all(bios_settings); g_hash_table_insert(bios_settings, g_strdup("CustomChargeStop"), g_strdup("1")); - ret = fu_engine_modify_bios_settings(engine, bios_settings, &error); + ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error); g_assert_false(ret); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); g_clear_error(&error); + /* force it to read only */ g_hash_table_remove_all(bios_settings); g_hash_table_insert(bios_settings, g_strdup("CustomChargeStop"), g_strdup("70")); - ret = fu_engine_modify_bios_settings(engine, bios_settings, &error); + ret = fu_engine_modify_bios_settings(engine, bios_settings, TRUE, &error); g_assert_no_error(error); g_assert_true(ret); @@ -4624,7 +4625,14 @@ fu_engine_modify_bios_settings_func(void) g_hash_table_remove_all(bios_settings); g_hash_table_insert(bios_settings, g_strdup("pending_reboot"), g_strdup("foo")); - ret = fu_engine_modify_bios_settings(engine, bios_settings, &error); + ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error); + g_assert_false(ret); + g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); + g_clear_error(&error); + + g_hash_table_remove_all(bios_settings); + g_hash_table_insert(bios_settings, g_strdup("CustomChargeStop"), g_strdup("80")); + ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error); g_assert_false(ret); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); g_clear_error(&error); diff --git a/src/fu-tool.c b/src/fu-tool.c index 21d8b831f..bac3952c1 100644 --- a/src/fu-tool.c +++ b/src/fu-tool.c @@ -3237,7 +3237,7 @@ fu_util_set_bios_setting(FuUtilPrivate *priv, gchar **input, GError **error) error)) return FALSE; - if (!fu_engine_modify_bios_settings(priv->engine, settings, error)) { + if (!fu_engine_modify_bios_settings(priv->engine, settings, FALSE, error)) { if (!g_error_matches(*error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) g_prefix_error(error, "failed to set BIOS setting: "); return FALSE; diff --git a/src/fu-util-bios-setting.c b/src/fu-util-bios-setting.c index 45446d160..e44976c31 100644 --- a/src/fu-util-bios-setting.c +++ b/src/fu-util-bios-setting.c @@ -206,8 +206,7 @@ fu_util_bios_setting_to_string(FwupdBiosSetting *setting, guint idt) GHashTable * fu_util_bios_settings_parse_argv(gchar **input, GError **error) { - g_autoptr(GHashTable) bios_settings = - g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + GHashTable *bios_settings; if (g_strv_length(input) == 0 || g_strv_length(input) % 2) { g_set_error_literal(error, @@ -220,36 +219,17 @@ fu_util_bios_settings_parse_argv(gchar **input, GError **error) /* json input */ if (g_strv_length(input) == 1) { - g_autofree gchar *data = NULL; - g_autoptr(JsonParser) parser = json_parser_new(); g_autoptr(FuBiosSettings) new_bios_settings = fu_bios_settings_new(); - g_autoptr(GPtrArray) new_items = NULL; - if (!g_file_get_contents(input[0], &data, NULL, error)) - return NULL; - if (!json_parser_load_from_data(parser, data, -1, error)) { - g_prefix_error(error, "%s doesn't look like JSON data: ", input[0]); - return NULL; - } - - if (!fu_bios_settings_from_json(new_bios_settings, - json_parser_get_root(parser), - error)) + if (!fu_bios_settings_from_json_file(new_bios_settings, input[0], error)) return NULL; - new_items = fu_bios_settings_get_all(new_bios_settings); - for (guint i = 0; i < new_items->len; i++) { - FwupdBiosSetting *item_setting = g_ptr_array_index(new_items, i); - g_hash_table_insert( - bios_settings, - g_strdup(fwupd_bios_setting_get_id(item_setting)), - g_strdup(fwupd_bios_setting_get_current_value(item_setting))); - } - } else { - for (guint i = 0; i < g_strv_length(input); i += 2) - g_hash_table_insert(bios_settings, - g_strdup(input[i]), - g_strdup(input[i + 1])); + return fu_bios_settings_to_hash_kv(new_bios_settings); } - return g_steal_pointer(&bios_settings); + + bios_settings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + for (guint i = 0; i < g_strv_length(input); i += 2) + g_hash_table_insert(bios_settings, g_strdup(input[i]), g_strdup(input[i + 1])); + + return bios_settings; }