diff options
Diffstat (limited to 'drivers/firmware/efivars.c')
| -rw-r--r-- | drivers/firmware/efivars.c | 131 | 
1 files changed, 97 insertions, 34 deletions
diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c index 7320bf89170..fe62aa39223 100644 --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -426,6 +426,44 @@ get_var_data(struct efivars *efivars, struct efi_variable *var)  	return status;  } +static efi_status_t +check_var_size_locked(struct efivars *efivars, u32 attributes, +			unsigned long size) +{ +	u64 storage_size, remaining_size, max_size; +	efi_status_t status; +	const struct efivar_operations *fops = efivars->ops; + +	if (!efivars->ops->query_variable_info) +		return EFI_UNSUPPORTED; + +	status = fops->query_variable_info(attributes, &storage_size, +					   &remaining_size, &max_size); + +	if (status != EFI_SUCCESS) +		return status; + +	if (!storage_size || size > remaining_size || size > max_size || +	    (remaining_size - size) < (storage_size / 2)) +		return EFI_OUT_OF_RESOURCES; + +	return status; +} + + +static efi_status_t +check_var_size(struct efivars *efivars, u32 attributes, unsigned long size) +{ +	efi_status_t status; +	unsigned long flags; + +	spin_lock_irqsave(&efivars->lock, flags); +	status = check_var_size_locked(efivars, attributes, size); +	spin_unlock_irqrestore(&efivars->lock, flags); + +	return status; +} +  static ssize_t  efivar_guid_read(struct efivar_entry *entry, char *buf)  { @@ -547,11 +585,16 @@ efivar_store_raw(struct efivar_entry *entry, const char *buf, size_t count)  	}  	spin_lock_irq(&efivars->lock); -	status = efivars->ops->set_variable(new_var->VariableName, -					    &new_var->VendorGuid, -					    new_var->Attributes, -					    new_var->DataSize, -					    new_var->Data); + +	status = check_var_size_locked(efivars, new_var->Attributes, +	       new_var->DataSize + utf16_strsize(new_var->VariableName, 1024)); + +	if (status == EFI_SUCCESS || status == EFI_UNSUPPORTED) +		status = efivars->ops->set_variable(new_var->VariableName, +						    &new_var->VendorGuid, +						    new_var->Attributes, +						    new_var->DataSize, +						    new_var->Data);  	spin_unlock_irq(&efivars->lock); @@ -702,8 +745,7 @@ static ssize_t efivarfs_file_write(struct file *file,  	u32 attributes;  	struct inode *inode = file->f_mapping->host;  	unsigned long datasize = count - sizeof(attributes); -	unsigned long newdatasize; -	u64 storage_size, remaining_size, max_size; +	unsigned long newdatasize, varsize;  	ssize_t bytes = 0;  	if (count < sizeof(attributes)) @@ -722,28 +764,18 @@ static ssize_t efivarfs_file_write(struct file *file,  	 * amounts of memory. Pick a default size of 64K if  	 * QueryVariableInfo() isn't supported by the firmware.  	 */ -	spin_lock_irq(&efivars->lock); - -	if (!efivars->ops->query_variable_info) -		status = EFI_UNSUPPORTED; -	else { -		const struct efivar_operations *fops = efivars->ops; -		status = fops->query_variable_info(attributes, &storage_size, -						   &remaining_size, &max_size); -	} -	spin_unlock_irq(&efivars->lock); +	varsize = datasize + utf16_strsize(var->var.VariableName, 1024); +	status = check_var_size(efivars, attributes, varsize);  	if (status != EFI_SUCCESS) {  		if (status != EFI_UNSUPPORTED)  			return efi_status_to_err(status); -		remaining_size = 65536; +		if (datasize > 65536) +			return -ENOSPC;  	} -	if (datasize > remaining_size) -		return -ENOSPC; -  	data = kmalloc(datasize, GFP_KERNEL);  	if (!data)  		return -ENOMEM; @@ -765,6 +797,19 @@ static ssize_t efivarfs_file_write(struct file *file,  	 */  	spin_lock_irq(&efivars->lock); +	/* +	 * Ensure that the available space hasn't shrunk below the safe level +	 */ + +	status = check_var_size_locked(efivars, attributes, varsize); + +	if (status != EFI_SUCCESS && status != EFI_UNSUPPORTED) { +		spin_unlock_irq(&efivars->lock); +		kfree(data); + +		return efi_status_to_err(status); +	} +  	status = efivars->ops->set_variable(var->var.VariableName,  					    &var->var.VendorGuid,  					    attributes, datasize, @@ -929,8 +974,8 @@ static bool efivarfs_valid_name(const char *str, int len)  	if (len < GUID_LEN + 2)  		return false; -	/* GUID should be right after the first '-' */ -	if (s - 1 != strchr(str, '-')) +	/* GUID must be preceded by a '-' */ +	if (*(s - 1) != '-')  		return false;  	/* @@ -1118,15 +1163,22 @@ static struct dentry_operations efivarfs_d_ops = {  static struct dentry *efivarfs_alloc_dentry(struct dentry *parent, char *name)  { +	struct dentry *d;  	struct qstr q; +	int err;  	q.name = name;  	q.len = strlen(name); -	if (efivarfs_d_hash(NULL, NULL, &q)) -		return NULL; +	err = efivarfs_d_hash(NULL, NULL, &q); +	if (err) +		return ERR_PTR(err); + +	d = d_alloc(parent, &q); +	if (d) +		return d; -	return d_alloc(parent, &q); +	return ERR_PTR(-ENOMEM);  }  static int efivarfs_fill_super(struct super_block *sb, void *data, int silent) @@ -1136,6 +1188,7 @@ static int efivarfs_fill_super(struct super_block *sb, void *data, int silent)  	struct efivar_entry *entry, *n;  	struct efivars *efivars = &__efivars;  	char *name; +	int err = -ENOMEM;  	efivarfs_sb = sb; @@ -1186,8 +1239,10 @@ static int efivarfs_fill_super(struct super_block *sb, void *data, int silent)  			goto fail_name;  		dentry = efivarfs_alloc_dentry(root, name); -		if (!dentry) +		if (IS_ERR(dentry)) { +			err = PTR_ERR(dentry);  			goto fail_inode; +		}  		/* copied by the above to local storage in the dentry. */  		kfree(name); @@ -1214,7 +1269,7 @@ fail_inode:  fail_name:  	kfree(name);  fail: -	return -ENOMEM; +	return err;  }  static struct dentry *efivarfs_mount(struct file_system_type *fs_type, @@ -1234,6 +1289,7 @@ static struct file_system_type efivarfs_type = {  	.mount   = efivarfs_mount,  	.kill_sb = efivarfs_kill_sb,  }; +MODULE_ALIAS_FS("efivarfs");  /*   * Handle negative dentry. @@ -1345,7 +1401,6 @@ static int efi_pstore_write(enum pstore_type_id type,  	efi_guid_t vendor = LINUX_EFI_CRASH_GUID;  	struct efivars *efivars = psi->data;  	int i, ret = 0; -	u64 storage_space, remaining_space, max_variable_size;  	efi_status_t status = EFI_NOT_FOUND;  	unsigned long flags; @@ -1365,11 +1420,11 @@ static int efi_pstore_write(enum pstore_type_id type,  	 * size: a size of logging data  	 * DUMP_NAME_LEN * 2: a maximum size of variable name  	 */ -	status = efivars->ops->query_variable_info(PSTORE_EFI_ATTRIBUTES, -						   &storage_space, -						   &remaining_space, -						   &max_variable_size); -	if (status || remaining_space < size + DUMP_NAME_LEN * 2) { + +	status = check_var_size_locked(efivars, PSTORE_EFI_ATTRIBUTES, +					 size + DUMP_NAME_LEN * 2); + +	if (status) {  		spin_unlock_irqrestore(&efivars->lock, flags);  		*id = part;  		return -ENOSPC; @@ -1544,6 +1599,14 @@ static ssize_t efivar_create(struct file *filp, struct kobject *kobj,  		return -EINVAL;  	} +	status = check_var_size_locked(efivars, new_var->Attributes, +	       new_var->DataSize + utf16_strsize(new_var->VariableName, 1024)); + +	if (status && status != EFI_UNSUPPORTED) { +		spin_unlock_irq(&efivars->lock); +		return efi_status_to_err(status); +	} +  	/* now *really* create the variable via EFI */  	status = efivars->ops->set_variable(new_var->VariableName,  					    &new_var->VendorGuid,  |