Commit 68d92986 authored by Matthew Garrett's avatar Matthew Garrett Committed by Matt Fleming

efi: be more paranoid about available space when creating variables

UEFI variables are typically stored in flash. For various reasons, avaiable
space is typically not reclaimed immediately upon the deletion of a
variable - instead, the system will garbage collect during initialisation
after a reboot.

Some systems appear to handle this garbage collection extremely poorly,
failing if more than 50% of the system flash is in use. This can result in
the machine refusing to boot. The safest thing to do for the moment is to
forbid writes if they'd end up using more than half of the storage space.
We can make this more finegrained later if we come up with a method for
identifying the broken machines.
Signed-off-by: default avatarMatthew Garrett <matthew.garrett@nebula.com>
Cc: Josh Boyer <jwboyer@redhat.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: default avatarMatt Fleming <matt.fleming@intel.com>
parent 6dbe51c2
...@@ -426,6 +426,44 @@ get_var_data(struct efivars *efivars, struct efi_variable *var) ...@@ -426,6 +426,44 @@ get_var_data(struct efivars *efivars, struct efi_variable *var)
return status; 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 static ssize_t
efivar_guid_read(struct efivar_entry *entry, char *buf) 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) ...@@ -547,11 +585,16 @@ efivar_store_raw(struct efivar_entry *entry, const char *buf, size_t count)
} }
spin_lock_irq(&efivars->lock); spin_lock_irq(&efivars->lock);
status = efivars->ops->set_variable(new_var->VariableName,
&new_var->VendorGuid, status = check_var_size_locked(efivars, new_var->Attributes,
new_var->Attributes, new_var->DataSize + utf16_strsize(new_var->VariableName, 1024));
new_var->DataSize,
new_var->Data); 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); spin_unlock_irq(&efivars->lock);
...@@ -702,8 +745,7 @@ static ssize_t efivarfs_file_write(struct file *file, ...@@ -702,8 +745,7 @@ static ssize_t efivarfs_file_write(struct file *file,
u32 attributes; u32 attributes;
struct inode *inode = file->f_mapping->host; struct inode *inode = file->f_mapping->host;
unsigned long datasize = count - sizeof(attributes); unsigned long datasize = count - sizeof(attributes);
unsigned long newdatasize; unsigned long newdatasize, varsize;
u64 storage_size, remaining_size, max_size;
ssize_t bytes = 0; ssize_t bytes = 0;
if (count < sizeof(attributes)) if (count < sizeof(attributes))
...@@ -722,28 +764,18 @@ static ssize_t efivarfs_file_write(struct file *file, ...@@ -722,28 +764,18 @@ static ssize_t efivarfs_file_write(struct file *file,
* amounts of memory. Pick a default size of 64K if * amounts of memory. Pick a default size of 64K if
* QueryVariableInfo() isn't supported by the firmware. * QueryVariableInfo() isn't supported by the firmware.
*/ */
spin_lock_irq(&efivars->lock);
if (!efivars->ops->query_variable_info) varsize = datasize + utf16_strsize(var->var.VariableName, 1024);
status = EFI_UNSUPPORTED; status = check_var_size(efivars, attributes, varsize);
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);
if (status != EFI_SUCCESS) { if (status != EFI_SUCCESS) {
if (status != EFI_UNSUPPORTED) if (status != EFI_UNSUPPORTED)
return efi_status_to_err(status); 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); data = kmalloc(datasize, GFP_KERNEL);
if (!data) if (!data)
return -ENOMEM; return -ENOMEM;
...@@ -765,6 +797,19 @@ static ssize_t efivarfs_file_write(struct file *file, ...@@ -765,6 +797,19 @@ static ssize_t efivarfs_file_write(struct file *file,
*/ */
spin_lock_irq(&efivars->lock); 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, status = efivars->ops->set_variable(var->var.VariableName,
&var->var.VendorGuid, &var->var.VendorGuid,
attributes, datasize, attributes, datasize,
...@@ -1345,7 +1390,6 @@ static int efi_pstore_write(enum pstore_type_id type, ...@@ -1345,7 +1390,6 @@ static int efi_pstore_write(enum pstore_type_id type,
efi_guid_t vendor = LINUX_EFI_CRASH_GUID; efi_guid_t vendor = LINUX_EFI_CRASH_GUID;
struct efivars *efivars = psi->data; struct efivars *efivars = psi->data;
int i, ret = 0; int i, ret = 0;
u64 storage_space, remaining_space, max_variable_size;
efi_status_t status = EFI_NOT_FOUND; efi_status_t status = EFI_NOT_FOUND;
unsigned long flags; unsigned long flags;
...@@ -1365,11 +1409,11 @@ static int efi_pstore_write(enum pstore_type_id type, ...@@ -1365,11 +1409,11 @@ static int efi_pstore_write(enum pstore_type_id type,
* size: a size of logging data * size: a size of logging data
* DUMP_NAME_LEN * 2: a maximum size of variable name * DUMP_NAME_LEN * 2: a maximum size of variable name
*/ */
status = efivars->ops->query_variable_info(PSTORE_EFI_ATTRIBUTES,
&storage_space, status = check_var_size_locked(efivars, PSTORE_EFI_ATTRIBUTES,
&remaining_space, size + DUMP_NAME_LEN * 2);
&max_variable_size);
if (status || remaining_space < size + DUMP_NAME_LEN * 2) { if (status) {
spin_unlock_irqrestore(&efivars->lock, flags); spin_unlock_irqrestore(&efivars->lock, flags);
*id = part; *id = part;
return -ENOSPC; return -ENOSPC;
...@@ -1544,6 +1588,14 @@ static ssize_t efivar_create(struct file *filp, struct kobject *kobj, ...@@ -1544,6 +1588,14 @@ static ssize_t efivar_create(struct file *filp, struct kobject *kobj,
return -EINVAL; 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 */ /* now *really* create the variable via EFI */
status = efivars->ops->set_variable(new_var->VariableName, status = efivars->ops->set_variable(new_var->VariableName,
&new_var->VendorGuid, &new_var->VendorGuid,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment