diff options
Diffstat (limited to 'drivers/firmware/efi/efivars.c')
| -rw-r--r-- | drivers/firmware/efi/efivars.c | 206 | 
1 files changed, 171 insertions, 35 deletions
diff --git a/drivers/firmware/efi/efivars.c b/drivers/firmware/efi/efivars.c index 8a7432a4b41..463c56545ae 100644 --- a/drivers/firmware/efi/efivars.c +++ b/drivers/firmware/efi/efivars.c @@ -69,6 +69,7 @@  #include <linux/module.h>  #include <linux/slab.h>  #include <linux/ucs2_string.h> +#include <linux/compat.h>  #define EFIVARS_VERSION "0.08"  #define EFIVARS_DATE "2004-May-17" @@ -86,6 +87,15 @@ static struct kset *efivars_kset;  static struct bin_attribute *efivars_new_var;  static struct bin_attribute *efivars_del_var; +struct compat_efi_variable { +	efi_char16_t  VariableName[EFI_VAR_NAME_LEN/sizeof(efi_char16_t)]; +	efi_guid_t    VendorGuid; +	__u32         DataSize; +	__u8          Data[1024]; +	__u32         Status; +	__u32         Attributes; +} __packed; +  struct efivar_attribute {  	struct attribute attr;  	ssize_t (*show) (struct efivar_entry *entry, char *buf); @@ -189,45 +199,107 @@ efivar_data_read(struct efivar_entry *entry, char *buf)  	memcpy(buf, var->Data, var->DataSize);  	return var->DataSize;  } -/* - * We allow each variable to be edited via rewriting the - * entire efi variable structure. - */ -static ssize_t -efivar_store_raw(struct efivar_entry *entry, const char *buf, size_t count) -{ -	struct efi_variable *new_var, *var = &entry->var; -	int err; -	if (count != sizeof(struct efi_variable)) -		return -EINVAL; - -	new_var = (struct efi_variable *)buf; +static inline int +sanity_check(struct efi_variable *var, efi_char16_t *name, efi_guid_t vendor, +	     unsigned long size, u32 attributes, u8 *data) +{  	/*  	 * If only updating the variable data, then the name  	 * and guid should remain the same  	 */ -	if (memcmp(new_var->VariableName, var->VariableName, sizeof(var->VariableName)) || -		efi_guidcmp(new_var->VendorGuid, var->VendorGuid)) { +	if (memcmp(name, var->VariableName, sizeof(var->VariableName)) || +		efi_guidcmp(vendor, var->VendorGuid)) {  		printk(KERN_ERR "efivars: Cannot edit the wrong variable!\n");  		return -EINVAL;  	} -	if ((new_var->DataSize <= 0) || (new_var->Attributes == 0)){ +	if ((size <= 0) || (attributes == 0)){  		printk(KERN_ERR "efivars: DataSize & Attributes must be valid!\n");  		return -EINVAL;  	} -	if ((new_var->Attributes & ~EFI_VARIABLE_MASK) != 0 || -	    efivar_validate(new_var, new_var->Data, new_var->DataSize) == false) { +	if ((attributes & ~EFI_VARIABLE_MASK) != 0 || +	    efivar_validate(name, data, size) == false) {  		printk(KERN_ERR "efivars: Malformed variable content\n");  		return -EINVAL;  	} -	memcpy(&entry->var, new_var, count); +	return 0; +} + +static inline bool is_compat(void) +{ +	if (IS_ENABLED(CONFIG_COMPAT) && is_compat_task()) +		return true; + +	return false; +} + +static void +copy_out_compat(struct efi_variable *dst, struct compat_efi_variable *src) +{ +	memcpy(dst->VariableName, src->VariableName, EFI_VAR_NAME_LEN); +	memcpy(dst->Data, src->Data, sizeof(src->Data)); + +	dst->VendorGuid = src->VendorGuid; +	dst->DataSize = src->DataSize; +	dst->Attributes = src->Attributes; +} + +/* + * We allow each variable to be edited via rewriting the + * entire efi variable structure. + */ +static ssize_t +efivar_store_raw(struct efivar_entry *entry, const char *buf, size_t count) +{ +	struct efi_variable *new_var, *var = &entry->var; +	efi_char16_t *name; +	unsigned long size; +	efi_guid_t vendor; +	u32 attributes; +	u8 *data; +	int err; + +	if (is_compat()) { +		struct compat_efi_variable *compat; + +		if (count != sizeof(*compat)) +			return -EINVAL; + +		compat = (struct compat_efi_variable *)buf; +		attributes = compat->Attributes; +		vendor = compat->VendorGuid; +		name = compat->VariableName; +		size = compat->DataSize; +		data = compat->Data; + +		err = sanity_check(var, name, vendor, size, attributes, data); +		if (err) +			return err; + +		copy_out_compat(&entry->var, compat); +	} else { +		if (count != sizeof(struct efi_variable)) +			return -EINVAL; + +		new_var = (struct efi_variable *)buf; + +		attributes = new_var->Attributes; +		vendor = new_var->VendorGuid; +		name = new_var->VariableName; +		size = new_var->DataSize; +		data = new_var->Data; + +		err = sanity_check(var, name, vendor, size, attributes, data); +		if (err) +			return err; + +		memcpy(&entry->var, new_var, count); +	} -	err = efivar_entry_set(entry, new_var->Attributes, -			       new_var->DataSize, new_var->Data, false); +	err = efivar_entry_set(entry, attributes, size, data, NULL);  	if (err) {  		printk(KERN_WARNING "efivars: set_variable() failed: status=%d\n", err);  		return -EIO; @@ -240,6 +312,8 @@ static ssize_t  efivar_show_raw(struct efivar_entry *entry, char *buf)  {  	struct efi_variable *var = &entry->var; +	struct compat_efi_variable *compat; +	size_t size;  	if (!entry || !buf)  		return 0; @@ -249,9 +323,23 @@ efivar_show_raw(struct efivar_entry *entry, char *buf)  			     &entry->var.DataSize, entry->var.Data))  		return -EIO; -	memcpy(buf, var, sizeof(*var)); +	if (is_compat()) { +		compat = (struct compat_efi_variable *)buf; + +		size = sizeof(*compat); +		memcpy(compat->VariableName, var->VariableName, +			EFI_VAR_NAME_LEN); +		memcpy(compat->Data, var->Data, sizeof(compat->Data)); + +		compat->VendorGuid = var->VendorGuid; +		compat->DataSize = var->DataSize; +		compat->Attributes = var->Attributes; +	} else { +		size = sizeof(*var); +		memcpy(buf, var, size); +	} -	return sizeof(*var); +	return size;  }  /* @@ -326,15 +414,39 @@ static ssize_t efivar_create(struct file *filp, struct kobject *kobj,  			     struct bin_attribute *bin_attr,  			     char *buf, loff_t pos, size_t count)  { +	struct compat_efi_variable *compat = (struct compat_efi_variable *)buf;  	struct efi_variable *new_var = (struct efi_variable *)buf;  	struct efivar_entry *new_entry; +	bool need_compat = is_compat(); +	efi_char16_t *name; +	unsigned long size; +	u32 attributes; +	u8 *data;  	int err;  	if (!capable(CAP_SYS_ADMIN))  		return -EACCES; -	if ((new_var->Attributes & ~EFI_VARIABLE_MASK) != 0 || -	    efivar_validate(new_var, new_var->Data, new_var->DataSize) == false) { +	if (need_compat) { +		if (count != sizeof(*compat)) +			return -EINVAL; + +		attributes = compat->Attributes; +		name = compat->VariableName; +		size = compat->DataSize; +		data = compat->Data; +	} else { +		if (count != sizeof(*new_var)) +			return -EINVAL; + +		attributes = new_var->Attributes; +		name = new_var->VariableName; +		size = new_var->DataSize; +		data = new_var->Data; +	} + +	if ((attributes & ~EFI_VARIABLE_MASK) != 0 || +	    efivar_validate(name, data, size) == false) {  		printk(KERN_ERR "efivars: Malformed variable content\n");  		return -EINVAL;  	} @@ -343,10 +455,13 @@ static ssize_t efivar_create(struct file *filp, struct kobject *kobj,  	if (!new_entry)  		return -ENOMEM; -	memcpy(&new_entry->var, new_var, sizeof(*new_var)); +	if (need_compat) +		copy_out_compat(&new_entry->var, compat); +	else +		memcpy(&new_entry->var, new_var, sizeof(*new_var)); -	err = efivar_entry_set(new_entry, new_var->Attributes, new_var->DataSize, -			       new_var->Data, &efivar_sysfs_list); +	err = efivar_entry_set(new_entry, attributes, size, +			       data, &efivar_sysfs_list);  	if (err) {  		if (err == -EEXIST)  			err = -EINVAL; @@ -369,26 +484,47 @@ static ssize_t efivar_delete(struct file *filp, struct kobject *kobj,  			     char *buf, loff_t pos, size_t count)  {  	struct efi_variable *del_var = (struct efi_variable *)buf; +	struct compat_efi_variable *compat;  	struct efivar_entry *entry; +	efi_char16_t *name; +	efi_guid_t vendor;  	int err = 0;  	if (!capable(CAP_SYS_ADMIN))  		return -EACCES; +	if (is_compat()) { +		if (count != sizeof(*compat)) +			return -EINVAL; + +		compat = (struct compat_efi_variable *)buf; +		name = compat->VariableName; +		vendor = compat->VendorGuid; +	} else { +		if (count != sizeof(*del_var)) +			return -EINVAL; + +		name = del_var->VariableName; +		vendor = del_var->VendorGuid; +	} +  	efivar_entry_iter_begin(); -	entry = efivar_entry_find(del_var->VariableName, del_var->VendorGuid, -				  &efivar_sysfs_list, true); +	entry = efivar_entry_find(name, vendor, &efivar_sysfs_list, true);  	if (!entry)  		err = -EINVAL;  	else if (__efivar_entry_delete(entry))  		err = -EIO; -	efivar_entry_iter_end(); - -	if (err) +	if (err) { +		efivar_entry_iter_end();  		return err; +	} -	efivar_unregister(entry); +	if (!entry->scanning) { +		efivar_entry_iter_end(); +		efivar_unregister(entry); +	} else +		efivar_entry_iter_end();  	/* It's dead Jim.... */  	return count; @@ -564,7 +700,7 @@ static int efivar_sysfs_destroy(struct efivar_entry *entry, void *data)  	return 0;  } -void efivars_sysfs_exit(void) +static void efivars_sysfs_exit(void)  {  	/* Remove all entries and destroy */  	__efivar_entry_iter(efivar_sysfs_destroy, &efivar_sysfs_list, NULL, NULL);  | 
