/* * SPDX-License-Identifier: GPL-2.0-or-later * * uefi vars device - VarCheckPolicyLibMmiHandler implementation * * variable policy specs: * https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Library/VariablePolicyLib/ReadMe.md */ #include "qemu/osdep.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" static void calc_policy(uefi_var_policy *pol); static int uefi_var_policy_post_load(void *opaque, int version_id) { uefi_var_policy *pol = opaque; calc_policy(pol); return 0; } const VMStateDescription vmstate_uefi_var_policy = { .name = "uefi-var-policy", .post_load = uefi_var_policy_post_load, .fields = (VMStateField[]) { VMSTATE_UINT32(entry_size, uefi_var_policy), VMSTATE_VBUFFER_ALLOC_UINT32(entry, uefi_var_policy, 0, NULL, entry_size), VMSTATE_END_OF_LIST() }, }; static void print_policy_entry(variable_policy_entry *pe) { uint16_t *name = (void *)pe + pe->offset_to_name; fprintf(stderr, "%s:\n", __func__); fprintf(stderr, " name ยด"); while (*name) { fprintf(stderr, "%c", *name); name++; } fprintf(stderr, "', version=%d.%d, size=%d\n", pe->version >> 16, pe->version & 0xffff, pe->size); if (pe->min_size) { fprintf(stderr, " size min=%d\n", pe->min_size); } if (pe->max_size != UINT32_MAX) { fprintf(stderr, " size max=%u\n", pe->max_size); } if (pe->attributes_must_have) { fprintf(stderr, " attr must=0x%x\n", pe->attributes_must_have); } if (pe->attributes_cant_have) { fprintf(stderr, " attr cant=0x%x\n", pe->attributes_cant_have); } if (pe->lock_policy_type) { fprintf(stderr, " lock policy type %d\n", pe->lock_policy_type); } } static gboolean wildcard_str_equal(uefi_var_policy *pol, uefi_variable *var) { return uefi_str_equal_ex(pol->name, pol->name_size, var->name, var->name_size, true); } static uefi_var_policy *find_policy(uefi_vars_state *uv, QemuUUID guid, uint16_t *name, uint64_t name_size) { uefi_var_policy *pol; QTAILQ_FOREACH(pol, &uv->var_policies, next) { if (!qemu_uuid_is_equal(&pol->entry->namespace, &guid)) { continue; } if (!uefi_str_equal(pol->name, pol->name_size, name, name_size)) { continue; } return pol; } return NULL; } static uefi_var_policy *wildcard_find_policy(uefi_vars_state *uv, uefi_variable *var) { uefi_var_policy *pol; QTAILQ_FOREACH(pol, &uv->var_policies, next) { if (!qemu_uuid_is_equal(&pol->entry->namespace, &var->guid)) { continue; } if (!wildcard_str_equal(pol, var)) { continue; } return pol; } return NULL; } static void calc_policy(uefi_var_policy *pol) { variable_policy_entry *pe = pol->entry; unsigned int i; pol->name = (void *)pol->entry + pe->offset_to_name; pol->name_size = pe->size - pe->offset_to_name; for (i = 0; i < pol->name_size / 2; i++) { if (pol->name[i] == '#') { pol->hashmarks++; } } } uefi_var_policy *uefi_vars_add_policy(uefi_vars_state *uv, variable_policy_entry *pe) { uefi_var_policy *pol, *p; pol = g_new0(uefi_var_policy, 1); pol->entry = g_malloc(pe->size); memcpy(pol->entry, pe, pe->size); pol->entry_size = pe->size; calc_policy(pol); /* keep list sorted by priority, add to tail of priority group */ QTAILQ_FOREACH(p, &uv->var_policies, next) { if ((p->hashmarks > pol->hashmarks) || (!p->name_size && pol->name_size)) { QTAILQ_INSERT_BEFORE(p, pol, next); return pol; } } QTAILQ_INSERT_TAIL(&uv->var_policies, pol, next); return pol; } efi_status uefi_vars_policy_check(uefi_vars_state *uv, uefi_variable *var, gboolean is_newvar) { uefi_var_policy *pol; variable_policy_entry *pe; variable_lock_on_var_state *lvarstate; uint16_t *lvarname; size_t lvarnamesize; uefi_variable *lvar; if (!uv->end_of_dxe) { return EFI_SUCCESS; } pol = wildcard_find_policy(uv, var); if (!pol) { return EFI_SUCCESS; } pe = pol->entry; uefi_trace_variable(__func__, var->guid, var->name, var->name_size); print_policy_entry(pe); if ((var->attributes & pe->attributes_must_have) != pe->attributes_must_have) { trace_uefi_vars_policy_deny("must-have-attr"); return EFI_INVALID_PARAMETER; } if ((var->attributes & pe->attributes_cant_have) != 0) { trace_uefi_vars_policy_deny("cant-have-attr"); return EFI_INVALID_PARAMETER; } if (var->data_size < pe->min_size) { trace_uefi_vars_policy_deny("min-size"); return EFI_INVALID_PARAMETER; } if (var->data_size > pe->max_size) { trace_uefi_vars_policy_deny("max-size"); return EFI_INVALID_PARAMETER; } switch (pe->lock_policy_type) { case VARIABLE_POLICY_TYPE_NO_LOCK: break; case VARIABLE_POLICY_TYPE_LOCK_NOW: trace_uefi_vars_policy_deny("lock-now"); return EFI_WRITE_PROTECTED; case VARIABLE_POLICY_TYPE_LOCK_ON_CREATE: if (!is_newvar) { trace_uefi_vars_policy_deny("lock-on-create"); return EFI_WRITE_PROTECTED; } break; case VARIABLE_POLICY_TYPE_LOCK_ON_VAR_STATE: lvarstate = (void *)pol->entry + sizeof(*pe); lvarname = (void *)pol->entry + sizeof(*pe) + sizeof(*lvarstate); lvarnamesize = pe->offset_to_name - sizeof(*pe) - sizeof(*lvarstate); uefi_trace_variable(__func__, lvarstate->namespace, lvarname, lvarnamesize); lvar = uefi_vars_find_variable(uv, lvarstate->namespace, lvarname, lvarnamesize); if (lvar && lvar->data_size == 1) { uint8_t *value = lvar->data; if (lvarstate->value == *value) { return EFI_WRITE_PROTECTED; } } break; } return EFI_SUCCESS; } void uefi_vars_policies_clear(uefi_vars_state *uv) { uefi_var_policy *pol; while (!QTAILQ_EMPTY(&uv->var_policies)) { pol = QTAILQ_FIRST(&uv->var_policies); QTAILQ_REMOVE(&uv->var_policies, pol, next); g_free(pol->entry); g_free(pol); } } static size_t uefi_vars_mm_policy_error(mm_header *mhdr, mm_check_policy *mchk, uint64_t status) { mchk->result = status; return sizeof(*mchk); } static uint32_t uefi_vars_mm_check_policy_is_enabled(uefi_vars_state *uv, mm_header *mhdr, mm_check_policy *mchk, void *func) { mm_check_policy_is_enabled *mpar = func; size_t length; length = sizeof(*mchk) + sizeof(*mpar); if (mhdr->length < length) { return uefi_vars_mm_policy_error(mhdr, mchk, EFI_BAD_BUFFER_SIZE); } mpar->state = TRUE; mchk->result = EFI_SUCCESS; return sizeof(*mchk); } static uint32_t uefi_vars_mm_check_policy_register(uefi_vars_state *uv, mm_header *mhdr, mm_check_policy *mchk, void *func) { variable_policy_entry *pe = func; uefi_var_policy *pol; uint64_t length; if (uadd64_overflow(sizeof(*mchk), pe->size, &length)) { return uefi_vars_mm_policy_error(mhdr, mchk, EFI_BAD_BUFFER_SIZE); } if (mhdr->length < length) { return uefi_vars_mm_policy_error(mhdr, mchk, EFI_BAD_BUFFER_SIZE); } if (pe->size < sizeof(*pe)) { return uefi_vars_mm_policy_error(mhdr, mchk, EFI_BAD_BUFFER_SIZE); } if (pe->offset_to_name < sizeof(*pe)) { return uefi_vars_mm_policy_error(mhdr, mchk, EFI_BAD_BUFFER_SIZE); } if (pe->lock_policy_type == VARIABLE_POLICY_TYPE_LOCK_ON_VAR_STATE && pe->offset_to_name < sizeof(*pe) + sizeof(variable_lock_on_var_state)) { return uefi_vars_mm_policy_error(mhdr, mchk, EFI_BAD_BUFFER_SIZE); } /* check space for minimum string length */ if (pe->size < (size_t)pe->offset_to_name) { return uefi_vars_mm_policy_error(mhdr, mchk, EFI_BAD_BUFFER_SIZE); } if (!uefi_str_is_valid((void *)pe + pe->offset_to_name, pe->size - pe->offset_to_name, false)) { return uefi_vars_mm_policy_error(mhdr, mchk, EFI_INVALID_PARAMETER); } pol = find_policy(uv, pe->namespace, (void *)pe + pe->offset_to_name, pe->size - pe->offset_to_name); if (pol) { return uefi_vars_mm_policy_error(mhdr, mchk, EFI_ALREADY_STARTED); } uefi_vars_add_policy(uv, pe); mchk->result = EFI_SUCCESS; return sizeof(*mchk); } uint32_t uefi_vars_mm_check_policy_proto(uefi_vars_state *uv) { static const char *fnames[] = { "zero", "disable", "is-enabled", "register", "dump", "lock", }; const char *fname; mm_header *mhdr = (mm_header *) uv->buffer; mm_check_policy *mchk = (mm_check_policy *) (uv->buffer + sizeof(*mhdr)); void *func = (uv->buffer + sizeof(*mhdr) + sizeof(*mchk)); if (mhdr->length < sizeof(*mchk)) { return UEFI_VARS_STS_ERR_BAD_BUFFER_SIZE; } fname = mchk->command < ARRAY_SIZE(fnames) ? fnames[mchk->command] : "unknown"; trace_uefi_vars_policy_cmd(fname); switch (mchk->command) { case VAR_CHECK_POLICY_COMMAND_DISABLE: mchk->result = EFI_UNSUPPORTED; break; case VAR_CHECK_POLICY_COMMAND_IS_ENABLED: uefi_vars_mm_check_policy_is_enabled(uv, mhdr, mchk, func); break; case VAR_CHECK_POLICY_COMMAND_REGISTER: if (uv->policy_locked) { mchk->result = EFI_WRITE_PROTECTED; } else { uefi_vars_mm_check_policy_register(uv, mhdr, mchk, func); } break; case VAR_CHECK_POLICY_COMMAND_LOCK: uv->policy_locked = true; mchk->result = EFI_SUCCESS; break; default: mchk->result = EFI_UNSUPPORTED; break; } uefi_trace_status(__func__, mchk->result); return UEFI_VARS_STS_SUCCESS; }