| // SPDX-License-Identifier: GPL-2.0+ | |
| /* | |
| * UEFI runtime variable services | |
| * | |
| * Copyright (c) 2020, Heinrich Schuchardt <xypron.glpk@gmx.de> | |
| * Copyright (c) 2020 Linaro Limited, Author: AKASHI Takahiro | |
| */ | |
| #include <common.h> | |
| #include <efi_loader.h> | |
| #include <efi_variable.h> | |
| #include <stdlib.h> | |
| enum efi_secure_mode { | |
| EFI_MODE_SETUP, | |
| EFI_MODE_USER, | |
| EFI_MODE_AUDIT, | |
| EFI_MODE_DEPLOYED, | |
| }; | |
| struct efi_auth_var_name_type { | |
| const u16 *name; | |
| const efi_guid_t *guid; | |
| const enum efi_auth_var_type type; | |
| }; | |
| const efi_guid_t efi_guid_image_security_database = | |
| EFI_IMAGE_SECURITY_DATABASE_GUID; | |
| static const struct efi_auth_var_name_type name_type[] = { | |
| {u"PK", &efi_global_variable_guid, EFI_AUTH_VAR_PK}, | |
| {u"KEK", &efi_global_variable_guid, EFI_AUTH_VAR_KEK}, | |
| {u"db", &efi_guid_image_security_database, EFI_AUTH_VAR_DB}, | |
| {u"dbx", &efi_guid_image_security_database, EFI_AUTH_VAR_DBX}, | |
| {u"dbt", &efi_guid_image_security_database, EFI_AUTH_VAR_DBT}, | |
| {u"dbr", &efi_guid_image_security_database, EFI_AUTH_VAR_DBR}, | |
| {u"AuditMode", &efi_global_variable_guid, EFI_AUTH_MODE}, | |
| {u"DeployedMode", &efi_global_variable_guid, EFI_AUTH_MODE}, | |
| }; | |
| static bool efi_secure_boot; | |
| static enum efi_secure_mode efi_secure_mode; | |
| /** | |
| * efi_efi_get_variable() - retrieve value of a UEFI variable | |
| * | |
| * This function implements the GetVariable runtime service. | |
| * | |
| * See the Unified Extensible Firmware Interface (UEFI) specification for | |
| * details. | |
| * | |
| * @variable_name: name of the variable | |
| * @vendor: vendor GUID | |
| * @attributes: attributes of the variable | |
| * @data_size: size of the buffer to which the variable value is copied | |
| * @data: buffer to which the variable value is copied | |
| * Return: status code | |
| */ | |
| efi_status_t EFIAPI efi_get_variable(u16 *variable_name, | |
| const efi_guid_t *vendor, u32 *attributes, | |
| efi_uintn_t *data_size, void *data) | |
| { | |
| efi_status_t ret; | |
| EFI_ENTRY("\"%ls\" %pUs %p %p %p", variable_name, vendor, attributes, | |
| data_size, data); | |
| ret = efi_get_variable_int(variable_name, vendor, attributes, | |
| data_size, data, NULL); | |
| /* Remove EFI_VARIABLE_READ_ONLY flag */ | |
| if (attributes) | |
| *attributes &= EFI_VARIABLE_MASK; | |
| return EFI_EXIT(ret); | |
| } | |
| /** | |
| * efi_set_variable() - set value of a UEFI variable | |
| * | |
| * This function implements the SetVariable runtime service. | |
| * | |
| * See the Unified Extensible Firmware Interface (UEFI) specification for | |
| * details. | |
| * | |
| * @variable_name: name of the variable | |
| * @vendor: vendor GUID | |
| * @attributes: attributes of the variable | |
| * @data_size: size of the buffer with the variable value | |
| * @data: buffer with the variable value | |
| * Return: status code | |
| */ | |
| efi_status_t EFIAPI efi_set_variable(u16 *variable_name, | |
| const efi_guid_t *vendor, u32 attributes, | |
| efi_uintn_t data_size, const void *data) | |
| { | |
| efi_status_t ret; | |
| EFI_ENTRY("\"%ls\" %pUs %x %zu %p", variable_name, vendor, attributes, | |
| data_size, data); | |
| /* Make sure that the EFI_VARIABLE_READ_ONLY flag is not set */ | |
| if (attributes & ~(u32)EFI_VARIABLE_MASK) | |
| ret = EFI_INVALID_PARAMETER; | |
| else | |
| ret = efi_set_variable_int(variable_name, vendor, attributes, | |
| data_size, data, true); | |
| return EFI_EXIT(ret); | |
| } | |
| /** | |
| * efi_get_next_variable_name() - enumerate the current variable names | |
| * | |
| * @variable_name_size: size of variable_name buffer in byte | |
| * @variable_name: name of uefi variable's name in u16 | |
| * @vendor: vendor's guid | |
| * | |
| * See the Unified Extensible Firmware Interface (UEFI) specification for | |
| * details. | |
| * | |
| * Return: status code | |
| */ | |
| efi_status_t EFIAPI efi_get_next_variable_name(efi_uintn_t *variable_name_size, | |
| u16 *variable_name, | |
| efi_guid_t *vendor) | |
| { | |
| efi_status_t ret; | |
| EFI_ENTRY("%p \"%ls\" %pUs", variable_name_size, variable_name, vendor); | |
| ret = efi_get_next_variable_name_int(variable_name_size, variable_name, | |
| vendor); | |
| return EFI_EXIT(ret); | |
| } | |
| /** | |
| * efi_query_variable_info() - get information about EFI variables | |
| * | |
| * This function implements the QueryVariableInfo() runtime service. | |
| * | |
| * See the Unified Extensible Firmware Interface (UEFI) specification for | |
| * details. | |
| * | |
| * @attributes: bitmask to select variables to be | |
| * queried | |
| * @maximum_variable_storage_size: maximum size of storage area for the | |
| * selected variable types | |
| * @remaining_variable_storage_size: remaining size of storage are for the | |
| * selected variable types | |
| * @maximum_variable_size: maximum size of a variable of the | |
| * selected type | |
| * Returns: status code | |
| */ | |
| efi_status_t EFIAPI efi_query_variable_info( | |
| u32 attributes, u64 *maximum_variable_storage_size, | |
| u64 *remaining_variable_storage_size, | |
| u64 *maximum_variable_size) | |
| { | |
| efi_status_t ret; | |
| EFI_ENTRY("%x %p %p %p", attributes, maximum_variable_storage_size, | |
| remaining_variable_storage_size, maximum_variable_size); | |
| if (!maximum_variable_storage_size || | |
| !remaining_variable_storage_size || | |
| !maximum_variable_size || | |
| !(attributes & EFI_VARIABLE_BOOTSERVICE_ACCESS)) | |
| return EFI_EXIT(EFI_INVALID_PARAMETER); | |
| if ((attributes & ~(u32)EFI_VARIABLE_MASK) || | |
| (attributes & EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS) || | |
| (attributes & EFI_VARIABLE_HARDWARE_ERROR_RECORD) || | |
| (!IS_ENABLED(CONFIG_EFI_SECURE_BOOT) && | |
| (attributes & EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS))) | |
| return EFI_EXIT(EFI_UNSUPPORTED); | |
| ret = efi_query_variable_info_int(attributes, | |
| maximum_variable_storage_size, | |
| remaining_variable_storage_size, | |
| maximum_variable_size); | |
| return EFI_EXIT(ret); | |
| } | |
| efi_status_t __efi_runtime EFIAPI | |
| efi_get_variable_runtime(u16 *variable_name, const efi_guid_t *guid, | |
| u32 *attributes, efi_uintn_t *data_size, void *data) | |
| { | |
| efi_status_t ret; | |
| ret = efi_get_variable_mem(variable_name, guid, attributes, data_size, data, NULL); | |
| /* Remove EFI_VARIABLE_READ_ONLY flag */ | |
| if (attributes) | |
| *attributes &= EFI_VARIABLE_MASK; | |
| return ret; | |
| } | |
| efi_status_t __efi_runtime EFIAPI | |
| efi_get_next_variable_name_runtime(efi_uintn_t *variable_name_size, | |
| u16 *variable_name, efi_guid_t *guid) | |
| { | |
| return efi_get_next_variable_name_mem(variable_name_size, variable_name, guid); | |
| } | |
| /** | |
| * efi_set_secure_state - modify secure boot state variables | |
| * @secure_boot: value of SecureBoot | |
| * @setup_mode: value of SetupMode | |
| * @audit_mode: value of AuditMode | |
| * @deployed_mode: value of DeployedMode | |
| * | |
| * Modify secure boot status related variables as indicated. | |
| * | |
| * Return: status code | |
| */ | |
| static efi_status_t efi_set_secure_state(u8 secure_boot, u8 setup_mode, | |
| u8 audit_mode, u8 deployed_mode) | |
| { | |
| efi_status_t ret; | |
| const u32 attributes_ro = EFI_VARIABLE_BOOTSERVICE_ACCESS | | |
| EFI_VARIABLE_RUNTIME_ACCESS | | |
| EFI_VARIABLE_READ_ONLY; | |
| const u32 attributes_rw = EFI_VARIABLE_BOOTSERVICE_ACCESS | | |
| EFI_VARIABLE_RUNTIME_ACCESS; | |
| efi_secure_boot = secure_boot; | |
| ret = efi_set_variable_int(u"SecureBoot", &efi_global_variable_guid, | |
| attributes_ro, sizeof(secure_boot), | |
| &secure_boot, false); | |
| if (ret != EFI_SUCCESS) | |
| goto err; | |
| ret = efi_set_variable_int(u"SetupMode", &efi_global_variable_guid, | |
| attributes_ro, sizeof(setup_mode), | |
| &setup_mode, false); | |
| if (ret != EFI_SUCCESS) | |
| goto err; | |
| ret = efi_set_variable_int(u"AuditMode", &efi_global_variable_guid, | |
| audit_mode || setup_mode ? | |
| attributes_ro : attributes_rw, | |
| sizeof(audit_mode), &audit_mode, false); | |
| if (ret != EFI_SUCCESS) | |
| goto err; | |
| ret = efi_set_variable_int(u"DeployedMode", | |
| &efi_global_variable_guid, | |
| audit_mode || deployed_mode || setup_mode ? | |
| attributes_ro : attributes_rw, | |
| sizeof(deployed_mode), &deployed_mode, | |
| false); | |
| err: | |
| return ret; | |
| } | |
| /** | |
| * efi_transfer_secure_state - handle a secure boot state transition | |
| * @mode: new state | |
| * | |
| * Depending on @mode, secure boot related variables are updated. | |
| * Those variables are *read-only* for users, efi_set_variable_int() | |
| * is called here. | |
| * | |
| * Return: status code | |
| */ | |
| static efi_status_t efi_transfer_secure_state(enum efi_secure_mode mode) | |
| { | |
| efi_status_t ret; | |
| EFI_PRINT("Switching secure state from %d to %d\n", efi_secure_mode, | |
| mode); | |
| if (mode == EFI_MODE_DEPLOYED) { | |
| ret = efi_set_secure_state(1, 0, 0, 1); | |
| if (ret != EFI_SUCCESS) | |
| goto err; | |
| } else if (mode == EFI_MODE_AUDIT) { | |
| ret = efi_set_variable_int(u"PK", &efi_global_variable_guid, | |
| EFI_VARIABLE_BOOTSERVICE_ACCESS | | |
| EFI_VARIABLE_RUNTIME_ACCESS, | |
| 0, NULL, false); | |
| if (ret != EFI_SUCCESS) | |
| goto err; | |
| ret = efi_set_secure_state(0, 1, 1, 0); | |
| if (ret != EFI_SUCCESS) | |
| goto err; | |
| } else if (mode == EFI_MODE_USER) { | |
| ret = efi_set_secure_state(1, 0, 0, 0); | |
| if (ret != EFI_SUCCESS) | |
| goto err; | |
| } else if (mode == EFI_MODE_SETUP) { | |
| ret = efi_set_secure_state(0, 1, 0, 0); | |
| if (ret != EFI_SUCCESS) | |
| goto err; | |
| } else { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| efi_secure_mode = mode; | |
| return EFI_SUCCESS; | |
| err: | |
| /* TODO: What action should be taken here? */ | |
| printf("ERROR: Secure state transition failed\n"); | |
| return ret; | |
| } | |
| efi_status_t efi_init_secure_state(void) | |
| { | |
| enum efi_secure_mode mode; | |
| u8 efi_vendor_keys = 0; | |
| efi_uintn_t size; | |
| efi_status_t ret; | |
| u8 deployed_mode = 0; | |
| u8 audit_mode = 0; | |
| u8 setup_mode = 1; | |
| if (IS_ENABLED(CONFIG_EFI_SECURE_BOOT)) { | |
| size = sizeof(deployed_mode); | |
| ret = efi_get_variable_int(u"DeployedMode", &efi_global_variable_guid, | |
| NULL, &size, &deployed_mode, NULL); | |
| size = sizeof(audit_mode); | |
| ret = efi_get_variable_int(u"AuditMode", &efi_global_variable_guid, | |
| NULL, &size, &audit_mode, NULL); | |
| size = 0; | |
| ret = efi_get_variable_int(u"PK", &efi_global_variable_guid, | |
| NULL, &size, NULL, NULL); | |
| if (ret == EFI_BUFFER_TOO_SMALL) { | |
| setup_mode = 0; | |
| audit_mode = 0; | |
| } else { | |
| setup_mode = 1; | |
| deployed_mode = 0; | |
| } | |
| } | |
| if (deployed_mode) | |
| mode = EFI_MODE_DEPLOYED; | |
| else if (audit_mode) | |
| mode = EFI_MODE_AUDIT; | |
| else if (setup_mode) | |
| mode = EFI_MODE_SETUP; | |
| else | |
| mode = EFI_MODE_USER; | |
| ret = efi_transfer_secure_state(mode); | |
| if (ret != EFI_SUCCESS) | |
| return ret; | |
| /* As we do not provide vendor keys this variable is always 0. */ | |
| ret = efi_set_variable_int(u"VendorKeys", | |
| &efi_global_variable_guid, | |
| EFI_VARIABLE_BOOTSERVICE_ACCESS | | |
| EFI_VARIABLE_RUNTIME_ACCESS | | |
| EFI_VARIABLE_READ_ONLY, | |
| sizeof(efi_vendor_keys), | |
| &efi_vendor_keys, false); | |
| return ret; | |
| } | |
| /** | |
| * efi_secure_boot_enabled - return if secure boot is enabled or not | |
| * | |
| * Return: true if enabled, false if disabled | |
| */ | |
| bool efi_secure_boot_enabled(void) | |
| { | |
| return efi_secure_boot; | |
| } | |
| enum efi_auth_var_type efi_auth_var_get_type(const u16 *name, | |
| const efi_guid_t *guid) | |
| { | |
| for (size_t i = 0; i < ARRAY_SIZE(name_type); ++i) { | |
| if (!u16_strcmp(name, name_type[i].name) && | |
| !guidcmp(guid, name_type[i].guid)) | |
| return name_type[i].type; | |
| } | |
| return EFI_AUTH_VAR_NONE; | |
| } | |
| const efi_guid_t *efi_auth_var_get_guid(const u16 *name) | |
| { | |
| for (size_t i = 0; i < ARRAY_SIZE(name_type); ++i) { | |
| if (!u16_strcmp(name, name_type[i].name)) | |
| return name_type[i].guid; | |
| } | |
| return &efi_global_variable_guid; | |
| } | |
| /** | |
| * efi_get_var() - read value of an EFI variable | |
| * | |
| * @name: variable name | |
| * @start: vendor GUID | |
| * @size: size of allocated buffer | |
| * | |
| * Return: buffer with variable data or NULL | |
| */ | |
| void *efi_get_var(const u16 *name, const efi_guid_t *vendor, efi_uintn_t *size) | |
| { | |
| efi_status_t ret; | |
| void *buf = NULL; | |
| *size = 0; | |
| ret = efi_get_variable_int(name, vendor, NULL, size, buf, NULL); | |
| if (ret == EFI_BUFFER_TOO_SMALL) { | |
| buf = malloc(*size); | |
| if (!buf) | |
| return NULL; | |
| ret = efi_get_variable_int(name, vendor, NULL, size, buf, NULL); | |
| } | |
| if (ret != EFI_SUCCESS) { | |
| free(buf); | |
| *size = 0; | |
| return NULL; | |
| } | |
| return buf; | |
| } |