aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatt Fleming <matt.fleming@intel.com>2012-11-09 21:02:56 +0000
committerMatt Fleming <matt.fleming@intel.com>2012-11-13 12:33:21 +0000
commit89d16665d388837b30972081d97b814be26d68a2 (patch)
tree0fd95e1e9d6886c27e7d9b7eed7464cd2e22988d
parentcfcf2f11708f934d2bd294f973c2fcb0cc54f293 (diff)
efivarfs: Use query_variable_info() to limit kmalloc()
We don't want someone who can write EFI variables to be able to allocate arbitrarily large amounts of memory, so cap it to something sensible like the amount of free space for EFI variables. Acked-by: Jeremy Kerr <jeremy.kerr@canonical.com> Cc: Matthew Garrett <mjg@redhat.com> Cc: Alan Cox <alan@lxorguk.ukuu.org.uk> Signed-off-by: Matt Fleming <matt.fleming@intel.com>
-rw-r--r--drivers/firmware/efivars.c43
-rw-r--r--include/linux/efi.h1
2 files changed, 35 insertions, 9 deletions
diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c
index 9ac934018bb..d6b8d2f628f 100644
--- a/drivers/firmware/efivars.c
+++ b/drivers/firmware/efivars.c
@@ -694,28 +694,51 @@ static ssize_t efivarfs_file_write(struct file *file,
struct inode *inode = file->f_mapping->host;
unsigned long datasize = count - sizeof(attributes);
unsigned long newdatasize;
+ u64 storage_size, remaining_size, max_size;
ssize_t bytes = 0;
if (count < sizeof(attributes))
return -EINVAL;
- data = kmalloc(datasize, GFP_KERNEL);
+ if (copy_from_user(&attributes, userbuf, sizeof(attributes)))
+ return -EFAULT;
- if (!data)
- return -ENOMEM;
+ if (attributes & ~(EFI_VARIABLE_MASK))
+ return -EINVAL;
efivars = var->efivars;
- if (copy_from_user(&attributes, userbuf, sizeof(attributes))) {
- bytes = -EFAULT;
- goto out;
+ /*
+ * Ensure that the user can't allocate arbitrarily large
+ * amounts of memory. Pick a default size of 64K if
+ * QueryVariableInfo() isn't supported by the firmware.
+ */
+ spin_lock(&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);
}
- if (attributes & ~(EFI_VARIABLE_MASK)) {
- bytes = -EINVAL;
- goto out;
+ spin_unlock(&efivars->lock);
+
+ if (status != EFI_SUCCESS) {
+ if (status != EFI_UNSUPPORTED)
+ return efi_status_to_err(status);
+
+ remaining_size = 65536;
}
+ if (datasize > remaining_size)
+ return -ENOSPC;
+
+ data = kmalloc(datasize, GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
if (copy_from_user(data, userbuf + sizeof(attributes), datasize)) {
bytes = -EFAULT;
goto out;
@@ -1709,6 +1732,8 @@ efivars_init(void)
ops.get_variable = efi.get_variable;
ops.set_variable = efi.set_variable;
ops.get_next_variable = efi.get_next_variable;
+ ops.query_variable_info = efi.query_variable_info;
+
error = register_efivars(&__efivars, &ops, efi_kobj);
if (error)
goto err_put;
diff --git a/include/linux/efi.h b/include/linux/efi.h
index 5e2308d9c6b..f80079cd84f 100644
--- a/include/linux/efi.h
+++ b/include/linux/efi.h
@@ -646,6 +646,7 @@ struct efivar_operations {
efi_get_variable_t *get_variable;
efi_get_next_variable_t *get_next_variable;
efi_set_variable_t *set_variable;
+ efi_query_variable_info_t *query_variable_info;
};
struct efivars {