// SPDX-License-Identifier: GPL-2.0+ /* * Integrate UEFI variables to u-boot env interface * * Copyright (c) 2018 AKASHI Takahiro, Linaro Limited */ #include #include #include #include #include #include #include #include #include #include #include /* * 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 | -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(struct cmd_tbl *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 */ 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(struct cmd_tbl *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; }