efivarfs: Use query_variable_info() to limit kmalloc()
authorMatt Fleming <matt.fleming@intel.com>
Fri, 9 Nov 2012 21:02:56 +0000 (21:02 +0000)
committerMatt Fleming <matt.fleming@intel.com>
Tue, 13 Nov 2012 12:33:21 +0000 (12:33 +0000)
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>
drivers/firmware/efivars.c
include/linux/efi.h

index 9ac934018bba10003c74078d4796fd7034a1b5ac..d6b8d2f628fab2617d676176998c8e55d91c7ea3 100644 (file)
@@ -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;
index 5e2308d9c6beb0b327f16bde26fc3b578b5bf5c5..f80079cd84f4542c125a1697f5ea3fd8ceb25ce4 100644 (file)
@@ -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 {