diff options
-rw-r--r-- | include/mm_communication.h | 43 | ||||
-rw-r--r-- | lib/efi_loader/efi_variable_tee.c | 142 |
2 files changed, 179 insertions, 6 deletions
diff --git a/include/mm_communication.h b/include/mm_communication.h index 193c4d1578..f9c05bb7f1 100644 --- a/include/mm_communication.h +++ b/include/mm_communication.h @@ -205,4 +205,47 @@ struct smm_variable_query_info { u32 attr; }; +#define VAR_CHECK_VARIABLE_PROPERTY_REVISION 0x0001 +#define VAR_CHECK_VARIABLE_PROPERTY_READ_ONLY BIT(0) +/** + * struct var_check_property - Used to store variable properties in StMM + * + * @revision: magic revision number for variable property checking + * @property: properties mask for the variable used in StMM. + * Currently RO flag is supported + * @attributes: variable attributes used in StMM checking when properties + * for a variable are enabled + * @minsize: minimum allowed size for variable payload checked against + * smm_variable_access->datasize in StMM + * @maxsize: maximum allowed size for variable payload checked against + * smm_variable_access->datasize in StMM + * + * Defined in EDK2 as VAR_CHECK_VARIABLE_PROPERTY. + */ +struct var_check_property { + u16 revision; + u16 property; + u32 attributes; + efi_uintn_t minsize; + efi_uintn_t maxsize; +}; + +/** + * struct smm_variable_var_check_property - Used to communicate variable + * properties with StMM + * + * @guid: vendor GUID + * @name_size: size of EFI name + * @property: variable properties struct + * @name: variable name + * + * Defined in EDK2 as SMM_VARIABLE_COMMUNICATE_VAR_CHECK_VARIABLE_PROPERTY. + */ +struct smm_variable_var_check_property { + efi_guid_t guid; + efi_uintn_t name_size; + struct var_check_property property; + u16 name[]; +}; + #endif /* _MM_COMMUNICATION_H_ */ diff --git a/lib/efi_loader/efi_variable_tee.c b/lib/efi_loader/efi_variable_tee.c index ff90aa8e81..24e0663ebd 100644 --- a/lib/efi_loader/efi_variable_tee.c +++ b/lib/efi_loader/efi_variable_tee.c @@ -244,10 +244,92 @@ out: return ret; } +/* + * StMM can store internal attributes and properties for variables, i.e enabling + * R/O variables + */ +static efi_status_t set_property_int(u16 *variable_name, efi_uintn_t name_size, + const efi_guid_t *vendor, + struct var_check_property *var_property) +{ + struct smm_variable_var_check_property *smm_property; + efi_uintn_t payload_size; + u8 *comm_buf = NULL; + efi_status_t ret; + + payload_size = sizeof(*smm_property) + name_size; + if (payload_size > max_payload_size) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + comm_buf = setup_mm_hdr((void **)&smm_property, payload_size, + SMM_VARIABLE_FUNCTION_VAR_CHECK_VARIABLE_PROPERTY_SET, + &ret); + if (!comm_buf) + goto out; + + guidcpy(&smm_property->guid, vendor); + smm_property->name_size = name_size; + memcpy(&smm_property->property, var_property, + sizeof(smm_property->property)); + memcpy(smm_property->name, variable_name, name_size); + + ret = mm_communicate(comm_buf, payload_size); + +out: + free(comm_buf); + return ret; +} + +static efi_status_t get_property_int(u16 *variable_name, efi_uintn_t name_size, + const efi_guid_t *vendor, + struct var_check_property *var_property) +{ + struct smm_variable_var_check_property *smm_property; + efi_uintn_t payload_size; + u8 *comm_buf = NULL; + efi_status_t ret; + + memset(var_property, 0, sizeof(*var_property)); + payload_size = sizeof(*smm_property) + name_size; + if (payload_size > max_payload_size) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + comm_buf = setup_mm_hdr((void **)&smm_property, payload_size, + SMM_VARIABLE_FUNCTION_VAR_CHECK_VARIABLE_PROPERTY_GET, + &ret); + if (!comm_buf) + goto out; + + guidcpy(&smm_property->guid, vendor); + smm_property->name_size = name_size; + memcpy(smm_property->name, variable_name, name_size); + + ret = mm_communicate(comm_buf, payload_size); + /* + * Currently only R/O property is supported in StMM. + * Variables that are not set to R/O will not set the property in StMM + * and the call will return EFI_NOT_FOUND. We are setting the + * properties to 0x0 so checking against that is enough for the + * EFI_NOT_FOUND case. + */ + if (ret == EFI_NOT_FOUND) + ret = EFI_SUCCESS; + if (ret != EFI_SUCCESS) + goto out; + memcpy(var_property, &smm_property->property, sizeof(*var_property)); + +out: + free(comm_buf); + return ret; +} + efi_status_t efi_get_variable_int(u16 *variable_name, const efi_guid_t *vendor, u32 *attributes, efi_uintn_t *data_size, void *data, u64 *timep) { + struct var_check_property var_property; struct smm_variable_access *var_acc; efi_uintn_t payload_size; efi_uintn_t name_size; @@ -299,8 +381,16 @@ efi_status_t efi_get_variable_int(u16 *variable_name, const efi_guid_t *vendor, if (ret != EFI_SUCCESS) goto out; - if (attributes) + ret = get_property_int(variable_name, name_size, vendor, &var_property); + if (ret != EFI_SUCCESS) + goto out; + + if (attributes) { *attributes = var_acc->attr; + if (var_property.property & VAR_CHECK_VARIABLE_PROPERTY_READ_ONLY) + *attributes |= EFI_VARIABLE_READ_ONLY; + } + if (data) memcpy(data, (u8 *)var_acc->name + var_acc->name_size, var_acc->data_size); @@ -387,11 +477,13 @@ efi_status_t efi_set_variable_int(u16 *variable_name, const efi_guid_t *vendor, u32 attributes, efi_uintn_t data_size, const void *data, bool ro_check) { + efi_status_t ret, alt_ret = EFI_SUCCESS; + struct var_check_property var_property; struct smm_variable_access *var_acc; efi_uintn_t payload_size; efi_uintn_t name_size; u8 *comm_buf = NULL; - efi_status_t ret; + bool ro; if (!variable_name || variable_name[0] == 0 || !vendor) { ret = EFI_INVALID_PARAMETER; @@ -401,7 +493,6 @@ efi_status_t efi_set_variable_int(u16 *variable_name, const efi_guid_t *vendor, ret = EFI_INVALID_PARAMETER; goto out; } - /* Check payload size */ name_size = u16_strsize(variable_name); payload_size = MM_VARIABLE_ACCESS_HEADER_SIZE + name_size + data_size; @@ -410,12 +501,41 @@ efi_status_t efi_set_variable_int(u16 *variable_name, const efi_guid_t *vendor, goto out; } - /* Get communication buffer and initialize header */ + /* + * Allocate the buffer early, before switching to RW (if needed) + * so we won't need to account for any failures in reading/setting + * the properties, if the allocation fails + */ comm_buf = setup_mm_hdr((void **)&var_acc, payload_size, SMM_VARIABLE_FUNCTION_SET_VARIABLE, &ret); if (!comm_buf) goto out; + ro = !!(attributes & EFI_VARIABLE_READ_ONLY); + attributes &= EFI_VARIABLE_MASK; + + /* + * The API has the ability to override RO flags. If no RO check was + * requested switch the variable to RW for the duration of this call + */ + ret = get_property_int(variable_name, name_size, vendor, + &var_property); + if (ret != EFI_SUCCESS) + goto out; + + if (var_property.property & VAR_CHECK_VARIABLE_PROPERTY_READ_ONLY) { + /* Bypass r/o check */ + if (!ro_check) { + var_property.property &= ~VAR_CHECK_VARIABLE_PROPERTY_READ_ONLY; + ret = set_property_int(variable_name, name_size, vendor, &var_property); + if (ret != EFI_SUCCESS) + goto out; + } else { + ret = EFI_WRITE_PROTECTED; + goto out; + } + } + /* Fill in contents */ guidcpy(&var_acc->guid, vendor); var_acc->data_size = data_size; @@ -426,10 +546,20 @@ efi_status_t efi_set_variable_int(u16 *variable_name, const efi_guid_t *vendor, /* Communicate */ ret = mm_communicate(comm_buf, payload_size); - + if (ret != EFI_SUCCESS) + alt_ret = ret; + + if (ro && !(var_property.property & VAR_CHECK_VARIABLE_PROPERTY_READ_ONLY)) { + var_property.revision = VAR_CHECK_VARIABLE_PROPERTY_REVISION; + var_property.property |= VAR_CHECK_VARIABLE_PROPERTY_READ_ONLY; + var_property.attributes = attributes; + var_property.minsize = 1; + var_property.maxsize = var_acc->data_size; + ret = set_property_int(variable_name, name_size, vendor, &var_property); + } out: free(comm_buf); - return ret; + return alt_ret == EFI_SUCCESS ? ret : alt_ret; } efi_status_t efi_query_variable_info_int(u32 attributes, |