]> git.karo-electronics.de Git - linux-beck.git/blobdiff - drivers/firmware/efivars.c
efi: Clarify GUID length calculations
[linux-beck.git] / drivers / firmware / efivars.c
index b7c9a3261dc47c7ce4adcf101e541a7dbca22d0d..a93e401c20a78b3d48ac459c6a32e91685f978b9 100644 (file)
@@ -95,7 +95,12 @@ MODULE_LICENSE("GPL");
 MODULE_VERSION(EFIVARS_VERSION);
 
 #define DUMP_NAME_LEN 52
-#define GUID_LEN 37
+
+/*
+ * Length of a GUID string (strlen("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"))
+ * not including trailing NUL
+ */
+#define GUID_LEN 36
 
 /*
  * The maximum size of VariableName + Data = 1024
@@ -690,35 +695,45 @@ static ssize_t efivarfs_file_write(struct file *file,
                goto out;
        }
 
+       /*
+        * The lock here protects the get_variable call, the conditional
+        * set_variable call, and removal of the variable from the efivars
+        * list (in the case of an authenticated delete).
+        */
+       spin_lock(&efivars->lock);
+
        status = efivars->ops->set_variable(var->var.VariableName,
                                            &var->var.VendorGuid,
                                            attributes, datasize,
                                            data);
 
