From patchwork Fri Mar 27 05:27:58 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Heinrich Schuchardt X-Patchwork-Id: 244408 List-Id: U-Boot discussion From: xypron.glpk at gmx.de (Heinrich Schuchardt) Date: Fri, 27 Mar 2020 06:27:58 +0100 Subject: [PATCH 14/16] efi_loader: use memory based variable storage In-Reply-To: <20200327052800.11022-1-xypron.glpk@gmx.de> References: <20200327052800.11022-1-xypron.glpk@gmx.de> Message-ID: <20200327052800.11022-15-xypron.glpk@gmx.de> Saving UEFI variable as encoded U-Boot environment variables does not allow implement run-time support. Use a memory buffer for storing UEFI variables. Signed-off-by: Heinrich Schuchardt --- lib/efi_loader/efi_variable.c | 584 +++++++---------------------- lib/efi_loader/efi_variables_mem.c | 7 + 2 files changed, 147 insertions(+), 444 deletions(-) -- 2.25.1 diff --git a/lib/efi_loader/efi_variable.c b/lib/efi_loader/efi_variable.c index 952a0a0db7..7c39542968 100644 --- a/lib/efi_loader/efi_variable.c +++ b/lib/efi_loader/efi_variable.c @@ -2,152 +2,15 @@ /* * UEFI runtime variable services * - * Copyright (c) 2017 Rob Clark + * Copyright (c) 2019 Heinrich Schuchardt */ #include #include #include -#include -#include -#include -#include -#include #define READ_ONLY BIT(31) -/* - * Mapping between EFI variables and u-boot variables: - * - * efi_$guid_$varname = {attributes}(type)value - * - * For example: - * - * efi_8be4df61-93ca-11d2-aa0d-00e098032b8c_OsIndicationsSupported= - * "{ro,boot,run}(blob)0000000000000000" - * efi_8be4df61-93ca-11d2-aa0d-00e098032b8c_BootOrder= - * "(blob)00010000" - * - * The attributes are a comma separated list of these possible - * attributes: - * - * + ro - read-only - * + boot - boot-services access - * + run - runtime access - * - * NOTE: with current implementation, no variables are available after - * ExitBootServices, and all are persisted (if possible). - * - * If not specified, the attributes default to "{boot}". - * - * The required type is one of: - * - * + utf8 - raw utf8 string - * + blob - arbitrary length hex string - * - * Maybe a utf16 type would be useful to for a string value to be auto - * converted to utf16? - */ - -#define PREFIX_LEN (strlen("efi_xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx_")) - -/** - * efi_to_native() - convert the UEFI variable name and vendor GUID to U-Boot - * variable name - * - * The U-Boot variable name is a concatenation of prefix 'efi', the hexstring - * encoded vendor GUID, and the UTF-8 encoded UEFI variable name separated by - * underscores, e.g. 'efi_8be4df61-93ca-11d2-aa0d-00e098032b8c_BootOrder'. - * - * @native: pointer to pointer to U-Boot variable name - * @variable_name: UEFI variable name - * @vendor: vendor GUID - * Return: status code - */ -static efi_status_t efi_to_native(char **native, const u16 *variable_name, - const efi_guid_t *vendor) -{ - size_t len; - char *pos; - - len = PREFIX_LEN + utf16_utf8_strlen(variable_name) + 1; - *native = malloc(len); - if (!*native) - return EFI_OUT_OF_RESOURCES; - - pos = *native; - pos += sprintf(pos, "efi_%pUl_", vendor); - utf16_utf8_strcpy(&pos, variable_name); - - return EFI_SUCCESS; -} - -/** - * prefix() - skip over prefix - * - * Skip over a prefix string. - * - * @str: string with prefix - * @prefix: prefix string - * Return: string without prefix, or NULL if prefix not found - */ -static const char *prefix(const char *str, const char *prefix) -{ - size_t n = strlen(prefix); - if (!strncmp(prefix, str, n)) - return str + n; - return NULL; -} - -/** - * parse_attr() - decode attributes part of variable value - * - * Convert the string encoded attributes of a UEFI variable to a bit mask. - * TODO: Several attributes are not supported. - * - * @str: value of U-Boot variable - * @attrp: pointer to UEFI attributes - * Return: pointer to remainder of U-Boot variable value - */ -static const char *parse_attr(const char *str, u32 *attrp) -{ - u32 attr = 0; - char sep = '{'; - - if (*str != '{') { - *attrp = EFI_VARIABLE_BOOTSERVICE_ACCESS; - return str; - } - - while (*str == sep) { - const char *s; - - str++; - - if ((s = prefix(str, "ro"))) { - attr |= READ_ONLY; - } else if ((s = prefix(str, "nv"))) { - attr |= EFI_VARIABLE_NON_VOLATILE; - } else if ((s = prefix(str, "boot"))) { - attr |= EFI_VARIABLE_BOOTSERVICE_ACCESS; - } else if ((s = prefix(str, "run"))) { - attr |= EFI_VARIABLE_RUNTIME_ACCESS; - } else { - printf("invalid attribute: %s\n", str); - break; - } - - str = s; - sep = ','; - } - - str++; - - *attrp = attr; - - return str; -} - /** * efi_get_variable() - retrieve value of a UEFI variable * @@ -167,11 +30,18 @@ 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); - return EFI_EXIT(efi_get_variable_int(variable_name, vendor, attributes, - data_size, data)); + ret = efi_get_variable_int(variable_name, vendor, attributes, data_size, + data); + /* Remove read-only bit */ + if (attributes) + *attributes &= EFI_VARIABLE_MASK; + + return EFI_EXIT(ret); } /** @@ -187,152 +57,37 @@ efi_status_t EFIAPI efi_get_variable(u16 *variable_name, * @data: buffer to which the variable value is copied * Return: status code */ -efi_status_t efi_get_variable_int(u16 *variable_name, - const efi_guid_t *vendor, u32 *attributes, - efi_uintn_t *data_size, void *data) +efi_status_t __efi_runtime +efi_get_variable_int(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; - u32 attr; - + efi_uintn_t old_size; + struct efi_var_entry *var; + u16 *pdata; if (!variable_name || !vendor || !data_size) return EFI_INVALID_PARAMETER; - ret = efi_to_native(&native_name, variable_name, vendor); - if (ret) - return ret; - - EFI_PRINT("get '%s'\n", native_name); - - val = env_get(native_name); - free(native_name); - if (!val) + var = efi_var_mem_find(vendor, variable_name, NULL); + if (!var) return EFI_NOT_FOUND; - val = parse_attr(val, &attr); - - in_size = *data_size; - - if ((s = prefix(val, "(blob)"))) { - size_t len = strlen(s); - - /* number of hexadecimal digits must be even */ - if (len & 1) - return EFI_DEVICE_ERROR; - - /* two characters per byte: */ - len /= 2; - *data_size = len; - - if (in_size < len) { - ret = EFI_BUFFER_TOO_SMALL; - goto out; - } - - if (!data) - return EFI_INVALID_PARAMETER; - - if (hex2bin(data, s, len)) - return EFI_DEVICE_ERROR; - - EFI_PRINT("got value: \"%s\"\n", s); - } else if ((s = prefix(val, "(utf8)"))) { - unsigned len = strlen(s) + 1; - - *data_size = len; - - if (in_size < len) { - ret = EFI_BUFFER_TOO_SMALL; - goto out; - } - - if (!data) - return EFI_INVALID_PARAMETER; - - memcpy(data, s, len); - ((char *)data)[len] = '\0'; - - EFI_PRINT("got value: \"%s\"\n", (char *)data); - } else { - EFI_PRINT("invalid value: '%s'\n", val); - return EFI_DEVICE_ERROR; - } - -out: if (attributes) - *attributes = attr & EFI_VARIABLE_MASK; + *attributes = var->attr; - return ret; -} - -static char *efi_variables_list; -static char *efi_cur_variable; - -/** - * parse_uboot_variable() - parse a u-boot variable and get uefi-related - * information - * @variable: whole data of u-boot variable (ie. name=value) - * @variable_name_size: size of variable_name buffer in byte - * @variable_name: name of uefi variable in u16, null-terminated - * @vendor: vendor's guid - * @attributes: attributes - * - * A uefi variable is encoded into a u-boot variable as described above. - * This function parses such a u-boot variable and retrieve uefi-related - * information into respective parameters. In return, variable_name_size - * is the size of variable name including NULL. - * - * Return: EFI_SUCCESS if parsing is OK, EFI_NOT_FOUND when - * the entire variable list has been returned, - * otherwise non-zero status code - */ -static efi_status_t parse_uboot_variable(char *variable, - efi_uintn_t *variable_name_size, - u16 *variable_name, - const efi_guid_t *vendor, - u32 *attributes) -{ - char *guid, *name, *end, c; - size_t name_len; - efi_uintn_t old_variable_name_size; - u16 *p; - - guid = strchr(variable, '_'); - if (!guid) - return EFI_INVALID_PARAMETER; - guid++; - name = strchr(guid, '_'); - if (!name) - return EFI_INVALID_PARAMETER; - name++; - end = strchr(name, '='); - if (!end) - return EFI_INVALID_PARAMETER; - - name_len = end - name; - old_variable_name_size = *variable_name_size; - *variable_name_size = sizeof(u16) * (name_len + 1); - if (old_variable_name_size < *variable_name_size) + old_size = *data_size; + *data_size = var->length; + if (old_size < var->length) 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; + if (!data) + return EFI_INVALID_PARAMETER; - /* 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); - *(name - 1) = c; + for (pdata = var->name; *pdata; ++pdata) + ; + ++pdata; - /* attributes */ - parse_attr(end, attributes); + efi_var_mem_memcpy(data, pdata, var->length); return EFI_SUCCESS; } @@ -378,78 +133,38 @@ efi_status_t EFIAPI efi_get_next_variable_name(efi_uintn_t *variable_name_size, * * Return: status code */ -efi_status_t efi_get_next_variable_name_int(efi_uintn_t *variable_name_size, - u16 *variable_name, - efi_guid_t *vendor) +efi_status_t __efi_runtime +efi_get_next_variable_name_int(efi_uintn_t *variable_name_size, + u16 *variable_name, efi_guid_t *vendor) { - char *native_name, *variable; - ssize_t name_len, list_len; - char regex[256]; - char * const regexlist[] = {regex}; - u32 attributes; - int i; - efi_status_t ret; + struct efi_var_entry *var; + efi_uintn_t old_size; + u16 *pdata; if (!variable_name_size || !variable_name || !vendor) return EFI_INVALID_PARAMETER; - if (variable_name[0]) { - /* check null-terminated string */ - for (i = 0; i < *variable_name_size; i++) - if (!variable_name[i]) - break; - if (i >= *variable_name_size) - return EFI_INVALID_PARAMETER; - - /* search for the last-returned variable */ - ret = efi_to_native(&native_name, variable_name, vendor); - if (ret) - return ret; - - name_len = strlen(native_name); - for (variable = efi_variables_list; variable && *variable;) { - if (!strncmp(variable, native_name, name_len) && - variable[name_len] == '=') - break; + if (!variable_name[0]) + var = NULL; - variable = strchr(variable, '\n'); - if (variable) - variable++; - } + efi_var_mem_find(vendor, variable_name, &var); + if (!var) + return EFI_NOT_FOUND; - free(native_name); - if (!(variable && *variable)) - return EFI_INVALID_PARAMETER; + for (pdata = var->name; *pdata; ++pdata) + ; + ++pdata; - /* next variable */ - variable = strchr(variable, '\n'); - if (variable) - variable++; - if (!(variable && *variable)) - return EFI_NOT_FOUND; - } else { - /* - *new search: free a list used in the previous search - */ - free(efi_variables_list); - efi_variables_list = NULL; - efi_cur_variable = NULL; - - snprintf(regex, 256, "efi_.*-.*-.*-.*-.*_.*"); - 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_NOT_FOUND; + old_size = *variable_name_size; + *variable_name_size = (uintptr_t)pdata - (uintptr_t)var->name; - variable = efi_variables_list; - } + if (old_size < *variable_name_size) + return EFI_BUFFER_TOO_SMALL; - ret = parse_uboot_variable(variable, variable_name_size, variable_name, - vendor, &attributes); + efi_var_mem_memcpy(variable_name, var->name, *variable_name_size); + efi_var_mem_memcpy(vendor, &var->guid, sizeof(efi_guid_t)); - return ret; + return EFI_SUCCESS; } /** @@ -471,17 +186,26 @@ 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\" %pUl %x %zu %p", variable_name, vendor, attributes, data_size, data); - return EFI_EXIT(efi_set_variable_int(variable_name, vendor, attributes, - data_size, data)); + ret = efi_set_variable_int(variable_name, vendor, attributes, + data_size, data); + + /* Write non-volatile EFI variables to file */ + if (ret == EFI_SUCCESS && (attributes & EFI_VARIABLE_NON_VOLATILE) && + efi_obj_list_initialized == EFI_SUCCESS) + efi_var_to_file(); + + return EFI_EXIT(ret); } /** - * efi_set_variable_int() - set value of a UEFI variable internal + * efi_set_variable_rt_int() - set value of a UEFI variable internal * - * This function can be used to set a UEFI variable without using EFI_CALL(). + * This implements SetVariable() without persistence. * * @variable_name: name of the variable * @vendor: vendor GUID @@ -490,130 +214,91 @@ efi_status_t EFIAPI efi_set_variable(u16 *variable_name, * @data: buffer with the variable value * Return: status code */ -efi_status_t efi_set_variable_int(u16 *variable_name, - const efi_guid_t *vendor, u32 attributes, - efi_uintn_t data_size, const void *data) +static efi_status_t __efi_runtime +efi_set_variable_rt_int(u16 *variable_name, const efi_guid_t *vendor, + u32 attributes, efi_uintn_t data_size, const void *data) { - char *native_name = NULL, *val = NULL, *s; - const char *old_val; - size_t old_size; - efi_status_t ret = EFI_SUCCESS; - u32 attr; - + struct efi_var_entry *var; + efi_uintn_t ret; + bool append; if (!variable_name || !*variable_name || !vendor || ((attributes & EFI_VARIABLE_RUNTIME_ACCESS) && !(attributes & EFI_VARIABLE_BOOTSERVICE_ACCESS))) { - ret = EFI_INVALID_PARAMETER; - goto out; + return EFI_INVALID_PARAMETER; } - ret = efi_to_native(&native_name, variable_name, vendor); - if (ret) - goto out; - - old_val = env_get(native_name); - if (old_val) { - old_val = parse_attr(old_val, &attr); - - /* check read-only first */ - if (attr & READ_ONLY) { - ret = EFI_WRITE_PROTECTED; - goto out; - } - - if ((data_size == 0 && - !(attributes & EFI_VARIABLE_APPEND_WRITE)) || - !attributes) { - /* delete the variable: */ - env_set(native_name, NULL); - ret = EFI_SUCCESS; - goto out; - } - - /* attributes won't be changed */ - if (attr != (attributes & ~EFI_VARIABLE_APPEND_WRITE)) { - ret = EFI_INVALID_PARAMETER; + var = efi_var_mem_find(vendor, variable_name, NULL); + append = attributes & EFI_VARIABLE_APPEND_WRITE; + attributes &= ~EFI_VARIABLE_APPEND_WRITE; + + /* + * TODO: + * * authentication + * * checks for EFI_VARIABLE_HARDWARE_ERROR_RECORD + */ + if (var) { + if (attributes && var->attr != attributes) + return EFI_INVALID_PARAMETER; + if (var->attr & READ_ONLY) + return EFI_WRITE_PROTECTED; + if ((!attributes || !data_size) && !append) { + attributes = var->attr; goto out; } - - 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; - } } else { - if (data_size == 0 || !attributes || - (attributes & EFI_VARIABLE_APPEND_WRITE)) { - /* - * Trying to delete or to update a non-existent - * variable. - */ - ret = EFI_NOT_FOUND; - goto out; - } - - old_size = 0; + if (!attributes || !data_size || append) + return EFI_NOT_FOUND; } - val = malloc(old_size + 2 * data_size - + strlen("{ro,run,boot,nv}(blob)") + 1); - if (!val) { - ret = EFI_OUT_OF_RESOURCES; - goto out; - } + if (append) { + u16 *old_data = var->name; + + for (; *old_data; ++old_data) + ; + ++old_data; - s = val; - - /* store attributes */ - attributes &= (EFI_VARIABLE_NON_VOLATILE | - EFI_VARIABLE_BOOTSERVICE_ACCESS | - EFI_VARIABLE_RUNTIME_ACCESS); - s += sprintf(s, "{"); - for (u32 attr_rem = attributes; attr_rem;) { - u32 attr = 1 << (ffs(attr_rem) - 1); - - if (attr == EFI_VARIABLE_NON_VOLATILE) - s += sprintf(s, "nv"); - else if (attr == EFI_VARIABLE_BOOTSERVICE_ACCESS) - s += sprintf(s, "boot"); - else if (attr == EFI_VARIABLE_RUNTIME_ACCESS) - s += sprintf(s, "run"); - - attr_rem &= ~attr; - if (attr_rem) - s += sprintf(s, ","); + ret = efi_var_mem_ins(variable_name, vendor, attributes, + var->length, old_data, data_size, data); + } else { + ret = efi_var_mem_ins(variable_name, vendor, attributes, + data_size, data, 0, NULL); } - s += sprintf(s, "}"); + if (ret != EFI_SUCCESS) + return ret; - if (old_size) - /* APPEND_WRITE */ - s += sprintf(s, old_val); - else - s += sprintf(s, "(blob)"); +out: + efi_var_mem_del(var); - /* store payload: */ - s = bin2hex(s, data, data_size); - *s = '\0'; + return EFI_SUCCESS; +} - EFI_PRINT("setting: %s=%s\n", native_name, val); +/** + * efi_set_variable_int() - set value of a UEFI variable internal + * + * This function can be used to set a UEFI variable without using EFI_CALL(). + * + * @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 +efi_set_variable_int(u16 *variable_name, const efi_guid_t *vendor, + u32 attributes, efi_uintn_t data_size, const void *data) +{ + efi_status_t ret; - if (env_set(native_name, val)) - ret = EFI_DEVICE_ERROR; + ret = efi_set_variable_rt_int(variable_name, vendor, attributes, + data_size, data); /* Write non-volatile EFI variables to file */ - if (attributes && EFI_VARIABLE_NON_VOLATILE && - ret == EFI_SUCCESS && efi_obj_list_initialized == EFI_SUCCESS) + if (ret == EFI_SUCCESS && (attributes & EFI_VARIABLE_NON_VOLATILE) && + efi_obj_list_initialized == EFI_SUCCESS) efi_var_to_file(); -out: - free(native_name); - free(val); - return ret; } @@ -629,7 +314,7 @@ out: * 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 + * @remaining_variable_storage_size: remaining size of storage for the * selected variable types * @maximum_variable_size: maximum size of a variable of the * selected type @@ -641,6 +326,14 @@ efi_status_t __efi_runtime EFIAPI efi_query_variable_info( u64 *remaining_variable_storage_size, u64 *maximum_variable_size) { + /* + *maximum_variable_storage_size = EFI_VAR_BUF_SIZE - + sizeof(struct efi_var_file); + *remaining_variable_storage_size = efi_var_mem_free(); + *maximum_variable_size = EFI_VAR_BUF_SIZE - + sizeof(struct efi_var_file) - + sizeof(struct efi_var_entry); + */ return EFI_UNSUPPORTED; } @@ -700,9 +393,6 @@ efi_set_variable_runtime(u16 *variable_name, const efi_guid_t *vendor, */ void efi_variables_boot_exit_notify(void) { - /* Write non-volatile EFI variables to file */ - efi_var_to_file(); - /* Switch variable services functions to runtime version */ efi_runtime_services.get_variable = efi_get_variable_runtime; efi_runtime_services.get_next_variable_name = @@ -718,5 +408,11 @@ void efi_variables_boot_exit_notify(void) */ efi_status_t efi_init_variables(void) { + efi_status_t ret; + + ret = efi_var_mem_init(); + if (ret != EFI_SUCCESS) + return ret; + return efi_var_from_file(); } diff --git a/lib/efi_loader/efi_variables_mem.c b/lib/efi_loader/efi_variables_mem.c index f70cc65f8b..939fbaa972 100644 --- a/lib/efi_loader/efi_variables_mem.c +++ b/lib/efi_loader/efi_variables_mem.c @@ -263,8 +263,15 @@ static void efi_var_mem_bs_del(void) static void EFIAPI __efi_runtime efi_var_mem_notify_exit_boot_services(struct efi_event *event, void *context) { + EFI_ENTRY("%p, %p", event, context); + + /* Write non-volatile EFI variables to file */ + efi_var_to_file(); + /* Delete boot service only variables */ efi_var_mem_bs_del(); + + EFI_EXIT(EFI_SUCCESS); } /**