| /* |
| * 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.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; |
| } |