Add support for loading default BIOS settings policy

A user can place a JSON file in /etc/fwupd/bios-settings.d/ with
the default desired policy for the machine.

fwupd will load this policy on startup to ensure BIOS settings
are set as desired by the system administrator.
This commit is contained in:
Mario Limonciello 2022-08-24 09:40:20 -05:00 committed by Mario Limonciello
parent 2f9cb74c59
commit 04c2186edc
13 changed files with 191 additions and 75 deletions

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -1,3 +1,4 @@
subdir('bios-settings.d')
subdir('pki')
subdir('remotes.d')

View File

@ -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);

View File

@ -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:
*

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
}
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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;
}