| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Integrate UEFI variables to u-boot env interface |
| * |
| * Copyright (c) 2018 AKASHI Takahiro, Linaro Limited |
| */ |
| |
| #include <charset.h> |
| #include <common.h> |
| #include <command.h> |
| #include <efi_loader.h> |
| #include <env.h> |
| #include <exports.h> |
| #include <hexdump.h> |
| #include <malloc.h> |
| #include <mapmem.h> |
| #include <uuid.h> |
| #include <linux/kernel.h> |
| |
| /* |
| * From efi_variable.c, |
| * |
| * Mapping between UEFI variables and u-boot variables: |
| * |
| * efi_$guid_$varname = {attributes}(type)value |
| */ |
| |
| static const struct { |
| u32 mask; |
| char *text; |
| } efi_var_attrs[] = { |
| {EFI_VARIABLE_NON_VOLATILE, "NV"}, |
| {EFI_VARIABLE_BOOTSERVICE_ACCESS, "BS"}, |
| {EFI_VARIABLE_RUNTIME_ACCESS, "RT"}, |
| {EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS, "AW"}, |
| {EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS, "AT"}, |
| }; |
| |
| static const struct { |
| efi_guid_t guid; |
| char *text; |
| } efi_guid_text[] = { |
| /* signature database */ |
| {EFI_GLOBAL_VARIABLE_GUID, "EFI_GLOBAL_VARIABLE_GUID"}, |
| {EFI_IMAGE_SECURITY_DATABASE_GUID, "EFI_IMAGE_SECURITY_DATABASE_GUID"}, |
| /* certificate type */ |
| {EFI_CERT_SHA256_GUID, "EFI_CERT_SHA256_GUID"}, |
| {EFI_CERT_X509_GUID, "EFI_CERT_X509_GUID"}, |
| {EFI_CERT_TYPE_PKCS7_GUID, "EFI_CERT_TYPE_PKCS7_GUID"}, |
| }; |
| |
| /* "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" */ |
| static char unknown_guid[37]; |
| |
| /** |
| * efi_guid_to_str() - convert guid to readable name |
| * |
| * @guid: GUID |
| * Return: string for GUID |
| * |
| * convert guid to readable name |
| */ |
| static const char *efi_guid_to_str(const efi_guid_t *guid) |
| { |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(efi_guid_text); i++) |
| if (!guidcmp(guid, &efi_guid_text[i].guid)) |
| return efi_guid_text[i].text; |
| |
| uuid_bin_to_str((unsigned char *)guid->b, unknown_guid, |
| UUID_STR_FORMAT_GUID); |
| |
| return unknown_guid; |
| } |
| |
| /** |
| * efi_dump_single_var() - show information about a UEFI variable |
| * |
| * @name: Name of the variable |
| * @guid: Vendor GUID |
| * @verbose: if true, dump data |
| * |
| * Show information encoded in one UEFI variable |
| */ |
| static void efi_dump_single_var(u16 *name, const efi_guid_t *guid, bool verbose) |
| { |
| u32 attributes; |
| u8 *data; |
| efi_uintn_t size; |
| int count, i; |
| efi_status_t ret; |
| |
| data = NULL; |
| size = 0; |
| ret = EFI_CALL(efi_get_variable(name, guid, &attributes, &size, data)); |
| if (ret == EFI_BUFFER_TOO_SMALL) { |
| data = malloc(size); |
| if (!data) |
| goto out; |
| |
| ret = EFI_CALL(efi_get_variable(name, guid, &attributes, &size, |
| data)); |
| } |
| if (ret == EFI_NOT_FOUND) { |
| printf("Error: \"%ls\" not defined\n", name); |
| goto out; |
| } |
| if (ret != EFI_SUCCESS) |
| goto out; |
| |
| printf("%ls:\n %s:", name, efi_guid_to_str(guid)); |
| for (count = 0, i = 0; i < ARRAY_SIZE(efi_var_attrs); i++) |
| if (attributes & efi_var_attrs[i].mask) { |
| if (count) |
| putc('|'); |
| else |
| putc(' '); |
| count++; |
| puts(efi_var_attrs[i].text); |
| } |
| printf(", DataSize = 0x%zx\n", size); |
| if (verbose) |
| print_hex_dump(" ", DUMP_PREFIX_OFFSET, 16, 1, |
| data, size, true); |
| |
| out: |
| free(data); |
| } |
| |
| /** |
| * efi_dump_vars() - show information about named UEFI variables |
| * |
| * @argc: Number of arguments (variables) |
| * @argv: Argument (variable name) array |
| * @verbose: if true, dump data |
| * Return: CMD_RET_SUCCESS on success, or CMD_RET_RET_FAILURE |
| * |
| * Show information encoded in named UEFI variables |
| */ |
| static int efi_dump_vars(int argc, char * const argv[], |
| const efi_guid_t *guid, bool verbose) |
| { |
| u16 *var_name16, *p; |
| efi_uintn_t buf_size, size; |
| |
| buf_size = 128; |
| var_name16 = malloc(buf_size); |
| if (!var_name16) |
| return CMD_RET_FAILURE; |
| |
| for (; argc > 0; argc--, argv++) { |
| size = (utf8_utf16_strlen(argv[0]) + 1) * sizeof(u16); |
| if (buf_size < size) { |
| buf_size = size; |
| p = realloc(var_name16, buf_size); |
| if (!p) { |
| free(var_name16); |
| return CMD_RET_FAILURE; |
| } |
| var_name16 = p; |
| } |
| |
| p = var_name16; |
| utf8_utf16_strcpy(&p, argv[0]); |
| |
| efi_dump_single_var(var_name16, guid, verbose); |
| } |
| |
| free(var_name16); |
| |
| return CMD_RET_SUCCESS; |
| } |
| |
| static bool match_name(int argc, char * const argv[], u16 *var_name16) |
| { |
| char *buf, *p; |
| size_t buflen; |
| int i; |
| bool result = false; |
| |
| buflen = utf16_utf8_strlen(var_name16) + 1; |
| buf = calloc(1, buflen); |
| if (!buf) |
| return result; |
| |
| p = buf; |
| utf16_utf8_strcpy(&p, var_name16); |
| |
| for (i = 0; i < argc; argc--, argv++) { |
| if (!strcmp(buf, argv[i])) { |
| result = true; |
| goto out; |
| } |
| } |
| |
| out: |
| free(buf); |
| |
| return result; |
| } |
| |
| /** |
| * efi_dump_var_all() - show information about all the UEFI variables |
| * |
| * @argc: Number of arguments (variables) |
| * @argv: Argument (variable name) array |
| * @verbose: if true, dump data |
| * Return: CMD_RET_SUCCESS on success, or CMD_RET_RET_FAILURE |
| * |
| * Show information encoded in all the UEFI variables |
| */ |
| static int efi_dump_var_all(int argc, char * const argv[], |
| const efi_guid_t *guid_p, bool verbose) |
| { |
| u16 *var_name16, *p; |
| efi_uintn_t buf_size, size; |
| efi_guid_t guid; |
| efi_status_t ret; |
| |
| if (argc && guid_p) |
| /* simplified case */ |
| return efi_dump_vars(argc, argv, guid_p, verbose); |
| |
| buf_size = 128; |
| var_name16 = malloc(buf_size); |
| if (!var_name16) |
| return CMD_RET_FAILURE; |
| |
| var_name16[0] = 0; |
| for (;;) { |
| size = buf_size; |
| ret = EFI_CALL(efi_get_next_variable_name(&size, var_name16, |
| &guid)); |
| if (ret == EFI_NOT_FOUND) |
| break; |
| if (ret == EFI_BUFFER_TOO_SMALL) { |
| buf_size = size; |
| p = realloc(var_name16, buf_size); |
| if (!p) { |
| free(var_name16); |
| return CMD_RET_FAILURE; |
| } |
| var_name16 = p; |
| ret = EFI_CALL(efi_get_next_variable_name(&size, |
| var_name16, |
| &guid)); |
| } |
| if (ret != EFI_SUCCESS) { |
| free(var_name16); |
| return CMD_RET_FAILURE; |
| } |
| |
| if ((!guid_p || !guidcmp(guid_p, &guid)) && |
| (!argc || match_name(argc, argv, var_name16))) |
| efi_dump_single_var(var_name16, &guid, verbose); |
| } |
| |
| free(var_name16); |
| |
| return CMD_RET_SUCCESS; |
| } |
| |
| /** |
| * do_env_print_efi() - show information about UEFI variables |
| * |
| * @cmdtp: Command table |
| * @flag: Command flag |
| * @argc: Number of arguments |
| * @argv: Argument array |
| * Return: CMD_RET_SUCCESS on success, or CMD_RET_RET_FAILURE |
| * |
| * This function is for "env print -e" or "printenv -e" command: |
| * => env print -e [-n] [-guid <guid> | -all] [var [...]] |
| * If one or more variable names are specified, show information |
| * named UEFI variables, otherwise show all the UEFI variables. |
| */ |
| int do_env_print_efi(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) |
| { |
| efi_guid_t guid; |
| const efi_guid_t *guid_p; |
| bool default_guid, guid_any, verbose; |
| efi_status_t ret; |
| |
| /* Initialize EFI drivers */ |
| ret = efi_init_obj_list(); |
| if (ret != EFI_SUCCESS) { |
| printf("Error: Cannot initialize UEFI sub-system, r = %lu\n", |
| ret & ~EFI_ERROR_MASK); |
| return CMD_RET_FAILURE; |
| } |
| |
| default_guid = true; |
| guid_any = false; |
| verbose = true; |
| for (argc--, argv++; argc > 0 && argv[0][0] == '-'; argc--, argv++) { |
| if (!strcmp(argv[0], "-guid")) { |
| if (argc == 1) |
| return CMD_RET_USAGE; |
| |
| /* -a already specified */ |
| if (!default_guid && guid_any) |
| return CMD_RET_USAGE; |
| |
| argc--; |
| argv++; |
| if (uuid_str_to_bin(argv[0], guid.b, |
| UUID_STR_FORMAT_GUID)) |
| return CMD_RET_USAGE; |
| default_guid = false; |
| } else if (!strcmp(argv[0], "-all")) { |
| /* -guid already specified */ |
| if (!default_guid && !guid_any) |
| return CMD_RET_USAGE; |
| |
| guid_any = true; |
| default_guid = false; |
| } else if (!strcmp(argv[0], "-n")) { |
| verbose = false; |
| } else { |
| return CMD_RET_USAGE; |
| } |
| } |
| |
| if (guid_any) |
| guid_p = NULL; |
| else if (default_guid) |
| guid_p = &efi_global_variable_guid; |
| else |
| guid_p = (const efi_guid_t *)guid.b; |
| |
| /* enumerate and show all UEFI variables */ |
| return efi_dump_var_all(argc, argv, guid_p, verbose); |
| } |
| |
| /** |
| * append_value() - encode UEFI variable's value |
| * @bufp: Buffer of encoded UEFI variable's value |
| * @sizep: Size of buffer |
| * @data: data to be encoded into the value |
| * Return: 0 on success, -1 otherwise |
| * |
| * Interpret a given data string and append it to buffer. |
| * Buffer will be realloc'ed if necessary. |
| * |
| * Currently supported formats are: |
| * =0x0123...: Hexadecimal number |
| * =H0123...: Hexadecimal-byte array |
| * ="...", =S"..." or <string>: |
| * String |
| */ |
| static int append_value(char **bufp, size_t *sizep, char *data) |
| { |
| char *tmp_buf = NULL, *new_buf = NULL, *value; |
| unsigned long len = 0; |
| |
| if (!strncmp(data, "=0x", 2)) { /* hexadecimal number */ |
| union { |
| u8 u8; |
| u16 u16; |
| u32 u32; |
| u64 u64; |
| } tmp_data; |
| unsigned long hex_value; |
| void *hex_ptr; |
| |
| data += 3; |
| len = strlen(data); |
| if ((len & 0x1)) /* not multiple of two */ |
| return -1; |
| |
| len /= 2; |
| if (len > 8) |
| return -1; |
| else if (len > 4) |
| len = 8; |
| else if (len > 2) |
| len = 4; |
| |
| /* convert hex hexadecimal number */ |
| if (strict_strtoul(data, 16, &hex_value) < 0) |
| return -1; |
| |
| tmp_buf = malloc(len); |
| if (!tmp_buf) |
| return -1; |
| |
| if (len == 1) { |
| tmp_data.u8 = hex_value; |
| hex_ptr = &tmp_data.u8; |
| } else if (len == 2) { |
| tmp_data.u16 = hex_value; |
| hex_ptr = &tmp_data.u16; |
| } else if (len == 4) { |
| tmp_data.u32 = hex_value; |
| hex_ptr = &tmp_data.u32; |
| } else { |
| tmp_data.u64 = hex_value; |
| hex_ptr = &tmp_data.u64; |
| } |
| memcpy(tmp_buf, hex_ptr, len); |
| value = tmp_buf; |
| |
| } else if (!strncmp(data, "=H", 2)) { /* hexadecimal-byte array */ |
| data += 2; |
| len = strlen(data); |
| if (len & 0x1) /* not multiple of two */ |
| return -1; |
| |
| len /= 2; |
| tmp_buf = malloc(len); |
| if (!tmp_buf) |
| return -1; |
| |
| if (hex2bin((u8 *)tmp_buf, data, len) < 0) { |
| printf("Error: illegal hexadecimal string\n"); |
| free(tmp_buf); |
| return -1; |
| } |
| |
| value = tmp_buf; |
| } else { /* string */ |
| if (!strncmp(data, "=\"", 2) || !strncmp(data, "=S\"", 3)) { |
| if (data[1] == '"') |
| data += 2; |
| else |
| data += 3; |
| value = data; |
| len = strlen(data) - 1; |
| if (data[len] != '"') |
| return -1; |
| } else { |
| value = data; |
| len = strlen(data); |
| } |
| } |
| |
| new_buf = realloc(*bufp, *sizep + len); |
| if (!new_buf) |
| goto out; |
| |
| memcpy(new_buf + *sizep, value, len); |
| *bufp = new_buf; |
| *sizep += len; |
| |
| out: |
| free(tmp_buf); |
| |
| return 0; |
| } |
| |
| /** |
| * do_env_set_efi() - set UEFI variable |
| * |
| * @cmdtp: Command table |
| * @flag: Command flag |
| * @argc: Number of arguments |
| * @argv: Argument array |
| * Return: CMD_RET_SUCCESS on success, or CMD_RET_RET_FAILURE |
| * |
| * This function is for "env set -e" or "setenv -e" command: |
| * => env set -e [-guid guid][-nv][-bs][-rt][-at][-a][-v] |
| * [-i address,size] var, or |
| * var [value ...] |
| * Encode values specified and set given UEFI variable. |
| * If no value is specified, delete the variable. |
| */ |
| int do_env_set_efi(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) |
| { |
| char *var_name, *value, *ep; |
| ulong addr; |
| efi_uintn_t size; |
| efi_guid_t guid; |
| u32 attributes; |
| bool default_guid, verbose, value_on_memory; |
| u16 *var_name16 = NULL, *p; |
| size_t len; |
| efi_status_t ret; |
| |
| if (argc == 1) |
| return CMD_RET_USAGE; |
| |
| /* Initialize EFI drivers */ |
| ret = efi_init_obj_list(); |
| if (ret != EFI_SUCCESS) { |
| printf("Error: Cannot initialize UEFI sub-system, r = %lu\n", |
| ret & ~EFI_ERROR_MASK); |
| return CMD_RET_FAILURE; |
| } |
| |
| /* |
| * attributes = EFI_VARIABLE_BOOTSERVICE_ACCESS | |
| * EFI_VARIABLE_RUNTIME_ACCESS; |
| */ |
| value = NULL; |
| size = 0; |
| attributes = 0; |
| guid = efi_global_variable_guid; |
| default_guid = true; |
| verbose = false; |
| value_on_memory = false; |
| for (argc--, argv++; argc > 0 && argv[0][0] == '-'; argc--, argv++) { |
| if (!strcmp(argv[0], "-guid")) { |
| if (argc == 1) |
| return CMD_RET_USAGE; |
| |
| argc--; |
| argv++; |
| if (uuid_str_to_bin(argv[0], guid.b, |
| UUID_STR_FORMAT_GUID)) { |
| printf("## Guid not specified or in XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX format\n"); |
| return CMD_RET_FAILURE; |
| } |
| default_guid = false; |
| } else if (!strcmp(argv[0], "-bs")) { |
| attributes |= EFI_VARIABLE_BOOTSERVICE_ACCESS; |
| } else if (!strcmp(argv[0], "-rt")) { |
| attributes |= EFI_VARIABLE_RUNTIME_ACCESS; |
| } else if (!strcmp(argv[0], "-nv")) { |
| attributes |= EFI_VARIABLE_NON_VOLATILE; |
| } else if (!strcmp(argv[0], "-at")) { |
| attributes |= |
| EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS; |
| } else if (!strcmp(argv[0], "-a")) { |
| attributes |= EFI_VARIABLE_APPEND_WRITE; |
| } else if (!strcmp(argv[0], "-i")) { |
| /* data comes from memory */ |
| if (argc == 1) |
| return CMD_RET_USAGE; |
| |
| argc--; |
| argv++; |
| addr = simple_strtoul(argv[0], &ep, 16); |
| if (*ep != ',') |
| return CMD_RET_USAGE; |
| |
| /* 0 should be allowed for delete */ |
| size = simple_strtoul(++ep, NULL, 16); |
| |
| value_on_memory = true; |
| } else if (!strcmp(argv[0], "-v")) { |
| verbose = true; |
| } else { |
| return CMD_RET_USAGE; |
| } |
| } |
| if (!argc) |
| return CMD_RET_USAGE; |
| |
| var_name = argv[0]; |
| if (default_guid) { |
| if (!strcmp(var_name, "db") || !strcmp(var_name, "dbx") || |
| !strcmp(var_name, "dbt")) |
| guid = efi_guid_image_security_database; |
| else |
| guid = efi_global_variable_guid; |
| } |
| |
| if (verbose) { |
| printf("GUID: %s\n", efi_guid_to_str((const efi_guid_t *) |
| &guid)); |
| printf("Attributes: 0x%x\n", attributes); |
| } |
| |
| /* for value */ |
| if (value_on_memory) |
| value = map_sysmem(addr, 0); |
| else if (argc > 1) |
| for (argc--, argv++; argc > 0; argc--, argv++) |
| if (append_value(&value, &size, argv[0]) < 0) { |
| printf("## Failed to process an argument, %s\n", |
| argv[0]); |
| ret = CMD_RET_FAILURE; |
| goto out; |
| } |
| |
| if (size && verbose) { |
| printf("Value:\n"); |
| print_hex_dump(" ", DUMP_PREFIX_OFFSET, |
| 16, 1, value, size, true); |
| } |
| |
| len = utf8_utf16_strnlen(var_name, strlen(var_name)); |
| var_name16 = malloc((len + 1) * 2); |
| if (!var_name16) { |
| printf("## Out of memory\n"); |
| ret = CMD_RET_FAILURE; |
| goto out; |
| } |
| p = var_name16; |
| utf8_utf16_strncpy(&p, var_name, len + 1); |
| |
| ret = EFI_CALL(efi_set_variable(var_name16, &guid, attributes, |
| size, value)); |
| unmap_sysmem(value); |
| if (ret == EFI_SUCCESS) { |
| ret = CMD_RET_SUCCESS; |
| } else { |
| const char *msg; |
| |
| switch (ret) { |
| case EFI_NOT_FOUND: |
| msg = " (not found)"; |
| break; |
| case EFI_WRITE_PROTECTED: |
| msg = " (read only)"; |
| break; |
| case EFI_INVALID_PARAMETER: |
| msg = " (invalid parameter)"; |
| break; |
| case EFI_SECURITY_VIOLATION: |
| msg = " (validation failed)"; |
| break; |
| case EFI_OUT_OF_RESOURCES: |
| msg = " (out of memory)"; |
| break; |
| default: |
| msg = ""; |
| break; |
| } |
| printf("## Failed to set EFI variable%s\n", msg); |
| ret = CMD_RET_FAILURE; |
| } |
| out: |
| if (value_on_memory) |
| unmap_sysmem(value); |
| else |
| free(value); |
| free(var_name16); |
| |
| return ret; |
| } |