53e54bf50d
Atish reports that on RISC-V, accessing the EFI variables causes
a kernel panic. An objdump of the file verifies that, since the
global pointer for efi_var_buf ends up in .GOT section which is
not mapped in virtual address space for Linux.
<snip of efi_var_mem_find>
0000000000000084 <efi_var_mem_find>:
84: 715d addi sp,sp,-80
* objdump -dr
0000000000000086 <.LCFI2>:
86: e0a2 sd s0,64(sp)
88: fc26 sd s1,56(sp)
8a: e486 sd ra,72(sp)
8c: f84a sd s2,48(sp)
8e: f44e sd s3,40(sp)
90: f052 sd s4,32(sp)
92: ec56 sd s5,24(sp)
94: 00000497 auipc s1,0x0
94: R_RISCV_GOT_HI20 efi_var_buf
98: 0004b483 ld s1,0(s1) # 94 <.LCFI2+0xe>
98: R_RISCV_PCREL_LO12_I .L0
98: R_RISCV_RELAX *ABS*
* objdump -t
0000000000000084 g F .text.efi_runtime 00000000000000b8 efi_var_mem_find
With the patch applied:
* objdump -dr
0000000000000086 <.LCFI2>:
86: e0a2 sd s0,64(sp)
88: fc26 sd s1,56(sp)
8a: e486 sd ra,72(sp)
8c: f84a sd s2,48(sp)
8e: f44e sd s3,40(sp)
90: f052 sd s4,32(sp)
92: ec56 sd s5,24(sp)
94: 00000497 auipc s1,0x0
94: R_RISCV_PCREL_HI20 .LANCHOR0
94: R_RISCV_RELAX *ABS*
98: 00048493 mv s1,s1
98: R_RISCV_PCREL_LO12_I .L0
98: R_RISCV_RELAX *ABS*
* objdump -t
0000000000000008 l O .data.efi_runtime 0000000000000008 efi_var_buf
On arm64 this works, because there's no .GOT entries for this
and everything is converted to relative references.
* objdump -dr (identical pre-post patch, only the new function shows up)
00000000000000b4 <efi_var_mem_find>:
b4: aa0003ee mov x14, x0
b8: 9000000a adrp x10, 0 <efi_var_mem_compare>
b8: R_AARCH64_ADR_PREL_PG_HI21 .data.efi_runtime
bc: 91000140 add x0, x10, #0x0
bc: R_AARCH64_ADD_ABS_LO12_NC .data.efi_runtime
c0: aa0103ed mov x13, x1
c4: 79400021 ldrh w1, [x1]
c8: aa0203eb mov x11, x2
cc: f9400400 ldr x0, [x0, #8]
d0: b940100c ldr w12, [x0, #16]
d4: 8b0c000c add x12, x0, x12
So let's switch efi_var_buf to static and create a helper function for
anyone that needs to update it.
Fixes: e01aed47d6
("efi_loader: Enable run-time variable support for tee based variables")
Reported-by: Atish Patra <atishp@atishpatra.org>
Tested-by: Atish Patra <atish.patra@wdc.com>
Signed-off-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
Reviewed-by: Heinrich Schuchardt <xypron.glpk@gmx.de>
353 lines
8.3 KiB
C
353 lines
8.3 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* File interface for UEFI variables
|
|
*
|
|
* Copyright (c) 2020, Heinrich Schuchardt
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <efi_loader.h>
|
|
#include <efi_variable.h>
|
|
#include <u-boot/crc.h>
|
|
|
|
/*
|
|
* The variables efi_var_file and efi_var_entry must be static to avoid
|
|
* referencing them via the global offset table (section .got). The GOT
|
|
* is neither mapped as EfiRuntimeServicesData nor do we support its
|
|
* relocation during SetVirtualAddressMap().
|
|
*/
|
|
static struct efi_var_file __efi_runtime_data *efi_var_buf;
|
|
static struct efi_var_entry __efi_runtime_data *efi_current_var;
|
|
|
|
/**
|
|
* efi_var_mem_compare() - compare GUID and name with a variable
|
|
*
|
|
* @var: variable to compare
|
|
* @guid: GUID to compare
|
|
* @name: variable name to compare
|
|
* @next: pointer to next variable
|
|
* Return: true if match
|
|
*/
|
|
static bool __efi_runtime
|
|
efi_var_mem_compare(struct efi_var_entry *var, const efi_guid_t *guid,
|
|
const u16 *name, struct efi_var_entry **next)
|
|
{
|
|
int i;
|
|
u8 *guid1, *guid2;
|
|
const u16 *data, *var_name;
|
|
bool match = true;
|
|
|
|
for (guid1 = (u8 *)&var->guid, guid2 = (u8 *)guid, i = 0;
|
|
i < sizeof(efi_guid_t) && match; ++i)
|
|
match = (guid1[i] == guid2[i]);
|
|
|
|
for (data = var->name, var_name = name;; ++data, ++var_name) {
|
|
if (match)
|
|
match = (*data == *var_name);
|
|
if (!*data)
|
|
break;
|
|
}
|
|
|
|
++data;
|
|
|
|
if (next)
|
|
*next = (struct efi_var_entry *)
|
|
ALIGN((uintptr_t)data + var->length, 8);
|
|
|
|
if (match)
|
|
efi_current_var = var;
|
|
|
|
return match;
|
|
}
|
|
|
|
struct efi_var_entry __efi_runtime
|
|
*efi_var_mem_find(const efi_guid_t *guid, const u16 *name,
|
|
struct efi_var_entry **next)
|
|
{
|
|
struct efi_var_entry *var, *last;
|
|
|
|
last = (struct efi_var_entry *)
|
|
((uintptr_t)efi_var_buf + efi_var_buf->length);
|
|
|
|
if (!*name) {
|
|
if (next) {
|
|
*next = efi_var_buf->var;
|
|
if (*next >= last)
|
|
*next = NULL;
|
|
}
|
|
return NULL;
|
|
}
|
|
if (efi_current_var &&
|
|
efi_var_mem_compare(efi_current_var, guid, name, next)) {
|
|
if (next && *next >= last)
|
|
*next = NULL;
|
|
return efi_current_var;
|
|
}
|
|
|
|
var = efi_var_buf->var;
|
|
if (var < last) {
|
|
for (; var;) {
|
|
struct efi_var_entry *pos;
|
|
bool match;
|
|
|
|
match = efi_var_mem_compare(var, guid, name, &pos);
|
|
if (pos >= last)
|
|
pos = NULL;
|
|
if (match) {
|
|
if (next)
|
|
*next = pos;
|
|
return var;
|
|
}
|
|
var = pos;
|
|
}
|
|
}
|
|
if (next)
|
|
*next = NULL;
|
|
return NULL;
|
|
}
|
|
|
|
void __efi_runtime efi_var_mem_del(struct efi_var_entry *var)
|
|
{
|
|
u16 *data;
|
|
struct efi_var_entry *next, *last;
|
|
|
|
if (!var)
|
|
return;
|
|
|
|
last = (struct efi_var_entry *)
|
|
((uintptr_t)efi_var_buf + efi_var_buf->length);
|
|
if (var <= efi_current_var)
|
|
efi_current_var = NULL;
|
|
|
|
for (data = var->name; *data; ++data)
|
|
;
|
|
++data;
|
|
next = (struct efi_var_entry *)
|
|
ALIGN((uintptr_t)data + var->length, 8);
|
|
efi_var_buf->length -= (uintptr_t)next - (uintptr_t)var;
|
|
|
|
/* efi_memcpy_runtime() can be used because next >= var. */
|
|
efi_memcpy_runtime(var, next, (uintptr_t)last - (uintptr_t)next);
|
|
efi_var_buf->crc32 = crc32(0, (u8 *)efi_var_buf->var,
|
|
efi_var_buf->length -
|
|
sizeof(struct efi_var_file));
|
|
}
|
|
|
|
efi_status_t __efi_runtime efi_var_mem_ins(
|
|
u16 *variable_name,
|
|
const efi_guid_t *vendor, u32 attributes,
|
|
const efi_uintn_t size1, const void *data1,
|
|
const efi_uintn_t size2, const void *data2,
|
|
const u64 time)
|
|
{
|
|
u16 *data;
|
|
struct efi_var_entry *var;
|
|
u32 var_name_len;
|
|
|
|
var = (struct efi_var_entry *)
|
|
((uintptr_t)efi_var_buf + efi_var_buf->length);
|
|
for (var_name_len = 0; variable_name[var_name_len]; ++var_name_len)
|
|
;
|
|
++var_name_len;
|
|
data = var->name + var_name_len;
|
|
|
|
if ((uintptr_t)data - (uintptr_t)efi_var_buf + size1 + size2 >
|
|
EFI_VAR_BUF_SIZE)
|
|
return EFI_OUT_OF_RESOURCES;
|
|
|
|
var->attr = attributes;
|
|
var->length = size1 + size2;
|
|
var->time = time;
|
|
|
|
efi_memcpy_runtime(&var->guid, vendor, sizeof(efi_guid_t));
|
|
efi_memcpy_runtime(var->name, variable_name,
|
|
sizeof(u16) * var_name_len);
|
|
efi_memcpy_runtime(data, data1, size1);
|
|
efi_memcpy_runtime((u8 *)data + size1, data2, size2);
|
|
|
|
var = (struct efi_var_entry *)
|
|
ALIGN((uintptr_t)data + var->length, 8);
|
|
efi_var_buf->length = (uintptr_t)var - (uintptr_t)efi_var_buf;
|
|
efi_var_buf->crc32 = crc32(0, (u8 *)efi_var_buf->var,
|
|
efi_var_buf->length -
|
|
sizeof(struct efi_var_file));
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
u64 __efi_runtime efi_var_mem_free(void)
|
|
{
|
|
return EFI_VAR_BUF_SIZE - efi_var_buf->length -
|
|
sizeof(struct efi_var_entry);
|
|
}
|
|
|
|
/**
|
|
* efi_var_mem_bs_del() - delete boot service only variables
|
|
*/
|
|
static void efi_var_mem_bs_del(void)
|
|
{
|
|
struct efi_var_entry *var = efi_var_buf->var;
|
|
|
|
for (;;) {
|
|
struct efi_var_entry *last;
|
|
|
|
last = (struct efi_var_entry *)
|
|
((uintptr_t)efi_var_buf + efi_var_buf->length);
|
|
if (var >= last)
|
|
break;
|
|
if (var->attr & EFI_VARIABLE_RUNTIME_ACCESS) {
|
|
u16 *data;
|
|
|
|
/* skip variable */
|
|
for (data = var->name; *data; ++data)
|
|
;
|
|
++data;
|
|
var = (struct efi_var_entry *)
|
|
ALIGN((uintptr_t)data + var->length, 8);
|
|
} else {
|
|
/* delete variable */
|
|
efi_var_mem_del(var);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* efi_var_mem_notify_exit_boot_services() - ExitBootService callback
|
|
*
|
|
* @event: callback event
|
|
* @context: callback context
|
|
*/
|
|
static void EFIAPI
|
|
efi_var_mem_notify_exit_boot_services(struct efi_event *event, void *context)
|
|
{
|
|
EFI_ENTRY("%p, %p", event, context);
|
|
|
|
/* Delete boot service only variables */
|
|
efi_var_mem_bs_del();
|
|
|
|
EFI_EXIT(EFI_SUCCESS);
|
|
}
|
|
|
|
/**
|
|
* efi_var_mem_notify_exit_boot_services() - SetVirtualMemoryMap callback
|
|
*
|
|
* @event: callback event
|
|
* @context: callback context
|
|
*/
|
|
static void EFIAPI __efi_runtime
|
|
efi_var_mem_notify_virtual_address_map(struct efi_event *event, void *context)
|
|
{
|
|
efi_convert_pointer(0, (void **)&efi_var_buf);
|
|
efi_current_var = NULL;
|
|
}
|
|
|
|
efi_status_t efi_var_mem_init(void)
|
|
{
|
|
u64 memory;
|
|
efi_status_t ret;
|
|
struct efi_event *event;
|
|
|
|
ret = efi_allocate_pages(EFI_ALLOCATE_ANY_PAGES,
|
|
EFI_RUNTIME_SERVICES_DATA,
|
|
efi_size_in_pages(EFI_VAR_BUF_SIZE),
|
|
&memory);
|
|
if (ret != EFI_SUCCESS)
|
|
return ret;
|
|
efi_var_buf = (struct efi_var_file *)(uintptr_t)memory;
|
|
memset(efi_var_buf, 0, EFI_VAR_BUF_SIZE);
|
|
efi_var_buf->magic = EFI_VAR_FILE_MAGIC;
|
|
efi_var_buf->length = (uintptr_t)efi_var_buf->var -
|
|
(uintptr_t)efi_var_buf;
|
|
/* crc32 for 0 bytes = 0 */
|
|
|
|
ret = efi_create_event(EVT_SIGNAL_EXIT_BOOT_SERVICES, TPL_CALLBACK,
|
|
efi_var_mem_notify_exit_boot_services, NULL,
|
|
NULL, &event);
|
|
if (ret != EFI_SUCCESS)
|
|
return ret;
|
|
ret = efi_create_event(EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE, TPL_CALLBACK,
|
|
efi_var_mem_notify_virtual_address_map, NULL,
|
|
NULL, &event);
|
|
if (ret != EFI_SUCCESS)
|
|
return ret;
|
|
return ret;
|
|
}
|
|
|
|
efi_status_t __efi_runtime
|
|
efi_get_variable_mem(u16 *variable_name, const efi_guid_t *vendor, u32 *attributes,
|
|
efi_uintn_t *data_size, void *data, u64 *timep)
|
|
{
|
|
efi_uintn_t old_size;
|
|
struct efi_var_entry *var;
|
|
u16 *pdata;
|
|
|
|
if (!variable_name || !vendor || !data_size)
|
|
return EFI_INVALID_PARAMETER;
|
|
var = efi_var_mem_find(vendor, variable_name, NULL);
|
|
if (!var)
|
|
return EFI_NOT_FOUND;
|
|
|
|
if (attributes)
|
|
*attributes = var->attr;
|
|
if (timep)
|
|
*timep = var->time;
|
|
|
|
old_size = *data_size;
|
|
*data_size = var->length;
|
|
if (old_size < var->length)
|
|
return EFI_BUFFER_TOO_SMALL;
|
|
|
|
if (!data)
|
|
return EFI_INVALID_PARAMETER;
|
|
|
|
for (pdata = var->name; *pdata; ++pdata)
|
|
;
|
|
++pdata;
|
|
|
|
efi_memcpy_runtime(data, pdata, var->length);
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
efi_status_t __efi_runtime
|
|
efi_get_next_variable_name_mem(efi_uintn_t *variable_name_size,
|
|
u16 *variable_name, efi_guid_t *vendor)
|
|
{
|
|
struct efi_var_entry *var;
|
|
efi_uintn_t old_size;
|
|
u16 *pdata;
|
|
|
|
if (!variable_name_size || !variable_name || !vendor)
|
|
return EFI_INVALID_PARAMETER;
|
|
|
|
if (u16_strnlen(variable_name, *variable_name_size) ==
|
|
*variable_name_size)
|
|
return EFI_INVALID_PARAMETER;
|
|
|
|
if (!efi_var_mem_find(vendor, variable_name, &var) && *variable_name)
|
|
return EFI_INVALID_PARAMETER;
|
|
|
|
if (!var)
|
|
return EFI_NOT_FOUND;
|
|
|
|
for (pdata = var->name; *pdata; ++pdata)
|
|
;
|
|
++pdata;
|
|
|
|
old_size = *variable_name_size;
|
|
*variable_name_size = (uintptr_t)pdata - (uintptr_t)var->name;
|
|
|
|
if (old_size < *variable_name_size)
|
|
return EFI_BUFFER_TOO_SMALL;
|
|
|
|
efi_memcpy_runtime(variable_name, var->name, *variable_name_size);
|
|
efi_memcpy_runtime(vendor, &var->guid, sizeof(efi_guid_t));
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
void efi_var_buf_update(struct efi_var_file *var_buf)
|
|
{
|
|
memcpy(efi_var_buf, var_buf, EFI_VAR_BUF_SIZE);
|
|
}
|