X-Git-Url: https://git.librecmc.org/?p=oweals%2Fu-boot.git;a=blobdiff_plain;f=lib%2Fefi_loader%2Fefi_variable.c;h=4d275b23ce74d72f8e6fc83e8ba8dc5b377ab855;hp=22ad271bd8524173522a3263862a30d9a0aafb46;hb=915f15ac5746739da0aa2ee2840c2d00dc65aaaa;hpb=edb6b6842cbddc012d64491701f1d0979ffa96b3 diff --git a/lib/efi_loader/efi_variable.c b/lib/efi_loader/efi_variable.c index 22ad271bd8..4d275b23ce 100644 --- a/lib/efi_loader/efi_variable.c +++ b/lib/efi_loader/efi_variable.c @@ -1,21 +1,49 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * EFI utils + * UEFI runtime variable services * - * Copyright (c) 2017 Rob Clark + * Copyright (c) 2017 Rob Clark */ -#include -#include -#include +#include #include -#include +#include #include +#include +#include +#include #include #include +#include +#include +#include +#include + +enum efi_secure_mode { + EFI_MODE_SETUP, + EFI_MODE_USER, + EFI_MODE_AUDIT, + EFI_MODE_DEPLOYED, +}; + +static bool efi_secure_boot; +static enum efi_secure_mode efi_secure_mode; +static u8 efi_vendor_keys; #define READ_ONLY BIT(31) +static efi_status_t efi_get_variable_common(u16 *variable_name, + const efi_guid_t *vendor, + u32 *attributes, + efi_uintn_t *data_size, void *data); + +static efi_status_t efi_set_variable_common(u16 *variable_name, + const efi_guid_t *vendor, + u32 attributes, + efi_uintn_t data_size, + const void *data, + bool ro_check); + /* * Mapping between EFI variables and u-boot variables: * @@ -107,9 +135,10 @@ static const char *prefix(const char *str, const char *prefix) * * @str: value of U-Boot variable * @attrp: pointer to UEFI attributes + * @timep: pointer to time attribute * Return: pointer to remainder of U-Boot variable value */ -static const char *parse_attr(const char *str, u32 *attrp) +static const char *parse_attr(const char *str, u32 *attrp, u64 *timep) { u32 attr = 0; char sep = '{'; @@ -132,6 +161,12 @@ static const char *parse_attr(const char *str, u32 *attrp) attr |= EFI_VARIABLE_BOOTSERVICE_ACCESS; } else if ((s = prefix(str, "run"))) { attr |= EFI_VARIABLE_RUNTIME_ACCESS; + } else if ((s = prefix(str, "time="))) { + attr |= EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS; + hex2bin((u8 *)timep, s, sizeof(*timep)); + s += sizeof(*timep) * 2; + } else if (*str == '}') { + break; } else { printf("invalid attribute: %s\n", str); break; @@ -149,48 +184,442 @@ static const char *parse_attr(const char *str, u32 *attrp) } /** - * efi_get_variable() - retrieve value of a UEFI variable + * efi_set_secure_state - modify secure boot state variables + * @sec_boot: value of SecureBoot + * @setup_mode: value of SetupMode + * @audit_mode: value of AuditMode + * @deployed_mode: value of DeployedMode * - * This function implements the GetVariable runtime service. + * Modify secure boot stat-related variables as indicated. * - * 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) +static efi_status_t efi_set_secure_state(int sec_boot, int setup_mode, + int audit_mode, int deployed_mode) +{ + u32 attributes; + efi_status_t ret; + + attributes = EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS | + READ_ONLY; + ret = efi_set_variable_common(L"SecureBoot", &efi_global_variable_guid, + attributes, sizeof(sec_boot), &sec_boot, + false); + if (ret != EFI_SUCCESS) + goto err; + + ret = efi_set_variable_common(L"SetupMode", &efi_global_variable_guid, + attributes, sizeof(setup_mode), + &setup_mode, false); + if (ret != EFI_SUCCESS) + goto err; + + ret = efi_set_variable_common(L"AuditMode", &efi_global_variable_guid, + attributes, sizeof(audit_mode), + &audit_mode, false); + if (ret != EFI_SUCCESS) + goto err; + + ret = efi_set_variable_common(L"DeployedMode", + &efi_global_variable_guid, attributes, + 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_common() + * is called here. + * + * Return: status code + */ +static efi_status_t efi_transfer_secure_state(enum efi_secure_mode mode) +{ + efi_status_t ret; + + debug("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; + + efi_secure_boot = true; + } else if (mode == EFI_MODE_AUDIT) { + ret = efi_set_variable_common(L"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; + + efi_secure_boot = true; + } else if (mode == EFI_MODE_USER) { + ret = efi_set_secure_state(1, 0, 0, 0); + if (ret != EFI_SUCCESS) + goto err; + + efi_secure_boot = true; + } 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_init_secure_state - initialize secure boot state + * + * Return: status code + */ +static efi_status_t efi_init_secure_state(void) +{ + enum efi_secure_mode mode; + efi_uintn_t size; + efi_status_t ret; + + /* + * TODO: + * Since there is currently no "platform-specific" installation + * method of Platform Key, we can't say if VendorKeys is 0 or 1 + * precisely. + */ + + size = 0; + ret = efi_get_variable_common(L"PK", &efi_global_variable_guid, + NULL, &size, NULL); + if (ret == EFI_BUFFER_TOO_SMALL) { + if (IS_ENABLED(CONFIG_EFI_SECURE_BOOT)) + mode = EFI_MODE_USER; + else + mode = EFI_MODE_SETUP; + + efi_vendor_keys = 0; + } else if (ret == EFI_NOT_FOUND) { + mode = EFI_MODE_SETUP; + efi_vendor_keys = 1; + } else { + goto err; + } + + ret = efi_transfer_secure_state(mode); + if (ret == EFI_SUCCESS) + ret = efi_set_variable_common(L"VendorKeys", + &efi_global_variable_guid, + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS | + READ_ONLY, + sizeof(efi_vendor_keys), + &efi_vendor_keys, false); + +err: + 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; +} + +#ifdef CONFIG_EFI_SECURE_BOOT +static u8 pkcs7_hdr[] = { + /* SEQUENCE */ + 0x30, 0x82, 0x05, 0xc7, + /* OID: pkcs7-signedData */ + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x02, + /* Context Structured? */ + 0xa0, 0x82, 0x05, 0xb8, +}; + +/** + * efi_variable_parse_signature - parse a signature in variable + * @buf: Pointer to variable's value + * @buflen: Length of @buf + * + * Parse a signature embedded in variable's value and instantiate + * a pkcs7_message structure. Since pkcs7_parse_message() accepts only + * pkcs7's signedData, some header needed be prepended for correctly + * parsing authentication data, particularly for variable's. + * + * Return: Pointer to pkcs7_message structure on success, NULL on error + */ +static struct pkcs7_message *efi_variable_parse_signature(const void *buf, + size_t buflen) +{ + u8 *ebuf; + size_t ebuflen, len; + struct pkcs7_message *msg; + + /* + * This is the best assumption to check if the binary is + * already in a form of pkcs7's signedData. + */ + if (buflen > sizeof(pkcs7_hdr) && + !memcmp(&((u8 *)buf)[4], &pkcs7_hdr[4], 11)) { + msg = pkcs7_parse_message(buf, buflen); + goto out; + } + + /* + * Otherwise, we should add a dummy prefix sequence for pkcs7 + * message parser to be able to process. + * NOTE: EDK2 also uses similar hack in WrapPkcs7Data() + * in CryptoPkg/Library/BaseCryptLib/Pk/CryptPkcs7VerifyCommon.c + * TODO: + * The header should be composed in a more refined manner. + */ + debug("Makeshift prefix added to authentication data\n"); + ebuflen = sizeof(pkcs7_hdr) + buflen; + if (ebuflen <= 0x7f) { + debug("Data is too short\n"); + return NULL; + } + + ebuf = malloc(ebuflen); + if (!ebuf) { + debug("Out of memory\n"); + return NULL; + } + + memcpy(ebuf, pkcs7_hdr, sizeof(pkcs7_hdr)); + memcpy(ebuf + sizeof(pkcs7_hdr), buf, buflen); + len = ebuflen - 4; + ebuf[2] = (len >> 8) & 0xff; + ebuf[3] = len & 0xff; + len = ebuflen - 0x13; + ebuf[0x11] = (len >> 8) & 0xff; + ebuf[0x12] = len & 0xff; + + msg = pkcs7_parse_message(ebuf, ebuflen); + + free(ebuf); + +out: + if (IS_ERR(msg)) + return NULL; + + return msg; +} + +/** + * efi_variable_authenticate - authenticate a variable + * @variable: Variable name in u16 + * @vendor: Guid of variable + * @data_size: Size of @data + * @data: Pointer to variable's value + * @given_attr: Attributes to be given at SetVariable() + * @env_attr: Attributes that an existing variable holds + * @time: signed time that an existing variable holds + * + * Called by efi_set_variable() to verify that the input is correct. + * Will replace the given data pointer with another that points to + * the actual data to store in the internal memory. + * On success, @data and @data_size will be replaced with variable's + * actual data, excluding authentication data, and its size, and variable's + * attributes and signed time will also be returned in @env_attr and @time, + * respectively. + * + * Return: status code + */ +static efi_status_t efi_variable_authenticate(u16 *variable, + const efi_guid_t *vendor, + efi_uintn_t *data_size, + const void **data, u32 given_attr, + u32 *env_attr, u64 *time) +{ + const struct efi_variable_authentication_2 *auth; + struct efi_signature_store *truststore, *truststore2; + struct pkcs7_message *var_sig; + struct efi_image_regions *regs; + struct efi_time timestamp; + struct rtc_time tm; + u64 new_time; + efi_status_t ret; + + var_sig = NULL; + truststore = NULL; + truststore2 = NULL; + regs = NULL; + ret = EFI_SECURITY_VIOLATION; + + if (*data_size < sizeof(struct efi_variable_authentication_2)) + goto err; + + /* authentication data */ + auth = *data; + if (*data_size < (sizeof(auth->time_stamp) + + auth->auth_info.hdr.dwLength)) + goto err; + + if (guidcmp(&auth->auth_info.cert_type, &efi_guid_cert_type_pkcs7)) + goto err; + + *data += sizeof(auth->time_stamp) + auth->auth_info.hdr.dwLength; + *data_size -= (sizeof(auth->time_stamp) + + auth->auth_info.hdr.dwLength); + + memcpy(×tamp, &auth->time_stamp, sizeof(timestamp)); + memset(&tm, 0, sizeof(tm)); + tm.tm_year = timestamp.year; + tm.tm_mon = timestamp.month; + tm.tm_mday = timestamp.day; + tm.tm_hour = timestamp.hour; + tm.tm_min = timestamp.minute; + tm.tm_sec = timestamp.second; + new_time = rtc_mktime(&tm); + + if (!efi_secure_boot_enabled()) { + /* finished checking */ + *time = new_time; + return EFI_SUCCESS; + } + + if (new_time <= *time) + goto err; + + /* data to be digested */ + regs = calloc(sizeof(*regs) + sizeof(struct image_region) * 5, 1); + if (!regs) + goto err; + regs->max = 5; + efi_image_region_add(regs, (uint8_t *)variable, + (uint8_t *)variable + + u16_strlen(variable) * sizeof(u16), 1); + efi_image_region_add(regs, (uint8_t *)vendor, + (uint8_t *)vendor + sizeof(*vendor), 1); + efi_image_region_add(regs, (uint8_t *)&given_attr, + (uint8_t *)&given_attr + sizeof(given_attr), 1); + efi_image_region_add(regs, (uint8_t *)×tamp, + (uint8_t *)×tamp + sizeof(timestamp), 1); + efi_image_region_add(regs, (uint8_t *)*data, + (uint8_t *)*data + *data_size, 1); + + /* variable's signature list */ + if (auth->auth_info.hdr.dwLength < sizeof(auth->auth_info)) + goto err; + var_sig = efi_variable_parse_signature(auth->auth_info.cert_data, + auth->auth_info.hdr.dwLength + - sizeof(auth->auth_info)); + if (!var_sig) { + debug("Parsing variable's signature failed\n"); + goto err; + } + + /* signature database used for authentication */ + if (u16_strcmp(variable, L"PK") == 0 || + u16_strcmp(variable, L"KEK") == 0) { + /* with PK */ + truststore = efi_sigstore_parse_sigdb(L"PK"); + if (!truststore) + goto err; + } else if (u16_strcmp(variable, L"db") == 0 || + u16_strcmp(variable, L"dbx") == 0) { + /* with PK and KEK */ + truststore = efi_sigstore_parse_sigdb(L"KEK"); + truststore2 = efi_sigstore_parse_sigdb(L"PK"); + + if (!truststore) { + if (!truststore2) + goto err; + + truststore = truststore2; + truststore2 = NULL; + } + } else { + /* TODO: support private authenticated variables */ + goto err; + } + + /* verify signature */ + if (efi_signature_verify_with_sigdb(regs, var_sig, truststore, NULL)) { + debug("Verified\n"); + } else { + if (truststore2 && + efi_signature_verify_with_sigdb(regs, var_sig, + truststore2, NULL)) { + debug("Verified\n"); + } else { + debug("Verifying variable's signature failed\n"); + goto err; + } + } + + /* finished checking */ + *time = rtc_mktime(&tm); + ret = EFI_SUCCESS; + +err: + efi_sigstore_free(truststore); + efi_sigstore_free(truststore2); + pkcs7_free_message(var_sig); + free(regs); + + return ret; +} +#else +static efi_status_t efi_variable_authenticate(u16 *variable, + const efi_guid_t *vendor, + efi_uintn_t *data_size, + const void **data, u32 given_attr, + u32 *env_attr, u64 *time) +{ + return EFI_SUCCESS; +} +#endif /* CONFIG_EFI_SECURE_BOOT */ + +static efi_status_t efi_get_variable_common(u16 *variable_name, + const efi_guid_t *vendor, + u32 *attributes, + efi_uintn_t *data_size, void *data) { char *native_name; efi_status_t ret; unsigned long in_size; - const char *val, *s; + const char *val = NULL, *s; + u64 time = 0; u32 attr; - EFI_ENTRY("\"%ls\" %pUl %p %p %p", variable_name, vendor, attributes, - data_size, data); - if (!variable_name || !vendor || !data_size) return EFI_EXIT(EFI_INVALID_PARAMETER); ret = efi_to_native(&native_name, variable_name, vendor); if (ret) - return EFI_EXIT(ret); + return ret; EFI_PRINT("get '%s'\n", native_name); val = env_get(native_name); free(native_name); if (!val) - return EFI_EXIT(EFI_NOT_FOUND); + return EFI_NOT_FOUND; - val = parse_attr(val, &attr); + val = parse_attr(val, &attr, &time); in_size = *data_size; @@ -199,7 +628,7 @@ efi_status_t EFIAPI efi_get_variable(u16 *variable_name, /* number of hexadecimal digits must be even */ if (len & 1) - return EFI_EXIT(EFI_DEVICE_ERROR); + return EFI_DEVICE_ERROR; /* two characters per byte: */ len /= 2; @@ -210,11 +639,13 @@ efi_status_t EFIAPI efi_get_variable(u16 *variable_name, goto out; } - if (!data) - return EFI_EXIT(EFI_INVALID_PARAMETER); + if (!data) { + debug("Variable with no data shouldn't exist.\n"); + return EFI_INVALID_PARAMETER; + } if (hex2bin(data, s, len)) - return EFI_EXIT(EFI_DEVICE_ERROR); + return EFI_DEVICE_ERROR; EFI_PRINT("got value: \"%s\"\n", s); } else if ((s = prefix(val, "(utf8)"))) { @@ -227,8 +658,10 @@ efi_status_t EFIAPI efi_get_variable(u16 *variable_name, goto out; } - if (!data) - return EFI_EXIT(EFI_INVALID_PARAMETER); + if (!data) { + debug("Variable with no data shouldn't exist.\n"); + return EFI_INVALID_PARAMETER; + } memcpy(data, s, len); ((char *)data)[len] = '\0'; @@ -236,13 +669,42 @@ efi_status_t EFIAPI efi_get_variable(u16 *variable_name, EFI_PRINT("got value: \"%s\"\n", (char *)data); } else { EFI_PRINT("invalid value: '%s'\n", val); - return EFI_EXIT(EFI_DEVICE_ERROR); + return EFI_DEVICE_ERROR; } out: if (attributes) *attributes = attr & EFI_VARIABLE_MASK; + return ret; +} + +/** + * 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\" %pUl %p %p %p", variable_name, vendor, attributes, + data_size, data); + + ret = efi_get_variable_common(variable_name, vendor, attributes, + data_size, data); return EFI_EXIT(ret); } @@ -274,7 +736,9 @@ static efi_status_t parse_uboot_variable(char *variable, u32 *attributes) { char *guid, *name, *end, c; - unsigned long name_len; + size_t name_len; + efi_uintn_t old_variable_name_size; + u64 time; u16 *p; guid = strchr(variable, '_'); @@ -290,26 +754,29 @@ static efi_status_t parse_uboot_variable(char *variable, return EFI_INVALID_PARAMETER; name_len = end - name; - if (*variable_name_size < (name_len + 1)) { - *variable_name_size = name_len + 1; + old_variable_name_size = *variable_name_size; + *variable_name_size = sizeof(u16) * (name_len + 1); + if (old_variable_name_size < *variable_name_size) return EFI_BUFFER_TOO_SMALL; - } + end++; /* point to value */ /* variable name */ p = variable_name; utf8_utf16_strncpy(&p, name, name_len); variable_name[name_len] = 0; - *variable_name_size = name_len + 1; /* guid */ c = *(name - 1); *(name - 1) = '\0'; /* guid need be null-terminated here */ - uuid_str_to_bin(guid, (unsigned char *)vendor, UUID_STR_FORMAT_GUID); + if (uuid_str_to_bin(guid, (unsigned char *)vendor, + UUID_STR_FORMAT_GUID)) + /* The only error would be EINVAL. */ + return EFI_INVALID_PARAMETER; *(name - 1) = c; /* attributes */ - parse_attr(end, attributes); + parse_attr(end, attributes, &time); return EFI_SUCCESS; } @@ -330,7 +797,7 @@ static efi_status_t parse_uboot_variable(char *variable, */ efi_status_t EFIAPI efi_get_next_variable_name(efi_uintn_t *variable_name_size, u16 *variable_name, - const efi_guid_t *vendor) + efi_guid_t *vendor) { char *native_name, *variable; ssize_t name_len, list_len; @@ -391,7 +858,7 @@ efi_status_t EFIAPI efi_get_next_variable_name(efi_uintn_t *variable_name_size, list_len = hexport_r(&env_htab, '\n', H_MATCH_REGEX | H_MATCH_KEY, &efi_variables_list, 0, 1, regexlist); - /* 1 indicates that no match was found */ + if (list_len <= 1) return EFI_EXIT(EFI_NOT_FOUND); @@ -404,140 +871,259 @@ efi_status_t EFIAPI efi_get_next_variable_name(efi_uintn_t *variable_name_size, 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) +static efi_status_t efi_set_variable_common(u16 *variable_name, + const efi_guid_t *vendor, + u32 attributes, + efi_uintn_t data_size, + const void *data, + bool ro_check) { - char *native_name = NULL, *val = NULL, *s; - const char *old_val; - size_t old_size; - efi_status_t ret = EFI_SUCCESS; + char *native_name = NULL, *old_data = NULL, *val = NULL, *s; + efi_uintn_t old_size; + bool append, delete; + u64 time = 0; u32 attr; - - EFI_ENTRY("\"%ls\" %pUl %x %zu %p", variable_name, vendor, attributes, - data_size, data); + efi_status_t ret = EFI_SUCCESS; if (!variable_name || !*variable_name || !vendor || ((attributes & EFI_VARIABLE_RUNTIME_ACCESS) && !(attributes & EFI_VARIABLE_BOOTSERVICE_ACCESS))) { ret = EFI_INVALID_PARAMETER; - goto out; + goto err; } ret = efi_to_native(&native_name, variable_name, vendor); if (ret) - goto out; - -#define ACCESS_ATTR (EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_BOOTSERVICE_ACCESS) - - old_val = env_get(native_name); - if (old_val) { - old_val = parse_attr(old_val, &attr); - - /* check read-only first */ - if (attr & READ_ONLY) { + goto err; + + /* check if a variable exists */ + old_size = 0; + attr = 0; + ret = efi_get_variable_common(variable_name, vendor, &attr, + &old_size, NULL); + append = !!(attributes & EFI_VARIABLE_APPEND_WRITE); + attributes &= ~(u32)EFI_VARIABLE_APPEND_WRITE; + delete = !append && (!data_size || !attributes); + + /* check attributes */ + if (old_size) { + if (ro_check && (attr & READ_ONLY)) { ret = EFI_WRITE_PROTECTED; - goto out; + goto err; } - if ((data_size == 0) || !(attributes & ACCESS_ATTR)) { - /* delete the variable: */ - env_set(native_name, NULL); - ret = EFI_SUCCESS; - goto out; + /* attributes won't be changed */ + if (!delete && + ((ro_check && attr != attributes) || + (!ro_check && ((attr & ~(u32)READ_ONLY) + != (attributes & ~(u32)READ_ONLY))))) { + ret = EFI_INVALID_PARAMETER; + goto err; + } + } else { + if (delete || append) { + /* + * Trying to delete or to update a non-existent + * variable. + */ + ret = EFI_NOT_FOUND; + goto err; } + } - /* attributes won't be changed */ - if (attr != (attributes & ~EFI_VARIABLE_APPEND_WRITE)) { + if (((!u16_strcmp(variable_name, L"PK") || + !u16_strcmp(variable_name, L"KEK")) && + !guidcmp(vendor, &efi_global_variable_guid)) || + ((!u16_strcmp(variable_name, L"db") || + !u16_strcmp(variable_name, L"dbx")) && + !guidcmp(vendor, &efi_guid_image_security_database))) { + /* authentication is mandatory */ + if (!(attributes & + EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS)) { + debug("%ls: AUTHENTICATED_WRITE_ACCESS required\n", + variable_name); ret = EFI_INVALID_PARAMETER; - goto out; + goto err; } + } - if (attributes & EFI_VARIABLE_APPEND_WRITE) { - if (!prefix(old_val, "(blob)")) { - ret = EFI_DEVICE_ERROR; - goto out; - } - old_size = strlen(old_val); - } else { - old_size = 0; + /* authenticate a variable */ + if (IS_ENABLED(CONFIG_EFI_SECURE_BOOT)) { + if (attributes & EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS) { + ret = EFI_INVALID_PARAMETER; + goto err; + } + if (attributes & + EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS) { + ret = efi_variable_authenticate(variable_name, vendor, + &data_size, &data, + attributes, &attr, + &time); + if (ret != EFI_SUCCESS) + goto err; + + /* last chance to check for delete */ + if (!data_size) + delete = true; } } else { - if ((data_size == 0) || !(attributes & ACCESS_ATTR) || - (attributes & EFI_VARIABLE_APPEND_WRITE)) { - /* delete, but nothing to do */ - ret = EFI_NOT_FOUND; - goto out; + if (attributes & + (EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS | + EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS)) { + debug("Secure boot is not configured\n"); + ret = EFI_INVALID_PARAMETER; + goto err; } + } + + /* delete a variable */ + if (delete) { + /* !old_size case has been handled before */ + val = NULL; + ret = EFI_SUCCESS; + goto out; + } + if (append) { + old_data = malloc(old_size); + if (!old_data) { + ret = EFI_OUT_OF_RESOURCES; + goto err; + } + ret = efi_get_variable_common(variable_name, vendor, + &attr, &old_size, old_data); + if (ret != EFI_SUCCESS) + goto err; + } else { old_size = 0; } - val = malloc(old_size + 2 * data_size - + strlen("{ro,run,boot,nv}(blob)") + 1); + val = malloc(2 * old_size + 2 * data_size + + strlen("{ro,run,boot,nv,time=0123456701234567}(blob)") + + 1); if (!val) { ret = EFI_OUT_OF_RESOURCES; - goto out; + goto err; } s = val; - /* store attributes */ - attributes &= (EFI_VARIABLE_NON_VOLATILE | + /* + * store attributes + */ + attributes &= (READ_ONLY | + EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | - EFI_VARIABLE_RUNTIME_ACCESS); + EFI_VARIABLE_RUNTIME_ACCESS | + EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS); s += sprintf(s, "{"); while (attributes) { - u32 attr = 1 << (ffs(attributes) - 1); + attr = 1 << (ffs(attributes) - 1); - if (attr == EFI_VARIABLE_NON_VOLATILE) + if (attr == READ_ONLY) { + s += sprintf(s, "ro"); + } else if (attr == EFI_VARIABLE_NON_VOLATILE) { s += sprintf(s, "nv"); - else if (attr == EFI_VARIABLE_BOOTSERVICE_ACCESS) + } else if (attr == EFI_VARIABLE_BOOTSERVICE_ACCESS) { s += sprintf(s, "boot"); - else if (attr == EFI_VARIABLE_RUNTIME_ACCESS) + } else if (attr == EFI_VARIABLE_RUNTIME_ACCESS) { s += sprintf(s, "run"); + } else if (attr == + EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS) { + s += sprintf(s, "time="); + s = bin2hex(s, (u8 *)&time, sizeof(time)); + } attributes &= ~attr; if (attributes) s += sprintf(s, ","); } s += sprintf(s, "}"); - - if (old_size) - /* APPEND_WRITE */ - s += sprintf(s, old_val); - else - s += sprintf(s, "(blob)"); + s += sprintf(s, "(blob)"); /* store payload: */ + if (append) + s = bin2hex(s, old_data, old_size); s = bin2hex(s, data, data_size); *s = '\0'; EFI_PRINT("setting: %s=%s\n", native_name, val); - if (env_set(native_name, val)) +out: + if (env_set(native_name, val)) { ret = EFI_DEVICE_ERROR; + } else { + bool vendor_keys_modified = false; + + if ((u16_strcmp(variable_name, L"PK") == 0 && + guidcmp(vendor, &efi_global_variable_guid) == 0)) { + ret = efi_transfer_secure_state( + (delete ? EFI_MODE_SETUP : + EFI_MODE_USER)); + if (ret != EFI_SUCCESS) + goto err; + + if (efi_secure_mode != EFI_MODE_SETUP) + vendor_keys_modified = true; + } else if ((u16_strcmp(variable_name, L"KEK") == 0 && + guidcmp(vendor, &efi_global_variable_guid) == 0)) { + if (efi_secure_mode != EFI_MODE_SETUP) + vendor_keys_modified = true; + } -out: + /* update VendorKeys */ + if (vendor_keys_modified & efi_vendor_keys) { + efi_vendor_keys = 0; + ret = efi_set_variable_common( + L"VendorKeys", + &efi_global_variable_guid, + EFI_VARIABLE_BOOTSERVICE_ACCESS + | EFI_VARIABLE_RUNTIME_ACCESS + | READ_ONLY, + sizeof(efi_vendor_keys), + &efi_vendor_keys, + false); + } else { + ret = EFI_SUCCESS; + } + } + +err: free(native_name); + free(old_data); free(val); - return EFI_EXIT(ret); + return 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_ENTRY("\"%ls\" %pUl %x %zu %p", variable_name, vendor, attributes, + data_size, data); + + /* READ_ONLY bit is not part of API */ + attributes &= ~(u32)READ_ONLY; + + return EFI_EXIT(efi_set_variable_common(variable_name, vendor, + attributes, data_size, data, + true)); } /** @@ -595,7 +1181,7 @@ efi_get_variable_runtime(u16 *variable_name, const efi_guid_t *vendor, */ static efi_status_t __efi_runtime EFIAPI efi_get_next_variable_name_runtime(efi_uintn_t *variable_name_size, - u16 *variable_name, const efi_guid_t *vendor) + u16 *variable_name, efi_guid_t *vendor) { return EFI_UNSUPPORTED; } @@ -637,5 +1223,9 @@ void efi_variables_boot_exit_notify(void) */ efi_status_t efi_init_variables(void) { - return EFI_SUCCESS; + efi_status_t ret; + + ret = efi_init_secure_state(); + + return ret; }