From 8529e0f7f70f427a7202815061362eceba6bfc50 Mon Sep 17 00:00:00 2001 From: Steve McIntyre Date: Wed, 27 Apr 2022 22:41:59 +0100 Subject: New upstream version 15.5 --- mock-variables.c | 1400 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1400 insertions(+) create mode 100644 mock-variables.c (limited to 'mock-variables.c') diff --git a/mock-variables.c b/mock-variables.c new file mode 100644 index 00000000..e9bce544 --- /dev/null +++ b/mock-variables.c @@ -0,0 +1,1400 @@ +// SPDX-License-Identifier: BSD-2-Clause-Patent +/* + * mock-variables.c - a mock GetVariable/SetVariable/GNVN/etc + * implementation for testing. + * Copyright Peter Jones + */ +#include "shim.h" +#include "mock-variables.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wunused-function" + +list_t mock_default_variable_limits; +list_t *mock_qvi_limits = &mock_default_variable_limits; +list_t *mock_sv_limits = &mock_default_variable_limits; + +list_t mock_variables = LIST_HEAD_INIT(mock_variables); + +mock_sort_policy_t mock_variable_sort_policy = MOCK_SORT_APPEND; +mock_sort_policy_t mock_config_table_sort_policy = MOCK_SORT_APPEND; + +UINT32 mock_variable_delete_attr_policy; + +mock_set_variable_pre_hook_t *mock_set_variable_pre_hook = NULL; +mock_set_variable_post_hook_t *mock_set_variable_post_hook = NULL; +mock_get_variable_pre_hook_t *mock_get_variable_pre_hook = NULL; +mock_get_variable_post_hook_t *mock_get_variable_post_hook = NULL; +mock_get_next_variable_name_pre_hook_t *mock_get_next_variable_name_pre_hook = NULL; +mock_get_next_variable_name_post_hook_t *mock_get_next_variable_name_post_hook = NULL; +mock_query_variable_info_pre_hook_t *mock_query_variable_info_pre_hook = NULL; +mock_query_variable_info_post_hook_t *mock_query_variable_info_post_hook = NULL; + +static EFI_STATUS +mock_sv_pre_hook(CHAR16 *name, EFI_GUID *guid, UINT32 attrs, UINTN size, + VOID *data) +{ + EFI_STATUS status = EFI_SUCCESS; + if (mock_set_variable_pre_hook) + status = mock_set_variable_pre_hook(name, guid, + attrs, size, data); + return status; +} + +static void +mock_sv_post_hook_(CHAR16 *name, EFI_GUID *guid, UINT32 attrs, UINTN size, + VOID *data, EFI_STATUS *status, mock_variable_op_t op, + const char * const file, const int line, + const char * const func) +{ + if (mock_set_variable_post_hook) + mock_set_variable_post_hook(name, guid, attrs, size, + data, status, op, file, line, func); +} +#define mock_sv_post_hook(name, guid, attrs, size, data, status, op) \ + mock_sv_post_hook_(name, guid, attrs, size, data, status, op,\ + __FILE__, __LINE__, __func__) + +static EFI_STATUS +mock_gv_pre_hook(CHAR16 *name, EFI_GUID *guid, UINT32 *attrs, UINTN *size, + VOID *data) +{ + EFI_STATUS status = EFI_SUCCESS; + if (mock_get_variable_pre_hook) + status = mock_get_variable_pre_hook(name, guid, + attrs, size, data); + return status; +} + +static void +mock_gv_post_hook_(CHAR16 *name, EFI_GUID *guid, UINT32 *attrs, UINTN *size, + VOID *data, EFI_STATUS *status, const char * const file, + const int line, const char * const func) +{ + if (mock_get_variable_post_hook) + mock_get_variable_post_hook(name, guid, attrs, size, data, + status, file, line, func); +} +#define mock_gv_post_hook(name, guid, attrs, size, data, status) \ + mock_gv_post_hook_(name, guid, attrs, size, data, status,\ + __FILE__, __LINE__, __func__) + +static EFI_STATUS +mock_gnvn_pre_hook(UINTN *size, CHAR16 *name, EFI_GUID *guid) +{ + EFI_STATUS status = EFI_SUCCESS; + if (mock_get_next_variable_name_pre_hook) + status = mock_get_next_variable_name_pre_hook(size, name, guid); + return status; +} + +static void +mock_gnvn_post_hook_(UINTN *size, CHAR16 *name, EFI_GUID *guid, + EFI_STATUS *status, const char * const file, + const int line, const char * const func) +{ + if (mock_get_next_variable_name_post_hook) + mock_get_next_variable_name_post_hook(size, name, guid, status, + file, line, func); +} +#define mock_gnvn_post_hook(size, name, guid, status) \ + mock_gnvn_post_hook_(size, name, guid, status,\ + __FILE__, __LINE__, __func__) + +static EFI_STATUS +mock_qvi_pre_hook(UINT32 attrs, UINT64 *max_var_storage, + UINT64 *remaining_var_storage, UINT64 *max_var_size) +{ + EFI_STATUS status = EFI_SUCCESS; + if (mock_query_variable_info_pre_hook) + status = mock_query_variable_info_pre_hook( + attrs, max_var_storage, + remaining_var_storage, max_var_size); + return status; +} + +static void +mock_qvi_post_hook_(UINT32 attrs, UINT64 *max_var_storage, + UINT64 *remaining_var_storage, UINT64 *max_var_size, + EFI_STATUS *status, const char * const file, + const int line, const char * const func) +{ + if (mock_query_variable_info_post_hook) + mock_query_variable_info_post_hook(attrs, max_var_storage, + remaining_var_storage, + max_var_size, status, + file, line, func); +} +#define mock_qvi_post_hook(attrs, max_var_storage, remaining_var_storage,\ + max_var_size, status) \ + mock_qvi_post_hook_(attrs, max_var_storage, remaining_var_storage,\ + max_var_size, status, \ + __FILE__, __LINE__, __func__) + +static const size_t guidstr_size = sizeof("8be4df61-93ca-11d2-aa0d-00e098032b8c"); + +static int +variable_limits_cmp(const struct mock_variable_limits * const v0, + const struct mock_variable_limits * const v1) +{ + UINT32 mask = EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS; + + return (v0->attrs & mask) - (v1->attrs & mask); +} + +static INT64 +variable_cmp(const struct mock_variable * const v0, + const struct mock_variable * const v1) +{ + INT64 ret; + if (v0 == NULL || v1 == NULL) + return (uintptr_t)v0 - (uintptr_t)v1; + + ret = CompareGuid(&v0->guid, &v1->guid); + ret <<= 8ul; +#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) + printf("%s:%d:%s(): "GUID_FMT" %s "GUID_FMT" (0x%011"PRIx64" %"PRId64")\n", + __FILE__, __LINE__-1, __func__, + GUID_ARGS(v0->guid), + ret < 0 ? "<" : (ret > 0 ? ">" : "="), + GUID_ARGS(v1->guid), + (UINT64)ret & 0x1fffffffffful, + ret); +#endif + if (ret != 0) { + return ret; + } + + ret = StrCmp(v0->name, v1->name); +#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) + printf("%s:%d:%s(): \"%s\" %s \"%s\" (0x%02hhx (%d)\n", + __FILE__, __LINE__-1, __func__, + Str2str(v0->name), + ret < 0 ? "<" : (ret > 0 ? ">" : "=="), + Str2str(v1->name), + ret, ret); +#endif + return ret; +} + +static char * +list2var(list_t *pos) +{ + static char buf0[1024]; + static char buf1[1024]; + char *out; + static int n; + struct mock_variable *var; + + out = n++ % 2 ? buf0 : buf1; + if (n > 1) + n -= 2; + SetMem(out, 1024, 0); + if (pos == &mock_variables) { + strcpy(out, "list tail"); + return out; + } + var = list_entry(pos, struct mock_variable, list); + snprintf(out, 1023, GUID_FMT"-%s", + GUID_ARGS(var->guid), + Str2str(var->name)); + return out; +} + +EFI_STATUS EFIAPI +mock_get_variable(CHAR16 *name, EFI_GUID *guid, UINT32 *attrs, UINTN *size, + VOID *data) +{ + list_t *pos = NULL; + struct mock_variable goal = { + .name = name, + .guid = *guid, + }; + struct mock_variable *result = NULL; + EFI_STATUS status; + + status = mock_gv_pre_hook(name, guid, attrs, size, data); + if (EFI_ERROR(status)) + return status; + + if (name == NULL || guid == NULL || size == NULL) { + status = EFI_INVALID_PARAMETER; + mock_gv_post_hook(name, guid, attrs, size, data, &status); + return status; + } + + list_for_each(pos, &mock_variables) { + struct mock_variable *var; + + var = list_entry(pos, struct mock_variable, list); +#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) + printf("%s:%d:%s(): varcmp("GUID_FMT"-%s, "GUID_FMT"-%s)\n", + __FILE__, __LINE__-1, __func__, + GUID_ARGS(goal.guid), Str2str(goal.name), + GUID_ARGS(var->guid), Str2str(var->name)); +#endif + if (variable_cmp(&goal, var) == 0) { + if (attrs != NULL) + *attrs = var->attrs; + if (var->size > *size) { + *size = var->size; + status = EFI_BUFFER_TOO_SMALL; + mock_gv_post_hook(name, guid, attrs, size, data, + &status); + return status; + } + if (data == NULL) { + status = EFI_INVALID_PARAMETER; + mock_gv_post_hook(name, guid, attrs, size, data, + &status); + return status; + } + *size = var->size; + memcpy(data, var->data, var->size); + status = EFI_SUCCESS; + mock_gv_post_hook(name, guid, attrs, size, data, + &status); + return status; + } + } + + status = EFI_NOT_FOUND; + mock_gv_post_hook(name, guid, attrs, size, data, &status); + return status; +} + +static EFI_STATUS +mock_gnvn_set_result(UINTN *size, CHAR16 *name, EFI_GUID *guid, + struct mock_variable *result) +{ + EFI_STATUS status; + + if (*size < StrSize(result->name)) { + *size = StrSize(result->name); + status = EFI_BUFFER_TOO_SMALL; + mock_gnvn_post_hook(size, name, guid, &status); +#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) + printf("%s:%d:%s(): returning %lx\n", + __FILE__, __LINE__-1, __func__, status); +#endif + return status; + } + + *size = StrLen(result->name) + 1; + StrCpy(name, result->name); + memcpy(guid, &result->guid, sizeof(EFI_GUID)); + + status = EFI_SUCCESS; + mock_gnvn_post_hook(size, name, guid, &status); +#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) + printf("%s:%d:%s(): returning %lx\n", + __FILE__, __LINE__-1, __func__, status); +#endif + return status; +} + +EFI_STATUS EFIAPI +mock_get_next_variable_name(UINTN *size, CHAR16 *name, EFI_GUID *guid) +{ + list_t *pos = NULL; + struct mock_variable goal = { + .name = name, + .guid = *guid, + }; + struct mock_variable *result = NULL; + bool found = false; + EFI_STATUS status; + + status = mock_gnvn_pre_hook(size, name, guid); + if (EFI_ERROR(status)) + return status; + + if (size == NULL || name == NULL || guid == NULL) { + status = EFI_INVALID_PARAMETER; + mock_gnvn_post_hook(size, name, guid, &status); + return status; + } + + for (size_t i = 0; i < *size; i++) { + if (name[i] == 0) { + found = true; + break; + } + } + + if (found == false) { + status = EFI_INVALID_PARAMETER; + mock_gnvn_post_hook(size, name, guid, &status); + return status; + } + + found = false; +#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) + printf("%s:%d:%s():searching for "GUID_FMT"%s%s\n", + __FILE__, __LINE__-1, __func__, + GUID_ARGS(*guid), + name[0] == 0 ? "" : "-", + name[0] == 0 ? "" : Str2str(name)); +#endif + list_for_each(pos, &mock_variables) { + struct mock_variable *var; + + var = list_entry(pos, struct mock_variable, list); +#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) + printf("%s:%d:%s(): candidate var:%p &var->guid:%p &var->list:%p\n", + __FILE__, __LINE__-1, __func__, var, &var->guid, &var->list); +#endif + if (name[0] == 0) { + if (CompareGuid(&var->guid, guid) == 0) { +#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) + printf("%s:%d:%s(): found\n", + __FILE__, __LINE__-1, __func__); +#endif + result = var; + found = true; + break; + } + } else { + if (found) { + if (CompareGuid(&var->guid, guid) == 0) { + result = var; + break; + } + continue; + } + +#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) + printf("%s:%d:%s(): varcmp("GUID_FMT"-%s, "GUID_FMT"-%s)\n", + __FILE__, __LINE__-1, __func__, + GUID_ARGS(goal.guid), Str2str(goal.name), + GUID_ARGS(var->guid), Str2str(var->name)); +#endif + if (variable_cmp(&goal, var) == 0) { +#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) + printf("%s:%d:%s(): found\n", + __FILE__, __LINE__-1, __func__); +#endif + found = true; + } + } + } +#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) + if (result) { + printf("%s:%d:%s(): found:%d result:%p &result->guid:%p &result->list:%p\n" + __FILE__, __LINE__-1, __func__, found, result, + &result->guid, &result->list); + printf("%s:%d:%s(): "GUID_FMT"-%s\n", + __FILE__, __LINE__-1, __func__, GUID_ARGS(result->guid), + Str2str(result->name)); + } else { + printf("%s:%d:%s(): not found\n", + __FILE__, __LINE__-1, __func__); + } +#endif + + if (!found) { + if (name[0] == 0) + status = EFI_NOT_FOUND; + else + status = EFI_INVALID_PARAMETER; + mock_gnvn_post_hook(size, name, guid, &status); + return status; + } + + if (!result) { + status = EFI_NOT_FOUND; + mock_gnvn_post_hook(size, name, guid, &status); + return status; + } + + return mock_gnvn_set_result(size, name, guid, result); +} + +static void +free_var(struct mock_variable *var) +{ + if (!var) + return; +#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) + printf("%s:%d:%s(): var:%p &var->guid:%p ", + __FILE__, __LINE__-1, __func__, + var, var ? &var->guid : NULL); + if (var) + printf(GUID_FMT"-%s", GUID_ARGS(var->guid), + var->name ? Str2str(var->name) : ""); + printf("\n"); +#endif + list_del(&var->list); + if (var->size && var->data) + free(var->data); + SetMem(var, sizeof(*var), 0); + free(var); +} + +static bool +mock_sv_attrs_match(UINT32 old, UINT32 new) +{ + UINT32 mask = ~EFI_VARIABLE_APPEND_WRITE; + + return (old & mask) == (new & mask); +} + +static EFI_STATUS +mock_sv_adjust_usage_data(UINT32 attrs, size_t size, ssize_t change) +{ + const UINT32 bs = EFI_VARIABLE_BOOTSERVICE_ACCESS; + const UINT32 bs_nv = bs | EFI_VARIABLE_NON_VOLATILE; + const UINT32 bs_rt = bs | EFI_VARIABLE_RUNTIME_ACCESS; + const UINT32 bs_rt_nv = bs_nv | bs_rt; + struct mock_variable_limits goal = { + .attrs = attrs & bs_rt_nv, + }; + struct mock_variable_limits *qvi_limits = NULL; + struct mock_variable_limits *sv_limits = NULL; + list_t var, *pos = NULL; + UINT64 remaining; + + list_for_each(pos, mock_qvi_limits) { + struct mock_variable_limits *candidate; + + candidate = list_entry(pos, struct mock_variable_limits, list); + if (variable_limits_cmp(&goal, candidate) == 0) { + qvi_limits = candidate; + break; + } + } + + list_for_each(pos, mock_sv_limits) { + struct mock_variable_limits *candidate; + + candidate = list_entry(pos, struct mock_variable_limits, list); + if (variable_limits_cmp(&goal, candidate) == 0) { + sv_limits = candidate; + break; + } + } + if (!sv_limits) { + return EFI_UNSUPPORTED; + } + + if (sv_limits->status != EFI_SUCCESS) + return sv_limits->status; + + if (*sv_limits->max_var_size < size) { +#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) + printf("%s:%d:%s():*sv_limits->max_var_size:%zu size:%zu\n", + __FILE__, __LINE__, __func__, + *sv_limits->max_var_size, size); +#endif + return EFI_OUT_OF_RESOURCES; + } + + if (change > 0 && (UINT64)change > *sv_limits->remaining_var_storage) { +#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) + printf("%s:%d:%s():*sv_limits->remaining_var_storage:%zu change:%zd\n", + __FILE__, __LINE__, __func__, + *sv_limits->remaining_var_storage, change); +#endif + return EFI_OUT_OF_RESOURCES; + } + + *sv_limits->remaining_var_storage += change; + + if (qvi_limits) { + /* + * If the adjustment here is wrong, we don't want to not do + * the set variable, we also don't want to not account + * for it, and of course we can't have any integer UB. So + * just limit it safely and move on, even though that may + * result in wrong checks against QueryVariableInfo() later. + * + * As if there are correct checks against QueryVariableInfo()... + */ + if (qvi_limits->remaining_var_storage == sv_limits->remaining_var_storage) + ; + else if (change < 0 && (UINT64)-change > *qvi_limits->remaining_var_storage) + *qvi_limits->remaining_var_storage = 0; + else if (change > 0 && UINT64_MAX - *qvi_limits->remaining_var_storage < (UINT64)change) + *qvi_limits->remaining_var_storage = UINT64_MAX; + else + *qvi_limits->remaining_var_storage += change; + } + return EFI_SUCCESS; +} + +static EFI_STATUS +mock_delete_variable(struct mock_variable *var) +{ + EFI_STATUS status; + + status = mock_sv_adjust_usage_data(var->attrs, 0, - var->size); + if (EFI_ERROR(status)) { + printf("%s:%d:%s(): status:0x%lx\n", + __FILE__, __LINE__ - 1, __func__, status); + mock_sv_post_hook(var->name, &var->guid, var->attrs, var->size, + var->data, &status, DELETE); + return status; + } + + status = EFI_SUCCESS; + mock_sv_post_hook(var->name, &var->guid, var->attrs, 0, 0, &status, + DELETE); + free_var(var); + return status; +} + +static EFI_STATUS +mock_replace_variable(struct mock_variable *var, VOID *data, UINTN size) +{ + EFI_STATUS status; + VOID *new; + + status = mock_sv_adjust_usage_data(var->attrs, size, + - var->size + size); + if (EFI_ERROR(status)) { + mock_sv_post_hook(var->name, &var->guid, var->attrs, var->size, + var->data, &status, REPLACE); + return status; + } + + new = calloc(1, size); + if (!new) { +#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) + printf("%s:%d:%s():calloc(1, %zu) failed\n", + __FILE__, __LINE__, __func__, + size); +#endif + status = EFI_OUT_OF_RESOURCES; + mock_sv_post_hook(var->name, &var->guid, var->attrs, var->size, + var->data, &status, REPLACE); + return status; + } + memcpy(new, data, size); + free(var->data); + var->data = new; + var->size = size; + + status = EFI_SUCCESS; + mock_sv_post_hook(var->name, &var->guid, var->attrs, var->size, + var->data, &status, REPLACE); + return status; +} + +static EFI_STATUS +mock_sv_extend(struct mock_variable *var, VOID *data, UINTN size) +{ + EFI_STATUS status; + uint8_t *new; + + if (size == 0) { + status = EFI_SUCCESS; + mock_sv_post_hook(var->name, &var->guid, var->attrs, var->size, + var->data, &status, APPEND); + return status; + } + + status = mock_sv_adjust_usage_data(var->attrs, var->size + size, size); + if (EFI_ERROR(status)) { + mock_sv_post_hook(var->name, &var->guid, var->attrs, var->size, + var->data, &status, APPEND); + return status; + } + + new = realloc(var->data, var->size + size); + if (!new) { +#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) + printf("%s:%d:%s():realloc(%zu) failed\n", + __FILE__, __LINE__, __func__, + var->size + size); +#endif + status = EFI_OUT_OF_RESOURCES; + mock_sv_post_hook(var->name, &var->guid, var->attrs, var->size, + var->data, &status, APPEND); + return status; + } + + memcpy(&new[var->size], data, size); + var->data = (void *)new; + var->size += size; + + status = EFI_SUCCESS; + mock_sv_post_hook(var->name, &var->guid, var->attrs, var->size, + var->data, &status, APPEND); + return status; +} + +void +mock_print_var_list(list_t *head) +{ + list_t *pos = NULL; +#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) + printf("%s:%d:%s():variables so far:\n", __FILE__, __LINE__, __func__); +#endif + list_for_each(pos, head) { + struct mock_variable *var = NULL; + + var = list_entry(pos, struct mock_variable, list); +#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) + printf("%s:%d:%s(): "GUID_FMT"-%s (%lu bytes)\n", + __FILE__, __LINE__ - 1, __func__, + GUID_ARGS(var->guid), Str2str(var->name), var->size); +#endif + } +} + +static EFI_STATUS +mock_new_variable(CHAR16 *name, EFI_GUID *guid, UINT32 attrs, UINTN size, + VOID *data, struct mock_variable **out) +{ + EFI_STATUS status; + struct mock_variable *var; + uint8_t *buf; + + if (size == 0) { + status = EFI_INVALID_PARAMETER; + return status; + } + + status = EFI_OUT_OF_RESOURCES; + buf = calloc(1, sizeof(struct mock_variable) + StrSize(name)); + if (!buf) { +#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) + printf("%s:%d:%s(): calloc(1, %zu) failed\n", + __FILE__, __LINE__, __func__, + sizeof(struct mock_variable) + StrSize(name)); +#endif + goto err; + } + var = (struct mock_variable *)buf; + +#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) + printf("%s:%d:%s(): var:%p &var->guid:%p &var->list:%p\n", + __FILE__, __LINE__-1, __func__, var, &var->guid, &var->list); +#endif + + var->data = malloc(size); + if (!var->data) + goto err_free; + + var->name = (CHAR16 *)&buf[sizeof(*var)]; + StrCpy(var->name, name); + memcpy(&var->guid, guid, sizeof(EFI_GUID)); + memcpy(var->data, data, size); + var->size = size; + var->attrs = attrs; + INIT_LIST_HEAD(&var->list); + +#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) + printf("%s:%d:%s(): var: "GUID_FMT"-%s\n", + __FILE__, __LINE__-1, __func__, + GUID_ARGS(var->guid), Str2str(var->name)); +#endif + + *out = var; + status = EFI_SUCCESS; +err_free: + if (EFI_ERROR(status)) + free_var(var); +err: + return status; +} + +EFI_STATUS EFIAPI +mock_set_variable(CHAR16 *name, EFI_GUID *guid, UINT32 attrs, UINTN size, + VOID *data) +{ + list_t *pos = NULL, *tmp = NULL, *var_list = NULL; + struct mock_variable goal = { + .name = name, + .guid = *guid, + }; + struct mock_variable *var = NULL; + bool found = false; + bool add_tail = true; + EFI_STATUS status; + long cmp = -1; + + status = mock_sv_pre_hook(name, guid, attrs, size, data); + if (EFI_ERROR(status)) + return status; + + if (!name || name[0] == 0 || !guid) { + status = EFI_INVALID_PARAMETER; + mock_sv_post_hook(name, guid, attrs, size, data, &status, + CREATE); + return status; + } + + if ((attrs & EFI_VARIABLE_RUNTIME_ACCESS) && + !(attrs & EFI_VARIABLE_BOOTSERVICE_ACCESS)) { + status = EFI_INVALID_PARAMETER; + mock_sv_post_hook(name, guid, attrs, size, data, &status, + CREATE); + return status; + } + +#if 0 + /* + * We don't ever operate after ExitBootServices(), so I'm not + * checking for the missing EFI_VARIABLE_RUNTIME_ACCESS case + */ + if (has_exited_boot_services() && !(attrs & EFI_VARIABLE_RUNTIME_ACCESS)) { + status = EFI_INVALID_PARAMETER; + mock_sv_post_hook(name, guid, attrs, size, data, &status, + CREATE); + return status; + } +#endif + +#if 0 + /* + * For now, we're ignoring that we don't support these. + */ + if (attrs & (EFI_VARIABLE_HARDWARE_ERROR_RECORD | + EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS | + EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS | + EFI_VARIABLE_ENHANCED_AUTHENTICATED_ACCESS)) { + status = EFI_UNSUPPORTED; + mock_sv_post_hook(name, guid, attrs, size, data, &status, + CREATE); + return status; + } +#endif + +#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) + printf("%s:%d:%s():Setting "GUID_FMT"-%s\n", + __FILE__, __LINE__ - 1, __func__, + GUID_ARGS(*guid), Str2str(name)); +#endif + switch (mock_variable_sort_policy) { + case MOCK_SORT_PREPEND: + var_list = &mock_variables; + add_tail = false; + break; + case MOCK_SORT_APPEND: + var_list = &mock_variables; + add_tail = true; + break; + case MOCK_SORT_DESCENDING: + add_tail = true; + break; + case MOCK_SORT_ASCENDING: + add_tail = true; + break; + default: + break; + } + + pos = &mock_variables; + list_for_each_safe(pos, tmp, &mock_variables) { + found = false; + var = list_entry(pos, struct mock_variable, list); +#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) + printf("%s:%d:%s(): varcmp("GUID_FMT"-%s, "GUID_FMT"-%s)\n", + __FILE__, __LINE__-1, __func__, + GUID_ARGS(goal.guid), Str2str(goal.name), + GUID_ARGS(var->guid), Str2str(var->name)); +#endif + cmp = variable_cmp(&goal, var); + cmp = cmp < 0 ? -1 : (cmp > 0 ? 1 : 0); + + switch (mock_variable_sort_policy) { + case MOCK_SORT_DESCENDING: + if (cmp >= 0) { + var_list = pos; + found = true; + } + break; + case MOCK_SORT_ASCENDING: + if (cmp <= 0) { + var_list = pos; + found = true; + } + break; + default: + if (cmp == 0) { + var_list = pos; + found = true; + } + break; + } + if (found) + break; + } +#if defined(SHIM_DEBUG) && SHIM_DEBUG != 0 + printf("%s:%d:%s():var_list:%p &mock_variables:%p cmp:%ld\n", + __FILE__, __LINE__ - 1, __func__, + var_list, &mock_variables, cmp); +#endif + if (cmp != 0 || (cmp == 0 && var_list == &mock_variables)) { + size_t totalsz = size + StrSize(name); +#if defined(SHIM_DEBUG) && SHIM_DEBUG != 0 + printf("%s:%d:%s():var:%p attrs:0x%lx\n", + __FILE__, __LINE__ - 1, __func__, var, attrs); +#endif + status = mock_new_variable(name, guid, attrs, size, data, &var); + if (EFI_ERROR(status)) { + mock_sv_post_hook(name, guid, attrs, size, data, + &status, CREATE); + return status; + } + mock_sv_adjust_usage_data(attrs, size, totalsz); + mock_sv_post_hook(name, guid, attrs, size, data, + &status, CREATE); + if (EFI_ERROR(status)) { + mock_sv_adjust_usage_data(attrs, 0, -totalsz); + return status; + } + +#if defined(SHIM_DEBUG) && SHIM_DEBUG != 0 + printf("%s:%d:%s(): Adding "GUID_FMT"-%s %s %s\n", + __FILE__, __LINE__ - 1, __func__, + GUID_ARGS(var->guid), Str2str(var->name), + add_tail ? "after" : "before", + list2var(pos)); +#endif + if (add_tail) + list_add_tail(&var->list, pos); + else + list_add(&var->list, pos); + return status; + } + + var = list_entry(var_list, struct mock_variable, list); +#if defined(SHIM_DEBUG) && SHIM_DEBUG != 0 + printf("%s:%d:%s():var:%p attrs:%s cmp:%ld size:%ld\n", + __FILE__, __LINE__ - 1, __func__, + var, format_var_attrs(var->attrs), cmp, size); +#endif + if (!mock_sv_attrs_match(var->attrs, attrs)) { + status = EFI_INVALID_PARAMETER; + if (size == 0 && !(attrs & EFI_VARIABLE_APPEND_WRITE)) { + if ((mock_variable_delete_attr_policy & MOCK_VAR_DELETE_ATTR_ALLOW_ZERO) + && attrs == 0) { + status = EFI_SUCCESS; + } else if (mock_variable_delete_attr_policy & MOCK_VAR_DELETE_ATTR_ALOW_MISMATCH) { + status = EFI_SUCCESS; + } + } + if (EFI_ERROR(status)) { + printf("%s:%d:%s(): var->attrs:%s attrs:%s\n", + __FILE__, __LINE__ - 1, __func__, + format_var_attrs(var->attrs), + format_var_attrs(attrs)); + mock_sv_post_hook(name, guid, attrs, size, data, + &status, REPLACE); + return status; + } + } + + if (attrs & EFI_VARIABLE_APPEND_WRITE) + return mock_sv_extend(var, data, size); + + if (size == 0) { + UINT32 mask = EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS + | EFI_VARIABLE_ENHANCED_AUTHENTICATED_ACCESS; + /* + * We can't process deletes on these correctly unless we + * parse the header. + */ + if (attrs & mask) { + return EFI_INVALID_PARAMETER; + } + + return mock_delete_variable(var); + } + + return mock_replace_variable(var, data, size); +} + +EFI_STATUS EFIAPI +mock_query_variable_info(UINT32 attrs, UINT64 *max_var_storage, + UINT64 *remaining_var_storage, UINT64 *max_var_size) +{ + list_t mvl, *pos = NULL; + struct mock_variable_limits goal = { + .attrs = attrs, + }; + struct mock_variable_limits *limits = NULL; + EFI_STATUS status; + + status = mock_qvi_pre_hook(attrs, max_var_storage, + remaining_var_storage, max_var_size); + if (EFI_ERROR(status)) + return status; + + if (max_var_storage == NULL || + remaining_var_storage == NULL || + max_var_size == NULL) { + status = EFI_INVALID_PARAMETER; + mock_qvi_post_hook(attrs, max_var_storage, + remaining_var_storage, max_var_size, + &status); + return status; + } + + list_for_each(pos, mock_qvi_limits) { + limits = list_entry(pos, struct mock_variable_limits, list); + if (variable_limits_cmp(&goal, limits) == 0) { + *max_var_storage = *limits->max_var_storage; + *remaining_var_storage = *limits->remaining_var_storage; + *max_var_size = *limits->max_var_size; + + status = EFI_SUCCESS; + mock_qvi_post_hook(attrs, max_var_storage, + remaining_var_storage, max_var_size, + &status); + return status; + } + } + + status = EFI_UNSUPPORTED; + mock_qvi_post_hook(attrs, max_var_storage, remaining_var_storage, + max_var_size, &status); + return status; +} + +static UINT64 default_max_var_storage; +static UINT64 default_remaining_var_storage; +static UINT64 default_max_var_size; + +static struct mock_variable_limits default_limits[] = { + {.attrs = EFI_VARIABLE_BOOTSERVICE_ACCESS, + .max_var_storage = &default_max_var_storage, + .remaining_var_storage = &default_remaining_var_storage, + .max_var_size = &default_max_var_size, + .status = EFI_SUCCESS, + }, + {.attrs = EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + .max_var_storage = &default_max_var_storage, + .remaining_var_storage = &default_remaining_var_storage, + .max_var_size = &default_max_var_size, + .status = EFI_SUCCESS, + }, + {.attrs = EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_NON_VOLATILE, + .max_var_storage = &default_max_var_storage, + .remaining_var_storage = &default_remaining_var_storage, + .max_var_size = &default_max_var_size, + .status = EFI_SUCCESS, + }, + {.attrs = EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS | + EFI_VARIABLE_NON_VOLATILE, + .max_var_storage = &default_max_var_storage, + .remaining_var_storage = &default_remaining_var_storage, + .max_var_size = &default_max_var_size, + .status = EFI_SUCCESS, + }, + {.attrs = 0, } +}; + +void +mock_set_default_usage_limits(void) +{ + default_max_var_storage = 65536; + default_remaining_var_storage = 65536; + default_max_var_size = 32768; + + INIT_LIST_HEAD(&mock_default_variable_limits); + for (size_t i = 0; default_limits[i].attrs != 0; i++) { + INIT_LIST_HEAD(&default_limits[i].list); + list_add_tail(&default_limits[i].list, + &mock_default_variable_limits); + } +} + +void +mock_load_one_variable(int dfd, const char * const dirname, char * const name) +{ + int fd; + FILE *f; + int rc; + struct stat statbuf; + size_t guidlen, namelen; + efi_guid_t guid; + size_t sz; + ssize_t offset = 0; + EFI_STATUS status; + UINT32 attrs; + + rc = fstatat(dfd, name, &statbuf, 0); + if (rc < 0) + err(2, "Could not stat \"%s/%s\"", dirname, name); + + if (!(S_ISREG(statbuf.st_mode))) + return; + + if (statbuf.st_size < 5) + errx(2, "Test data variable \"%s/%s\" is too small (%ld bytes)", + dirname, name, statbuf.st_size); + +#if 0 + mock_print_var_list(&mock_variables); +#endif + + uint8_t buf[statbuf.st_size]; + + fd = openat(dfd, name, O_RDONLY); + if (fd < 0) + err(2, "Could not open \"%s/%s\"", dirname, name); + + f = fdopen(fd, "r"); + if (!f) + err(2, "Could not open \"%s/%s\"", dirname, name); + + while (offset != statbuf.st_size) { + sz = fread(buf + offset, 1, statbuf.st_size - offset, f); + if (sz == 0) { + if (ferror(f)) + err(2, "Could not read from \"%s/%s\"", + dirname, name); + if (feof(f)) + errx(2, "Unexpected end of file reading \"%s/%s\"", + dirname, name); + } + + offset += sz; + } + + guidlen = strlen("8be4df61-93ca-11d2-aa0d-00e098032b8c"); + namelen = strlen(name) - guidlen; + + if (namelen < 2) + errx(2, "namelen for \"%s\" is %zu!?!", name, namelen); + + CHAR16 namebuf[namelen]; + + name[namelen-1] = 0; +#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) + printf("loading %s-%s\n", &name[namelen], name); +#endif + for (size_t i = 0; i < namelen; i++) + namebuf[i] = name[i]; + + rc = efi_str_to_guid(&name[namelen], &guid); + if (rc < 0) + err(2, "Could not parse \"%s\" as EFI GUID", &name[namelen]); + + memcpy(&attrs, (UINT32 *)buf, sizeof(UINT32)); + + status = RT->SetVariable(namebuf, (EFI_GUID *)&guid, attrs, + statbuf.st_size - sizeof(attrs), + &buf[sizeof(attrs)]); + if (EFI_ERROR(status)) + errx(2, "%s:%d:%s(): Could not set variable: 0x%llx", + __FILE__, __LINE__ - 1, __func__, + (unsigned long long)status); + + fclose(f); +} + +void +mock_load_variables(const char *const dirname, const char *filters[], + bool filter_out) +{ + int dfd; + DIR *d; + struct dirent *entry; + + d = opendir(dirname); + if (!d) + err(1, "Could not open directory \"%s\"", dirname); + + dfd = dirfd(d); + if (dfd < 0) + err(1, "Could not get directory file descriptor for \"%s\"", + dirname); + + while ((entry = readdir(d)) != NULL) { + size_t len = strlen(entry->d_name); + bool found = false; + if (filters && len > guidstr_size + 1) { + char spacebuf[len]; + + len -= guidstr_size; + SetMem(spacebuf, sizeof(spacebuf)-1, ' '); + spacebuf[len] = '\0'; + for (size_t i = 0; filters[i]; i++) { + if (strlen(filters[i]) > len) + continue; + if (!strncmp(entry->d_name, filters[i], len)) { + found = true; + break; + } + } + } + if ((found == false && filter_out == true) || + (found == true && filter_out == false)) { + mock_load_one_variable(dfd, dirname, entry->d_name); + } + } + + closedir(d); +#if 0 + mock_print_var_list(&mock_variables); +#endif +} + +static bool qvi_installed = false; + +void +mock_install_query_variable_info(void) +{ + qvi_installed = true; + RT->Hdr.Revision = 2ul << 16ul; + RT->QueryVariableInfo = mock_query_variable_info; +} + +void +mock_uninstall_query_variable_info(void) +{ + qvi_installed = false; + RT->Hdr.Revision = EFI_1_10_SYSTEM_TABLE_REVISION; + RT->QueryVariableInfo = mock_efi_unsupported; +} + +EFI_CONFIGURATION_TABLE mock_config_table[MOCK_CONFIG_TABLE_ENTRIES] = { + { + .VendorGuid = { 0, }, + .VendorTable = NULL + }, +}; + +int +mock_config_table_cmp(const void *p0, const void *p1) +{ + EFI_CONFIGURATION_TABLE *entry0, *entry1; + long cmp; + + if (!p0 || !p1) { + cmp = (int)((intptr_t)p0 - (intptr_t)p1); + } else { + entry0 = (EFI_CONFIGURATION_TABLE *)p0; + entry1 = (EFI_CONFIGURATION_TABLE *)p1; +#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) + printf("comparing %p to %p\n", p0, p1); +#endif + cmp = CompareGuid(&entry0->VendorGuid, &entry1->VendorGuid); + } + + if (mock_config_table_sort_policy == MOCK_SORT_DESCENDING) { + cmp = -cmp; + } + + return cmp; +} + +EFI_STATUS EFIAPI +mock_install_configuration_table(EFI_GUID *guid, VOID *table) +{ + bool found = false; + EFI_CONFIGURATION_TABLE *entry; + int idx = 0; + size_t sz; + + if (!guid) + return EFI_INVALID_PARAMETER; + + for (UINTN i = 0; i < ST->NumberOfTableEntries; i++) { + EFI_CONFIGURATION_TABLE *entry = &ST->ConfigurationTable[i]; + + if (CompareGuid(guid, &entry->VendorGuid) == 0) { + found = true; + if (table) { + // replace it + entry->VendorTable = table; + } else { + // delete it + ST->NumberOfTableEntries -= 1; + sz = ST->NumberOfTableEntries - i; + sz *= sizeof(*entry); + memmove(&entry[0], &entry[1], sz); + } + return EFI_SUCCESS; + } + } + if (!found && table == NULL) + return EFI_NOT_FOUND; + if (ST->NumberOfTableEntries == MOCK_CONFIG_TABLE_ENTRIES - 1) { + /* + * If necessary, we could allocate another table and copy + * the data, but I'm lazy and we probably don't need to. + */ + return EFI_OUT_OF_RESOURCES; + } + + switch (mock_config_table_sort_policy) { + case MOCK_SORT_DESCENDING: + case MOCK_SORT_ASCENDING: + case MOCK_SORT_APPEND: + idx = ST->NumberOfTableEntries; + break; + case MOCK_SORT_PREPEND: + sz = ST->NumberOfTableEntries ? ST->NumberOfTableEntries : 0; + sz *= sizeof(ST->ConfigurationTable[0]); + memmove(&ST->ConfigurationTable[1], &ST->ConfigurationTable[0], sz); + idx = 0; + break; + default: + break; + } + + entry = &ST->ConfigurationTable[idx]; + memcpy(&entry->VendorGuid, guid, sizeof(EFI_GUID)); + entry->VendorTable = table; +#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) + printf("%s:%d:%s(): installing entry %p={%p,%p} as entry %d\n", + __FILE__, __LINE__, __func__, + entry, &entry->VendorGuid, entry->VendorTable, idx); +#endif + ST->NumberOfTableEntries += 1; + +#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) + printf("%s:%d:%s():ST->ConfigurationTable:%p\n" + "\t[%d]:%p\n" + "\t[%d]:%p\n", + __FILE__, __LINE__, __func__, ST->ConfigurationTable, + 0, &ST->ConfigurationTable[0], + 1, &ST->ConfigurationTable[1]); +#endif + switch (mock_config_table_sort_policy) { + case MOCK_SORT_DESCENDING: + case MOCK_SORT_ASCENDING: +#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) + printf("%s:%d:%s(): entries before sorting:\n", __FILE__, __LINE__, __func__); + for (UINTN i = 0; i < ST->NumberOfTableEntries; i++) { + printf("\t[%d] = %p = {", i, &ST->ConfigurationTable[i]); + printf(".VendorGuid=" GUID_FMT, GUID_ARGS(ST->ConfigurationTable[i].VendorGuid)); + printf(".VendorTable=%p}\n", ST->ConfigurationTable[i].VendorTable); + } +#endif + qsort(&ST->ConfigurationTable[0], ST->NumberOfTableEntries, + sizeof(ST->ConfigurationTable[0]), + mock_config_table_cmp); + break; + default: + break; + } +#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) + printf("%s:%d:%s(): entries:\n", __FILE__, __LINE__, __func__); + for (UINTN i = 0; i < ST->NumberOfTableEntries; i++) { + printf("\t[%d] = %p = {", i, &ST->ConfigurationTable[i]); + printf(".VendorGuid=" GUID_FMT, GUID_ARGS(ST->ConfigurationTable[i].VendorGuid)); + printf(".VendorTable=%p}\n", ST->ConfigurationTable[i].VendorTable); + } +#endif + + return EFI_SUCCESS; +} + +void CONSTRUCTOR +mock_reset_variables(void) +{ + list_t *pos = NULL, *tmp = NULL; + static bool once = true; + + init_efi_system_table(); + + mock_set_variable_pre_hook = NULL; + mock_set_variable_post_hook = NULL; + mock_get_variable_pre_hook = NULL; + mock_get_variable_post_hook = NULL; + mock_get_next_variable_name_pre_hook = NULL; + mock_get_next_variable_name_post_hook = NULL; + mock_query_variable_info_pre_hook = NULL; + mock_query_variable_info_post_hook = NULL; + + if (once) { + INIT_LIST_HEAD(&mock_variables); + once = false; +#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) + printf("%s:%d:%s():mock_variables = {%p,%p};\n", + __FILE__, __LINE__-1, __func__, + mock_variables.next, + mock_variables.prev); + printf("%s:%d:%s():list_empty(&mock_variables):%d\n", + __FILE__, __LINE__-1, __func__, list_empty(&mock_variables)); + printf("%s:%d:%s():list_size(&mock_variables):%d\n", + __FILE__, __LINE__-1, __func__, list_size(&mock_variables)); +#endif + } + + list_for_each_safe(pos, tmp, &mock_variables) { + struct mock_variable *var = NULL; + var = list_entry(pos, struct mock_variable, list); + +#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) + printf("%s:%d:%s():var:"GUID_FMT"-%s\n", + __FILE__, __LINE__-1, __func__, + GUID_ARGS(var->guid), Str2str(var->name)); +#endif + mock_delete_variable(var); + } + INIT_LIST_HEAD(&mock_variables); + mock_set_default_usage_limits(); + + mock_variable_delete_attr_policy = MOCK_VAR_DELETE_ATTR_ALLOW_ZERO; + + RT->GetVariable = mock_get_variable; + RT->GetNextVariableName = mock_get_next_variable_name; + RT->SetVariable = mock_set_variable; + if (qvi_installed) + mock_install_query_variable_info(); + else + mock_uninstall_query_variable_info(); +} + +void CONSTRUCTOR +mock_reset_config_table(void) +{ + init_efi_system_table(); + + /* + * Note that BS->InstallConfigurationTable() is *not* defined as + * freeing these. If a test case installs non-malloc()ed tables, + * it needs to call BS->InstallConfigurationTable(guid, NULL) to + * clear them. + */ + for (UINTN i = 0; i < ST->NumberOfTableEntries; i++) { + EFI_CONFIGURATION_TABLE *entry = &ST->ConfigurationTable[i]; + + if (entry->VendorTable) + free(entry->VendorTable); + } + + SetMem(ST->ConfigurationTable, + ST->NumberOfTableEntries * sizeof(EFI_CONFIGURATION_TABLE), + 0); + + ST->NumberOfTableEntries = 0; + + if (ST->ConfigurationTable != mock_config_table) { + free(ST->ConfigurationTable); + ST->ConfigurationTable = mock_config_table; + SetMem(mock_config_table, sizeof(mock_config_table), 0); + } + + BS->InstallConfigurationTable = mock_install_configuration_table; +} + +void DESTRUCTOR +mock_finalize_vars_and_configs(void) +{ + mock_reset_variables(); + mock_reset_config_table(); +} + +// vim:fenc=utf-8:tw=75:noet -- cgit v1.2.3