-       switch (status) {
-       case EFI_SUCCESS:
-               break;
-       case EFI_INVALID_PARAMETER:
-               count = -EINVAL;
-               goto out;
-       case EFI_OUT_OF_RESOURCES:
-               count = -ENOSPC;
-               goto out;
-       case EFI_DEVICE_ERROR:
-               count = -EIO;
-               goto out;
-       case EFI_WRITE_PROTECTED:
-               count = -EROFS;
-               goto out;
-       case EFI_SECURITY_VIOLATION:
-               count = -EACCES;
-               goto out;
-       case EFI_NOT_FOUND:
-               count = -ENOENT;
-               goto out;
-       default:
-               count = -EINVAL;
-               goto out;
+       if (status != EFI_SUCCESS) {
+               spin_unlock(&efivars->lock);
+               kfree(data);
+
+               switch (status) {
+               case EFI_INVALID_PARAMETER:
+                       count = -EINVAL;
+                       break;
+               case EFI_OUT_OF_RESOURCES:
+                       count = -ENOSPC;
+                       break;
+               case EFI_DEVICE_ERROR:
+                       count = -EIO;
+                       break;
+               case EFI_WRITE_PROTECTED:
+                       count = -EROFS;
+                       break;
+               case EFI_SECURITY_VIOLATION:
+                       count = -EACCES;
+                       break;
+               case EFI_NOT_FOUND:
+                       count = -ENOENT;
+                       break;
+               default:
+                       count = -EINVAL;
+               }
+               return count;
        }
 
        /*
@@ -734,12 +749,12 @@ static ssize_t efivarfs_file_write(struct file *file,
                                            NULL);
 
        if (status == EFI_BUFFER_TOO_SMALL) {
+               spin_unlock(&efivars->lock);
                mutex_lock(&inode->i_mutex);
                i_size_write(inode, newdatasize + sizeof(attributes));
                mutex_unlock(&inode->i_mutex);
 
        } else if (status == EFI_NOT_FOUND) {
-               spin_lock(&efivars->lock);
                list_del(&var->list);
                spin_unlock(&efivars->lock);
                efivar_unregister(var);
@@ -747,6 +762,7 @@ static ssize_t efivarfs_file_write(struct file *file,
                dput(file->f_dentry);
 
        } else {
+               spin_unlock(&efivars->lock);
                pr_warn("efivarfs: inconsistent EFI variable implementation? "
                                "status = %lx\n", status);
        }
@@ -768,9 +784,11 @@ static ssize_t efivarfs_file_read(struct file *file, char __user *userbuf,
        void *data;
        ssize_t size = 0;
 
+       spin_lock(&efivars->lock);
        status = efivars->ops->get_variable(var->var.VariableName,
                                            &var->var.VendorGuid,
                                            &attributes, &datasize, NULL);
+       spin_unlock(&efivars->lock);
 
        if (status != EFI_BUFFER_TOO_SMALL)
                return 0;
@@ -780,10 +798,13 @@ static ssize_t efivarfs_file_read(struct file *file, char __user *userbuf,
        if (!data)
                return 0;
 
+       spin_lock(&efivars->lock);
        status = efivars->ops->get_variable(var->var.VariableName,
                                            &var->var.VendorGuid,
                                            &attributes, &datasize,
                                            (data + 4));
+       spin_unlock(&efivars->lock);
+
        if (status != EFI_SUCCESS)
                goto out_free;
 
@@ -866,23 +887,30 @@ static void efivarfs_hex_to_guid(const char *str, efi_guid_t *guid)
 static int efivarfs_create(struct inode *dir, struct dentry *dentry,
                          umode_t mode, bool excl)
 {
-       struct inode *inode = efivarfs_get_inode(dir->i_sb, dir, mode, 0);
+       struct inode *inode;
        struct efivars *efivars = &__efivars;
        struct efivar_entry *var;
        int namelen, i = 0, err = 0;
 
-       if (dentry->d_name.len < 38)
+       /*
+        * We need a GUID, plus at least one letter for the variable name,
+        * plus the '-' separator
+        */
+       if (dentry->d_name.len < GUID_LEN + 2)
                return -EINVAL;
 
+       inode = efivarfs_get_inode(dir->i_sb, dir, mode, 0);
        if (!inode)
                return -ENOSPC;
 
        var = kzalloc(sizeof(struct efivar_entry), GFP_KERNEL);
+       if (!var) {
+               err = -ENOMEM;
+               goto out;
+       }
 
-       if (!var)
-               return -ENOMEM;
-
-       namelen = dentry->d_name.len - GUID_LEN;
+       /* length of the variable name itself: remove GUID and separator */
+       namelen = dentry->d_name.len - GUID_LEN - 1;
 
        efivarfs_hex_to_guid(dentry->d_name.name + namelen + 1,
                        &var->var.VendorGuid);
@@ -908,8 +936,10 @@ static int efivarfs_create(struct inode *dir, struct dentry *dentry,
        d_instantiate(dentry, inode);
        dget(dentry);
 out:
-       if (err)
+       if (err) {
                kfree(var);
+               iput(inode);
+       }
        return err;
 }
 
@@ -944,7 +974,7 @@ int efivarfs_fill_super(struct super_block *sb, void *data, int silent)
        struct dentry *root;
        struct efivar_entry *entry, *n;
        struct efivars *efivars = &__efivars;
-       int err;
+       char *name;
 
        efivarfs_sb = sb;
 
@@ -956,30 +986,28 @@ int efivarfs_fill_super(struct super_block *sb, void *data, int silent)
        sb->s_time_gran         = 1;
 
        inode = efivarfs_get_inode(sb, NULL, S_IFDIR | 0755, 0);
-       if (!inode) {
-               err = -ENOMEM;
-               goto fail;
-       }
+       if (!inode)
+               return -ENOMEM;
        inode->i_op = &efivarfs_dir_inode_operations;
 
        root = d_make_root(inode);
        sb->s_root = root;
-       if (!root) {
-               err = -ENOMEM;
-               goto fail;
-       }
+       if (!root)
+               return -ENOMEM;
 
        list_for_each_entry_safe(entry, n, &efivars->list, list) {
-               struct inode *inode;
                struct dentry *dentry, *root = efivarfs_sb->s_root;
-               char *name;
                unsigned long size = 0;
                int len, i;
 
+               inode = NULL;
+
                len = utf16_strlen(entry->var.VariableName);
 
-               /* GUID plus trailing NULL */
-               name = kmalloc(len + 38, GFP_ATOMIC);
+               /* name, plus '-', plus GUID, plus NUL*/
+               name = kmalloc(len + 1 + GUID_LEN + 1, GFP_ATOMIC);
+               if (!name)
+                       goto fail;
 
                for (i = 0; i < len; i++)
                        name[i] = entry->var.VariableName[i] & 0xFF;
@@ -988,17 +1016,27 @@ int efivarfs_fill_super(struct super_block *sb, void *data, int silent)
 
                efi_guid_unparse(&entry->var.VendorGuid, name + len + 1);
 
-               name[len+GUID_LEN] = '\0';
+               name[len+GUID_LEN+1] = '\0';
 
                inode = efivarfs_get_inode(efivarfs_sb, root->d_inode,
                                          S_IFREG | 0644, 0);
+               if (!inode)
+                       goto fail_name;
+
                dentry = d_alloc_name(root, name);
+               if (!dentry)
+                       goto fail_inode;
 
+               /* copied by the above to local storage in the dentry. */
+               kfree(name);
+
+               spin_lock(&efivars->lock);
                efivars->ops->get_variable(entry->var.VariableName,
                                           &entry->var.VendorGuid,
                                           &entry->var.Attributes,
                                           &size,
                                           NULL);
+               spin_unlock(&efivars->lock);
 
                mutex_lock(&inode->i_mutex);
                inode->i_private = entry;
@@ -1008,9 +1046,13 @@ int efivarfs_fill_super(struct super_block *sb, void *data, int silent)
        }
 
        return 0;
-fail:
+
+fail_inode:
        iput(inode);
-       return err;
+fail_name:
+       kfree(name);
+fail:
+       return -ENOMEM;
 }
 
 static struct dentry *efivarfs_mount(struct file_system_type *fs_type,
@@ -1403,11 +1445,18 @@ efivar_create_sysfs_entry(struct efivars *efivars,
                          efi_char16_t *variable_name,
                          efi_guid_t *vendor_guid)
 {
-       int i, short_name_size = variable_name_size / sizeof(efi_char16_t) + 38;
+       int i, short_name_size;
        char *short_name;
        struct efivar_entry *new_efivar;
 
-       short_name = kzalloc(short_name_size + 1, GFP_KERNEL);
+       /*
+        * Length of the variable bytes in ASCII, plus the '-' separator,
+        * plus the GUID, plus trailing NUL
+        */
+       short_name_size = variable_name_size / sizeof(efi_char16_t)
+                               + 1 + GUID_LEN + 1;
+
+       short_name = kzalloc(short_name_size, GFP_KERNEL);
        new_efivar = kzalloc(sizeof(struct efivar_entry), GFP_KERNEL);
 
        if (!short_name || !new_efivar)  {