/* * SPDX-License-Identifier: GPL-2.0-or-later * * uefi vars device - EfiSmmVariableProtocol implementation */ #include "qemu/osdep.h" #include "qemu/error-report.h" #include "system/dma.h" #include "migration/vmstate.h" #include "hw/uefi/var-service.h" #include "hw/uefi/var-service-api.h" #include "hw/uefi/var-service-edk2.h" #include "trace/trace-hw_uefi.h" #define EFI_VARIABLE_ATTRIBUTE_SUPPORTED \ (EFI_VARIABLE_NON_VOLATILE | \ EFI_VARIABLE_BOOTSERVICE_ACCESS | \ EFI_VARIABLE_RUNTIME_ACCESS | \ EFI_VARIABLE_HARDWARE_ERROR_RECORD | \ EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS | \ EFI_VARIABLE_APPEND_WRITE) const VMStateDescription vmstate_uefi_time = { .name = "uefi-time", .fields = (VMStateField[]) { VMSTATE_UINT16(year, efi_time), VMSTATE_UINT8(month, efi_time), VMSTATE_UINT8(day, efi_time), VMSTATE_UINT8(hour, efi_time), VMSTATE_UINT8(minute, efi_time), VMSTATE_UINT8(second, efi_time), VMSTATE_UINT32(nanosecond, efi_time), VMSTATE_END_OF_LIST() }, }; const VMStateDescription vmstate_uefi_variable = { .name = "uefi-variable", .fields = (VMStateField[]) { VMSTATE_UINT8_ARRAY_V(guid.data, uefi_variable, sizeof(QemuUUID), 0), VMSTATE_UINT32(name_size, uefi_variable), VMSTATE_UINT32(data_size, uefi_variable), VMSTATE_UINT32(attributes, uefi_variable), VMSTATE_VBUFFER_ALLOC_UINT32(name, uefi_variable, 0, NULL, name_size), VMSTATE_VBUFFER_ALLOC_UINT32(data, uefi_variable, 0, NULL, data_size), VMSTATE_STRUCT(time, uefi_variable, 0, vmstate_uefi_time, efi_time), VMSTATE_END_OF_LIST() }, }; uefi_variable *uefi_vars_find_variable(uefi_vars_state *uv, QemuUUID guid, const uint16_t *name, uint64_t name_size) { uefi_variable *var; QTAILQ_FOREACH(var, &uv->variables, next) { if (!uefi_str_equal(var->name, var->name_size, name, name_size)) { continue; } if (!qemu_uuid_is_equal(&var->guid, &guid)) { continue; } if (!var->data_size) { /* in process of being created/updated */ continue; } return var; } return NULL; } static uefi_variable *add_variable(uefi_vars_state *uv, QemuUUID guid, const uint16_t *name, uint64_t name_size, uint32_t attributes) { uefi_variable *var; var = g_new0(uefi_variable, 1); var->guid = guid; var->name = g_malloc(name_size); memcpy(var->name, name, name_size); var->name_size = name_size; var->attributes = attributes; var->attributes &= ~EFI_VARIABLE_APPEND_WRITE; QTAILQ_INSERT_TAIL(&uv->variables, var, next); return var; } static void del_variable(uefi_vars_state *uv, uefi_variable *var) { if (!var) { return; } QTAILQ_REMOVE(&uv->variables, var, next); g_free(var->data); g_free(var->name); g_free(var->digest); g_free(var); } static size_t variable_size(uefi_variable *var) { size_t size; size = sizeof(*var); size += var->name_size; size += var->data_size; size += var->digest_size; return size; } void uefi_vars_set_variable(uefi_vars_state *uv, QemuUUID guid, const uint16_t *name, uint64_t name_size, uint32_t attributes, void *data, uint64_t data_size) { uefi_variable *old_var, *new_var; uefi_trace_variable(__func__, guid, name, name_size); old_var = uefi_vars_find_variable(uv, guid, name, name_size); if (old_var) { uv->used_storage -= variable_size(old_var); del_variable(uv, old_var); } new_var = add_variable(uv, guid, name, name_size, attributes); new_var->data = g_malloc(data_size); new_var->data_size = data_size; memcpy(new_var->data, data, data_size); uv->used_storage += variable_size(new_var); } void uefi_vars_clear_volatile(uefi_vars_state *uv) { uefi_variable *var, *n; QTAILQ_FOREACH_SAFE(var, &uv->variables, next, n) { if (var->attributes & EFI_VARIABLE_NON_VOLATILE) { continue; } uv->used_storage -= variable_size(var); del_variable(uv, var); } } void uefi_vars_clear_all(uefi_vars_state *uv) { uefi_variable *var, *n; QTAILQ_FOREACH_SAFE(var, &uv->variables, next, n) { del_variable(uv, var); } uv->used_storage = 0; } void uefi_vars_update_storage(uefi_vars_state *uv) { uefi_variable *var; uv->used_storage = 0; QTAILQ_FOREACH(var, &uv->variables, next) { uv->used_storage += variable_size(var); } } static gboolean check_access(uefi_vars_state *uv, uefi_variable *var) { if (!uv->exit_boot_service) { if (!(var->attributes & EFI_VARIABLE_BOOTSERVICE_ACCESS)) { return false; } } else { if (!(var->attributes & EFI_VARIABLE_RUNTIME_ACCESS)) { return false; } } return true; } static efi_status check_update(uefi_vars_state *uv, uefi_variable *old_var, uefi_variable *new_var) { efi_status status; if (old_var) { if (!check_access(uv, old_var)) { return EFI_ACCESS_DENIED; } } if (new_var) { if (new_var->attributes & ~EFI_VARIABLE_ATTRIBUTE_SUPPORTED) { return EFI_UNSUPPORTED; } if (!check_access(uv, new_var)) { return EFI_ACCESS_DENIED; } } if (old_var && new_var) { if (old_var->attributes != new_var->attributes) { return EFI_INVALID_PARAMETER; } } if (new_var) { /* create + update */ status = uefi_vars_policy_check(uv, new_var, old_var == NULL); } else { /* delete */ g_assert(old_var); status = uefi_vars_policy_check(uv, old_var, false); } if (status != EFI_SUCCESS) { return status; } status = uefi_vars_check_secure_boot(uv, new_var ?: old_var); if (status != EFI_SUCCESS) { return status; } return EFI_SUCCESS; } static void append_write(uefi_variable *old_var, uefi_variable *new_var) { uefi_vars_siglist siglist; uint64_t size; void *data; uefi_vars_siglist_init(&siglist); uefi_vars_siglist_parse(&siglist, old_var->data, old_var->data_size); uefi_vars_siglist_parse(&siglist, new_var->data, new_var->data_size); size = uefi_vars_siglist_blob_size(&siglist); data = g_malloc(size); uefi_vars_siglist_blob_generate(&siglist, data, size); g_free(new_var->data); new_var->data = data; new_var->data_size = size; uefi_vars_siglist_free(&siglist); } static size_t uefi_vars_mm_error(mm_header *mhdr, mm_variable *mvar, uint64_t status) { mvar->status = status; return sizeof(*mvar); } static size_t uefi_vars_mm_get_variable(uefi_vars_state *uv, mm_header *mhdr, mm_variable *mvar, void *func) { mm_variable_access *va = func; uint16_t *name; void *data; uefi_variable *var; uint64_t length; length = sizeof(*mvar) + sizeof(*va); if (mhdr->length < length) { return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); } if (va->name_size > uv->max_storage || va->data_size > uv->max_storage) { return uefi_vars_mm_error(mhdr, mvar, EFI_OUT_OF_RESOURCES); } name = func + sizeof(*va); if (uadd64_overflow(length, va->name_size, &length)) { return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); } if (mhdr->length < length) { return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); } if (!uefi_str_is_valid(name, va->name_size, true)) { return uefi_vars_mm_error(mhdr, mvar, EFI_INVALID_PARAMETER); } uefi_trace_variable(__func__, va->guid, name, va->name_size); var = uefi_vars_find_variable(uv, va->guid, name, va->name_size); if (!var) { return uefi_vars_mm_error(mhdr, mvar, EFI_NOT_FOUND); } /* check permissions etc. */ if (!check_access(uv, var)) { return uefi_vars_mm_error(mhdr, mvar, EFI_ACCESS_DENIED); } data = func + sizeof(*va) + va->name_size; if (uadd64_overflow(length, va->data_size, &length)) { return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); } if (uv->buf_size < length) { return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); } va->attributes = var->attributes; if (va->data_size < var->data_size) { va->data_size = var->data_size; length -= va->data_size; mvar->status = EFI_BUFFER_TOO_SMALL; } else { va->data_size = var->data_size; memcpy(data, var->data, var->data_size); mvar->status = EFI_SUCCESS; } return length; } static size_t uefi_vars_mm_get_next_variable(uefi_vars_state *uv, mm_header *mhdr, mm_variable *mvar, void *func) { mm_next_variable *nv = func; uefi_variable *var; uint16_t *name; uint64_t length; length = sizeof(*mvar) + sizeof(*nv); if (mhdr->length < length) { return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); } if (nv->name_size > uv->max_storage) { return uefi_vars_mm_error(mhdr, mvar, EFI_OUT_OF_RESOURCES); } name = func + sizeof(*nv); if (uadd64_overflow(length, nv->name_size, &length)) { return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); } if (mhdr->length < length) { return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); } if (!uefi_str_is_valid(name, nv->name_size, true)) { return uefi_vars_mm_error(mhdr, mvar, EFI_INVALID_PARAMETER); } if (uefi_strlen(name, nv->name_size) == 0) { /* empty string -> first */ var = QTAILQ_FIRST(&uv->variables); if (!var) { return uefi_vars_mm_error(mhdr, mvar, EFI_NOT_FOUND); } } else { var = uefi_vars_find_variable(uv, nv->guid, name, nv->name_size); if (!var) { return uefi_vars_mm_error(mhdr, mvar, EFI_INVALID_PARAMETER); } do { var = QTAILQ_NEXT(var, next); } while (var && !check_access(uv, var)); if (!var) { return uefi_vars_mm_error(mhdr, mvar, EFI_NOT_FOUND); } } length = sizeof(*mvar) + sizeof(*nv) + var->name_size; if (uv->buf_size < length) { return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); } nv->guid = var->guid; nv->name_size = var->name_size; memcpy(name, var->name, var->name_size); mvar->status = EFI_SUCCESS; return length; } static bool uefi_vars_mm_digest_compare(uefi_variable *old_var, uefi_variable *new_var) { if (!old_var->digest || !new_var->digest || !old_var->digest_size || !new_var->digest_size) { /* should not happen */ trace_uefi_vars_security_violation("inconsistent authvar digest state"); return false; } if (old_var->digest_size != new_var->digest_size) { trace_uefi_vars_security_violation("authvar digest size mismatch"); return false; } if (memcmp(old_var->digest, new_var->digest, old_var->digest_size) != 0) { trace_uefi_vars_security_violation("authvar digest data mismatch"); return false; } return true; } static size_t uefi_vars_mm_set_variable(uefi_vars_state *uv, mm_header *mhdr, mm_variable *mvar, void *func) { mm_variable_access *va = func; uint32_t attributes = 0; uint16_t *name; void *data; uefi_variable *old_var, *new_var; uint64_t length; size_t new_storage; efi_status status; length = sizeof(*mvar) + sizeof(*va); if (mhdr->length < length) { return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); } if (va->name_size > uv->max_storage || va->data_size > uv->max_storage) { return uefi_vars_mm_error(mhdr, mvar, EFI_OUT_OF_RESOURCES); } name = func + sizeof(*va); if (uadd64_overflow(length, va->name_size, &length)) { return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); } if (mhdr->length < length) { return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); } data = func + sizeof(*va) + va->name_size; if (uadd64_overflow(length, va->data_size, &length)) { return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); } if (mhdr->length < length) { return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); } g_assert(va->name_size < G_MAXUINT32); g_assert(va->data_size < G_MAXUINT32); if (!uefi_str_is_valid(name, va->name_size, true)) { return uefi_vars_mm_error(mhdr, mvar, EFI_INVALID_PARAMETER); } uefi_trace_variable(__func__, va->guid, name, va->name_size); old_var = uefi_vars_find_variable(uv, va->guid, name, va->name_size); if (va->data_size) { new_var = add_variable(uv, va->guid, name, va->name_size, va->attributes); if (va->attributes & EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS) { /* not implemented (deprecated in uefi spec) */ warn_report("%s: AUTHENTICATED_WRITE_ACCESS", __func__); mvar->status = EFI_UNSUPPORTED; goto rollback; } else if (va->attributes & EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS) { status = uefi_vars_check_auth_2(uv, new_var, va, data); if (status != EFI_SUCCESS) { mvar->status = status; goto rollback; } if (old_var && new_var) { if (uefi_time_compare(&old_var->time, &new_var->time) > 0) { trace_uefi_vars_security_violation("time check failed"); mvar->status = EFI_SECURITY_VIOLATION; goto rollback; } if (old_var->digest_size || new_var->digest_size) { if (!uefi_vars_mm_digest_compare(old_var, new_var)) { mvar->status = EFI_SECURITY_VIOLATION; goto rollback; } } } } else { new_var->data = g_malloc(va->data_size); memcpy(new_var->data, data, va->data_size); new_var->data_size = va->data_size; } if (!new_var->data) { /* we land here when deleting authenticated variables */ del_variable(uv, new_var); new_var = NULL; } } else { new_var = NULL; } if (!old_var && !new_var) { /* delete non-existing variable -> nothing to do */ mvar->status = EFI_SUCCESS; return sizeof(*mvar); } /* check permissions etc. */ status = check_update(uv, old_var, new_var); if (status != EFI_SUCCESS) { mvar->status = status; goto rollback; } if (va->attributes & EFI_VARIABLE_APPEND_WRITE && old_var && new_var) { /* merge signature databases */ if (!uefi_vars_is_sb_any(new_var)) { mvar->status = EFI_UNSUPPORTED; goto rollback; } append_write(old_var, new_var); } /* check storage space */ new_storage = uv->used_storage; if (old_var) { new_storage -= variable_size(old_var); } if (new_var) { new_storage += variable_size(new_var); } if (new_storage > uv->max_storage) { mvar->status = EFI_OUT_OF_RESOURCES; goto rollback; } attributes = new_var ? new_var->attributes : old_var->attributes; /* all good, commit */ del_variable(uv, old_var); uv->used_storage = new_storage; if (attributes & EFI_VARIABLE_NON_VOLATILE) { uefi_vars_json_save(uv); } if (new_var && uefi_vars_is_sb_pk(new_var)) { uefi_vars_auth_init(uv); } mvar->status = EFI_SUCCESS; return sizeof(*mvar); rollback: del_variable(uv, new_var); return sizeof(*mvar); } static size_t uefi_vars_mm_variable_info(uefi_vars_state *uv, mm_header *mhdr, mm_variable *mvar, void *func) { mm_variable_info *vi = func; uint64_t length; length = sizeof(*mvar) + sizeof(*vi); if (uv->buf_size < length) { return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); } vi->max_storage_size = uv->max_storage; vi->free_storage_size = uv->max_storage - uv->used_storage; vi->max_variable_size = uv->max_storage >> 2; vi->attributes = 0; mvar->status = EFI_SUCCESS; return length; } static size_t uefi_vars_mm_get_payload_size(uefi_vars_state *uv, mm_header *mhdr, mm_variable *mvar, void *func) { mm_get_payload_size *ps = func; uint64_t length; length = sizeof(*mvar) + sizeof(*ps); if (uv->buf_size < length) { return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); } ps->payload_size = uv->buf_size; mvar->status = EFI_SUCCESS; return length; } static size_t uefi_vars_mm_lock_variable(uefi_vars_state *uv, mm_header *mhdr, mm_variable *mvar, void *func) { mm_lock_variable *lv = func; variable_policy_entry *pe; uint16_t *name, *dest; uint64_t length; length = sizeof(*mvar) + sizeof(*lv); if (mhdr->length < length) { return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); } name = func + sizeof(*lv); if (uadd64_overflow(length, lv->name_size, &length)) { return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); } if (mhdr->length < length) { return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); } uefi_trace_variable(__func__, lv->guid, name, lv->name_size); pe = g_malloc0(sizeof(*pe) + lv->name_size); pe->version = VARIABLE_POLICY_ENTRY_REVISION; pe->size = sizeof(*pe) + lv->name_size; pe->offset_to_name = sizeof(*pe); pe->namespace = lv->guid; pe->min_size = 0; pe->max_size = UINT32_MAX; pe->attributes_must_have = 0; pe->attributes_cant_have = 0; pe->lock_policy_type = VARIABLE_POLICY_TYPE_LOCK_NOW; dest = (void *)pe + pe->offset_to_name; memcpy(dest, name, lv->name_size); uefi_vars_add_policy(uv, pe); g_free(pe); mvar->status = EFI_SUCCESS; return length; } uint32_t uefi_vars_mm_vars_proto(uefi_vars_state *uv) { static const char *fnames[] = { "zero", "get-variable", "get-next-variable-name", "set-variable", "query-variable-info", "ready-to-boot", "exit-boot-service", "get-statistics", "lock-variable", "var-check-prop-set", "var-check-prop-get", "get-payload-size", "init-runtime-cache-contect", "sync-runtime-cache", "get-runtime-cache-info", }; const char *fname; uint64_t length; mm_header *mhdr = (mm_header *) uv->buffer; mm_variable *mvar = (mm_variable *) (uv->buffer + sizeof(*mhdr)); void *func = (uv->buffer + sizeof(*mhdr) + sizeof(*mvar)); if (mhdr->length < sizeof(*mvar)) { return UEFI_VARS_STS_ERR_BAD_BUFFER_SIZE; } fname = mvar->function < ARRAY_SIZE(fnames) ? fnames[mvar->function] : "unknown"; trace_uefi_vars_proto_cmd(fname); switch (mvar->function) { case SMM_VARIABLE_FUNCTION_GET_VARIABLE: length = uefi_vars_mm_get_variable(uv, mhdr, mvar, func); break; case SMM_VARIABLE_FUNCTION_GET_NEXT_VARIABLE_NAME: length = uefi_vars_mm_get_next_variable(uv, mhdr, mvar, func); break; case SMM_VARIABLE_FUNCTION_SET_VARIABLE: length = uefi_vars_mm_set_variable(uv, mhdr, mvar, func); break; case SMM_VARIABLE_FUNCTION_QUERY_VARIABLE_INFO: length = uefi_vars_mm_variable_info(uv, mhdr, mvar, func); break; case SMM_VARIABLE_FUNCTION_LOCK_VARIABLE: length = uefi_vars_mm_lock_variable(uv, mhdr, mvar, func); break; case SMM_VARIABLE_FUNCTION_GET_PAYLOAD_SIZE: length = uefi_vars_mm_get_payload_size(uv, mhdr, mvar, func); break; case SMM_VARIABLE_FUNCTION_READY_TO_BOOT: trace_uefi_event("ready-to-boot"); uv->ready_to_boot = true; length = 0; break; case SMM_VARIABLE_FUNCTION_EXIT_BOOT_SERVICE: trace_uefi_event("exit-boot-service"); uv->exit_boot_service = true; length = 0; break; default: length = uefi_vars_mm_error(mhdr, mvar, EFI_UNSUPPORTED); break; } if (mhdr->length < length) { mvar->status = EFI_BUFFER_TOO_SMALL; } uefi_trace_status(__func__, mvar->status); return UEFI_VARS_STS_SUCCESS; }