|  | /* | 
|  | * SPDX-License-Identifier: GPL-2.0-or-later | 
|  | * | 
|  | * uefi vars device - AuthVariableLib | 
|  | */ | 
|  |  | 
|  | #include "qemu/osdep.h" | 
|  | #include "qemu/error-report.h" | 
|  | #include "system/dma.h" | 
|  |  | 
|  | #include "hw/uefi/var-service.h" | 
|  |  | 
|  | static const uint16_t name_pk[]           = u"PK"; | 
|  | static const uint16_t name_kek[]          = u"KEK"; | 
|  | static const uint16_t name_db[]           = u"db"; | 
|  | static const uint16_t name_dbx[]          = u"dbx"; | 
|  | static const uint16_t name_setup_mode[]   = u"SetupMode"; | 
|  | static const uint16_t name_sigs_support[] = u"SignatureSupport"; | 
|  | static const uint16_t name_sb[]           = u"SecureBoot"; | 
|  | static const uint16_t name_sb_enable[]    = u"SecureBootEnable"; | 
|  | static const uint16_t name_custom_mode[]  = u"CustomMode"; | 
|  | static const uint16_t name_vk[]           = u"VendorKeys"; | 
|  | static const uint16_t name_vk_nv[]        = u"VendorKeysNv"; | 
|  |  | 
|  | static const uint32_t sigdb_attrs = | 
|  | EFI_VARIABLE_NON_VOLATILE | | 
|  | EFI_VARIABLE_BOOTSERVICE_ACCESS | | 
|  | EFI_VARIABLE_RUNTIME_ACCESS | | 
|  | EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS; | 
|  |  | 
|  | static void set_secure_boot(uefi_vars_state *uv, uint8_t sb) | 
|  | { | 
|  | uefi_vars_set_variable(uv, EfiGlobalVariable, | 
|  | name_sb, sizeof(name_sb), | 
|  | EFI_VARIABLE_BOOTSERVICE_ACCESS | | 
|  | EFI_VARIABLE_RUNTIME_ACCESS, | 
|  | &sb, sizeof(sb)); | 
|  | } | 
|  |  | 
|  | static void set_secure_boot_enable(uefi_vars_state *uv, uint8_t sbe) | 
|  | { | 
|  | uefi_vars_set_variable(uv, EfiSecureBootEnableDisable, | 
|  | name_sb_enable, sizeof(name_sb_enable), | 
|  | EFI_VARIABLE_NON_VOLATILE | | 
|  | EFI_VARIABLE_BOOTSERVICE_ACCESS, | 
|  | &sbe, sizeof(sbe)); | 
|  | } | 
|  |  | 
|  | static void set_setup_mode(uefi_vars_state *uv, uint8_t sm) | 
|  | { | 
|  | uefi_vars_set_variable(uv, EfiGlobalVariable, | 
|  | name_setup_mode, sizeof(name_setup_mode), | 
|  | EFI_VARIABLE_BOOTSERVICE_ACCESS | | 
|  | EFI_VARIABLE_RUNTIME_ACCESS, | 
|  | &sm, sizeof(sm)); | 
|  | } | 
|  |  | 
|  | static void set_custom_mode(uefi_vars_state *uv, uint8_t cm) | 
|  | { | 
|  | uefi_vars_set_variable(uv, EfiCustomModeEnable, | 
|  | name_custom_mode, sizeof(name_custom_mode), | 
|  | EFI_VARIABLE_NON_VOLATILE | | 
|  | EFI_VARIABLE_BOOTSERVICE_ACCESS, | 
|  | &cm, sizeof(cm)); | 
|  | } | 
|  |  | 
|  | static void set_signature_support(uefi_vars_state *uv) | 
|  | { | 
|  | QemuUUID sigs_support[5]; | 
|  |  | 
|  | sigs_support[0] = EfiCertSha256Guid; | 
|  | sigs_support[1] = EfiCertSha384Guid; | 
|  | sigs_support[2] = EfiCertSha512Guid; | 
|  | sigs_support[3] = EfiCertRsa2048Guid; | 
|  | sigs_support[4] = EfiCertX509Guid; | 
|  |  | 
|  | uefi_vars_set_variable(uv, EfiGlobalVariable, | 
|  | name_sigs_support, sizeof(name_sigs_support), | 
|  | EFI_VARIABLE_BOOTSERVICE_ACCESS | | 
|  | EFI_VARIABLE_RUNTIME_ACCESS, | 
|  | sigs_support, sizeof(sigs_support)); | 
|  | } | 
|  |  | 
|  | static bool setup_mode_is_active(uefi_vars_state *uv) | 
|  | { | 
|  | uefi_variable *var; | 
|  | uint8_t *value; | 
|  |  | 
|  | var = uefi_vars_find_variable(uv, EfiGlobalVariable, | 
|  | name_setup_mode, sizeof(name_setup_mode)); | 
|  | if (var) { | 
|  | value = var->data; | 
|  | if (value[0] == SETUP_MODE) { | 
|  | return true; | 
|  | } | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | static bool custom_mode_is_active(uefi_vars_state *uv) | 
|  | { | 
|  | uefi_variable *var; | 
|  | uint8_t *value; | 
|  |  | 
|  | var = uefi_vars_find_variable(uv, EfiCustomModeEnable, | 
|  | name_custom_mode, sizeof(name_custom_mode)); | 
|  | if (var) { | 
|  | value = var->data; | 
|  | if (value[0] == CUSTOM_SECURE_BOOT_MODE) { | 
|  | return true; | 
|  | } | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool uefi_vars_is_sb_pk(uefi_variable *var) | 
|  | { | 
|  | if (qemu_uuid_is_equal(&var->guid, &EfiGlobalVariable) && | 
|  | uefi_str_equal(var->name, var->name_size, name_pk, sizeof(name_pk))) { | 
|  | return true; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | static bool uefi_vars_is_sb_kek(uefi_variable *var) | 
|  | { | 
|  | if (qemu_uuid_is_equal(&var->guid, &EfiGlobalVariable) && | 
|  | uefi_str_equal(var->name, var->name_size, name_kek, sizeof(name_kek))) { | 
|  | return true; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | static bool uefi_vars_is_sb_db(uefi_variable *var) | 
|  | { | 
|  | if (!qemu_uuid_is_equal(&var->guid, &EfiImageSecurityDatabase)) { | 
|  | return false; | 
|  | } | 
|  | if (uefi_str_equal(var->name, var->name_size, name_db, sizeof(name_db))) { | 
|  | return true; | 
|  | } | 
|  | if (uefi_str_equal(var->name, var->name_size, name_dbx, sizeof(name_dbx))) { | 
|  | return true; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool uefi_vars_is_sb_any(uefi_variable *var) | 
|  | { | 
|  | if (uefi_vars_is_sb_pk(var) || | 
|  | uefi_vars_is_sb_kek(var) || | 
|  | uefi_vars_is_sb_db(var)) { | 
|  | return true; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | static uefi_variable *uefi_vars_find_siglist(uefi_vars_state *uv, | 
|  | uefi_variable *var) | 
|  | { | 
|  | if (uefi_vars_is_sb_pk(var)) { | 
|  | return uefi_vars_find_variable(uv, EfiGlobalVariable, | 
|  | name_pk, sizeof(name_pk)); | 
|  | } | 
|  | if (uefi_vars_is_sb_kek(var)) { | 
|  | return uefi_vars_find_variable(uv, EfiGlobalVariable, | 
|  | name_pk, sizeof(name_pk)); | 
|  | } | 
|  | if (uefi_vars_is_sb_db(var)) { | 
|  | return uefi_vars_find_variable(uv, EfiGlobalVariable, | 
|  | name_kek, sizeof(name_kek)); | 
|  | } | 
|  |  | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | static efi_status uefi_vars_check_auth_2_sb(uefi_vars_state *uv, | 
|  | uefi_variable *var, | 
|  | mm_variable_access *va, | 
|  | void *data, | 
|  | uint64_t data_offset) | 
|  | { | 
|  | variable_auth_2 *auth = data; | 
|  | uefi_variable *siglist; | 
|  |  | 
|  | if (custom_mode_is_active(uv)) { | 
|  | /* no authentication in custom mode */ | 
|  | return EFI_SUCCESS; | 
|  | } | 
|  |  | 
|  | if (setup_mode_is_active(uv) && !uefi_vars_is_sb_pk(var)) { | 
|  | /* no authentication in setup mode (except PK) */ | 
|  | return EFI_SUCCESS; | 
|  | } | 
|  |  | 
|  | if (auth->hdr_length == 24) { | 
|  | /* no signature (auth->cert_data is empty) */ | 
|  | return EFI_SECURITY_VIOLATION; | 
|  | } | 
|  |  | 
|  | siglist = uefi_vars_find_siglist(uv, var); | 
|  | if (!siglist && setup_mode_is_active(uv) && uefi_vars_is_sb_pk(var)) { | 
|  | /* check PK is self-signed */ | 
|  | uefi_variable tmp = { | 
|  | .guid       = EfiGlobalVariable, | 
|  | .name       = (uint16_t *)name_pk, | 
|  | .name_size  = sizeof(name_pk), | 
|  | .attributes = sigdb_attrs, | 
|  | .data       = data + data_offset, | 
|  | .data_size  = va->data_size - data_offset, | 
|  | }; | 
|  | return uefi_vars_check_pkcs7_2(&tmp, NULL, NULL, va, data); | 
|  | } | 
|  |  | 
|  | return uefi_vars_check_pkcs7_2(siglist, NULL, NULL, va, data); | 
|  | } | 
|  |  | 
|  | efi_status uefi_vars_check_auth_2(uefi_vars_state *uv, uefi_variable *var, | 
|  | mm_variable_access *va, void *data) | 
|  | { | 
|  | variable_auth_2 *auth = data; | 
|  | uint64_t data_offset; | 
|  | efi_status status; | 
|  |  | 
|  | if (va->data_size < sizeof(*auth)) { | 
|  | return EFI_SECURITY_VIOLATION; | 
|  | } | 
|  | if (uadd64_overflow(sizeof(efi_time), auth->hdr_length, &data_offset)) { | 
|  | return EFI_SECURITY_VIOLATION; | 
|  | } | 
|  | if (va->data_size < data_offset) { | 
|  | return EFI_SECURITY_VIOLATION; | 
|  | } | 
|  |  | 
|  | if (auth->hdr_revision != 0x0200 || | 
|  | auth->hdr_cert_type != WIN_CERT_TYPE_EFI_GUID || | 
|  | !qemu_uuid_is_equal(&auth->guid_cert_type, &EfiCertTypePkcs7Guid)) { | 
|  | return EFI_UNSUPPORTED; | 
|  | } | 
|  |  | 
|  | if (uefi_vars_is_sb_any(var)) { | 
|  | /* secure boot variables */ | 
|  | status = uefi_vars_check_auth_2_sb(uv, var, va, data, data_offset); | 
|  | if (status != EFI_SUCCESS) { | 
|  | return status; | 
|  | } | 
|  | } else { | 
|  | /* other authenticated variables */ | 
|  | status = uefi_vars_check_pkcs7_2(NULL, | 
|  | &var->digest, &var->digest_size, | 
|  | va, data); | 
|  | if (status != EFI_SUCCESS) { | 
|  | return status; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* checks passed, set variable data */ | 
|  | var->time = auth->timestamp; | 
|  | if (va->data_size - data_offset > 0) { | 
|  | var->data = g_malloc(va->data_size - data_offset); | 
|  | memcpy(var->data, data + data_offset, va->data_size - data_offset); | 
|  | var->data_size = va->data_size - data_offset; | 
|  | } | 
|  |  | 
|  | return EFI_SUCCESS; | 
|  | } | 
|  |  | 
|  | efi_status uefi_vars_check_secure_boot(uefi_vars_state *uv, uefi_variable *var) | 
|  | { | 
|  | uint8_t *value = var->data; | 
|  |  | 
|  | if (uefi_vars_is_sb_any(var)) { | 
|  | if (var->attributes != sigdb_attrs) { | 
|  | return EFI_INVALID_PARAMETER; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* reject SecureBootEnable updates if force_secure_boot is set */ | 
|  | if (qemu_uuid_is_equal(&var->guid, &EfiSecureBootEnableDisable) && | 
|  | uefi_str_equal(var->name, var->name_size, | 
|  | name_sb_enable, sizeof(name_sb_enable)) && | 
|  | uv->force_secure_boot && | 
|  | value[0] != SECURE_BOOT_ENABLE) { | 
|  | return EFI_WRITE_PROTECTED; | 
|  | } | 
|  |  | 
|  | /* reject CustomMode updates if disable_custom_mode is set */ | 
|  | if (qemu_uuid_is_equal(&var->guid, &EfiCustomModeEnable) && | 
|  | uefi_str_equal(var->name, var->name_size, | 
|  | name_custom_mode, sizeof(name_custom_mode)) && | 
|  | uv->disable_custom_mode) { | 
|  | return EFI_WRITE_PROTECTED; | 
|  | } | 
|  |  | 
|  | return EFI_SUCCESS; | 
|  | } | 
|  |  | 
|  | /* AuthVariableLibInitialize */ | 
|  | void uefi_vars_auth_init(uefi_vars_state *uv) | 
|  | { | 
|  | uefi_variable *pk_var, *sbe_var; | 
|  | uint8_t platform_mode, sb, sbe, vk; | 
|  |  | 
|  | /* SetupMode */ | 
|  | pk_var = uefi_vars_find_variable(uv, EfiGlobalVariable, | 
|  | name_pk, sizeof(name_pk)); | 
|  | if (!pk_var) { | 
|  | platform_mode = SETUP_MODE; | 
|  | } else { | 
|  | platform_mode = USER_MODE; | 
|  | } | 
|  | set_setup_mode(uv, platform_mode); | 
|  |  | 
|  | /* SignatureSupport */ | 
|  | set_signature_support(uv); | 
|  |  | 
|  | /* SecureBootEnable */ | 
|  | sbe = SECURE_BOOT_DISABLE; | 
|  | sbe_var = uefi_vars_find_variable(uv, EfiSecureBootEnableDisable, | 
|  | name_sb_enable, sizeof(name_sb_enable)); | 
|  | if (sbe_var) { | 
|  | if (platform_mode == USER_MODE) { | 
|  | sbe = ((uint8_t *)sbe_var->data)[0]; | 
|  | } | 
|  | } else if (platform_mode == USER_MODE) { | 
|  | sbe = SECURE_BOOT_ENABLE; | 
|  | set_secure_boot_enable(uv, sbe); | 
|  | } | 
|  |  | 
|  | if (uv->force_secure_boot && sbe != SECURE_BOOT_ENABLE) { | 
|  | sbe = SECURE_BOOT_ENABLE; | 
|  | set_secure_boot_enable(uv, sbe); | 
|  | } | 
|  |  | 
|  | /* SecureBoot */ | 
|  | if ((sbe == SECURE_BOOT_ENABLE) && (platform_mode == USER_MODE)) { | 
|  | sb = SECURE_BOOT_MODE_ENABLE; | 
|  | } else { | 
|  | sb = SECURE_BOOT_MODE_DISABLE; | 
|  | } | 
|  | set_secure_boot(uv, sb); | 
|  |  | 
|  | /* CustomMode */ | 
|  | set_custom_mode(uv, STANDARD_SECURE_BOOT_MODE); | 
|  |  | 
|  | vk = 0; | 
|  | uefi_vars_set_variable(uv, EfiGlobalVariable, | 
|  | name_vk_nv, sizeof(name_vk_nv), | 
|  | EFI_VARIABLE_NON_VOLATILE | | 
|  | EFI_VARIABLE_BOOTSERVICE_ACCESS | | 
|  | EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS, | 
|  | &vk, sizeof(vk)); | 
|  | uefi_vars_set_variable(uv, EfiGlobalVariable, | 
|  | name_vk, sizeof(name_vk), | 
|  | EFI_VARIABLE_BOOTSERVICE_ACCESS | | 
|  | EFI_VARIABLE_RUNTIME_ACCESS, | 
|  | &vk, sizeof(vk)); | 
|  |  | 
|  | /* flush to disk */ | 
|  | uefi_vars_json_save(uv); | 
|  | } |