]> 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 d10c9873dd9a8684d7137e49245afc9b40d13d60..a93e401c20a78b3d48ac459c6a32e91685f978b9 100644 (file)
 #include <linux/slab.h>
 #include <linux/pstore.h>
 
+#include <linux/fs.h>
+#include <linux/ramfs.h>
+#include <linux/pagemap.h>
+
 #include <asm/uaccess.h>
 
 #define EFIVARS_VERSION "0.08"
@@ -92,6 +96,12 @@ MODULE_VERSION(EFIVARS_VERSION);
 
 #define DUMP_NAME_LEN 52
 
+/*
+ * 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
  * Therefore, it's reasonable to save that much
@@ -108,7 +118,6 @@ struct efi_variable {
        __u32         Attributes;
 } __attribute__((packed));
 
-
 struct efivar_entry {
        struct efivars *efivars;
        struct efi_variable var;
@@ -122,6 +131,9 @@ struct efivar_attribute {
        ssize_t (*store)(struct efivar_entry *entry, const char *buf, size_t count);
 };
 
+static struct efivars __efivars;
+static struct efivar_operations ops;
+
 #define PSTORE_EFI_ATTRIBUTES \
        (EFI_VARIABLE_NON_VOLATILE | \
         EFI_VARIABLE_BOOTSERVICE_ACCESS | \
@@ -629,14 +641,446 @@ static struct kobj_type efivar_ktype = {
        .default_attrs = def_attrs,
 };
 
-static struct pstore_info efi_pstore_info;
-
 static inline void
 efivar_unregister(struct efivar_entry *var)
 {
        kobject_put(&var->kobj);
 }
 
+static int efivarfs_file_open(struct inode *inode, struct file *file)
+{
+       file->private_data = inode->i_private;
+       return 0;
+}
+
+static ssize_t efivarfs_file_write(struct file *file,
+               const char __user *userbuf, size_t count, loff_t *ppos)
+{
+       struct efivar_entry *var = file->private_data;
+       struct efivars *efivars;
+       efi_status_t status;
+       void *data;
+       u32 attributes;
+       struct inode *inode = file->f_mapping->host;
+       int datasize = count - sizeof(attributes);
+       unsigned long newdatasize;
+
+       if (count < sizeof(attributes))
+               return -EINVAL;
+
+       data = kmalloc(datasize, GFP_KERNEL);
+
+       if (!data)
+               return -ENOMEM;
+
+       efivars = var->efivars;
+
+       if (copy_from_user(&attributes, userbuf, sizeof(attributes))) {
+               count = -EFAULT;
+               goto out;
+       }
+
+       if (attributes & ~(EFI_VARIABLE_MASK)) {
+               count = -EINVAL;
+               goto out;
+       }
+
+       if (copy_from_user(data, userbuf + sizeof(attributes), datasize)) {
+               count = -EFAULT;
+               goto out;
+       }
+
+       if (validate_var(&var->var, data, datasize) == false) {
+               count = -EINVAL;
+               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);
+
+       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;
+       }
+
+       /*
+        * Writing to the variable may have caused a change in size (which
+        * could either be an append or an overwrite), or the variable to be
+        * deleted. Perform a GetVariable() so we can tell what actually
+        * happened.
+        */
+       newdatasize = 0;
+       status = efivars->ops->get_variable(var->var.VariableName,
+                                           &var->var.VendorGuid,
+                                           NULL, &newdatasize,
+                                           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) {
+               list_del(&var->list);
+               spin_unlock(&efivars->lock);
+               efivar_unregister(var);
+               drop_nlink(inode);
+               dput(file->f_dentry);
+
+       } else {
+               spin_unlock(&efivars->lock);
+               pr_warn("efivarfs: inconsistent EFI variable implementation? "
+                               "status = %lx\n", status);
+       }
+
+out:
+       kfree(data);
+
+       return count;
+}
+
+static ssize_t efivarfs_file_read(struct file *file, char __user *userbuf,
+               size_t count, loff_t *ppos)
+{
+       struct efivar_entry *var = file->private_data;
+       struct efivars *efivars = var->efivars;
+       efi_status_t status;
+       unsigned long datasize = 0;
+       u32 attributes;
+       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;
+
+       data = kmalloc(datasize + 4, GFP_KERNEL);
+
+       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;
+
+       memcpy(data, &attributes, 4);
+       size = simple_read_from_buffer(userbuf, count, ppos,
+                                       data, datasize + 4);
+out_free:
+       kfree(data);
+
+       return size;
+}
+
+static void efivarfs_evict_inode(struct inode *inode)
+{
+       clear_inode(inode);
+}
+
+static const struct super_operations efivarfs_ops = {
+       .statfs = simple_statfs,
+       .drop_inode = generic_delete_inode,
+       .evict_inode = efivarfs_evict_inode,
+       .show_options = generic_show_options,
+};
+
+static struct super_block *efivarfs_sb;
+
+static const struct inode_operations efivarfs_dir_inode_operations;
+
+static const struct file_operations efivarfs_file_operations = {
+       .open   = efivarfs_file_open,
+       .read   = efivarfs_file_read,
+       .write  = efivarfs_file_write,
+       .llseek = no_llseek,
+};
+
+static struct inode *efivarfs_get_inode(struct super_block *sb,
+                               const struct inode *dir, int mode, dev_t dev)
+{
+       struct inode *inode = new_inode(sb);
+
+       if (inode) {
+               inode->i_ino = get_next_ino();
+               inode->i_uid = inode->i_gid = 0;
+               inode->i_mode = mode;
+               inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
+               switch (mode & S_IFMT) {
+               case S_IFREG:
+                       inode->i_fop = &efivarfs_file_operations;
+                       break;
+               case S_IFDIR:
+                       inode->i_op = &efivarfs_dir_inode_operations;
+                       inode->i_fop = &simple_dir_operations;
+                       inc_nlink(inode);
+                       break;
+               }
+       }
+       return inode;
+}
+
+static void efivarfs_hex_to_guid(const char *str, efi_guid_t *guid)
+{
+       guid->b[0] = hex_to_bin(str[6]) << 4 | hex_to_bin(str[7]);
+       guid->b[1] = hex_to_bin(str[4]) << 4 | hex_to_bin(str[5]);
+       guid->b[2] = hex_to_bin(str[2]) << 4 | hex_to_bin(str[3]);
+       guid->b[3] = hex_to_bin(str[0]) << 4 | hex_to_bin(str[1]);
+       guid->b[4] = hex_to_bin(str[11]) << 4 | hex_to_bin(str[12]);
+       guid->b[5] = hex_to_bin(str[9]) << 4 | hex_to_bin(str[10]);
+       guid->b[6] = hex_to_bin(str[16]) << 4 | hex_to_bin(str[17]);
+       guid->b[7] = hex_to_bin(str[14]) << 4 | hex_to_bin(str[15]);
+       guid->b[8] = hex_to_bin(str[19]) << 4 | hex_to_bin(str[20]);
+       guid->b[9] = hex_to_bin(str[21]) << 4 | hex_to_bin(str[22]);
+       guid->b[10] = hex_to_bin(str[24]) << 4 | hex_to_bin(str[25]);
+       guid->b[11] = hex_to_bin(str[26]) << 4 | hex_to_bin(str[27]);
+       guid->b[12] = hex_to_bin(str[28]) << 4 | hex_to_bin(str[29]);
+       guid->b[13] = hex_to_bin(str[30]) << 4 | hex_to_bin(str[31]);
+       guid->b[14] = hex_to_bin(str[32]) << 4 | hex_to_bin(str[33]);
+       guid->b[15] = hex_to_bin(str[34]) << 4 | hex_to_bin(str[35]);
+}
+
+static int efivarfs_create(struct inode *dir, struct dentry *dentry,
+                         umode_t mode, bool excl)
+{
+       struct inode *inode;
+       struct efivars *efivars = &__efivars;
+       struct efivar_entry *var;
+       int namelen, i = 0, err = 0;
+
+       /*
+        * 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;
+       }
+
+       /* 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);
+
+       for (i = 0; i < namelen; i++)
+               var->var.VariableName[i] = dentry->d_name.name[i];
+
+       var->var.VariableName[i] = '\0';
+
+       inode->i_private = var;
+       var->efivars = efivars;
+       var->kobj.kset = efivars->kset;
+
+       err = kobject_init_and_add(&var->kobj, &efivar_ktype, NULL, "%s",
+                            dentry->d_name.name);
+       if (err)
+               goto out;
+
+       kobject_uevent(&var->kobj, KOBJ_ADD);
+       spin_lock(&efivars->lock);
+       list_add(&var->list, &efivars->list);
+       spin_unlock(&efivars->lock);
+       d_instantiate(dentry, inode);
+       dget(dentry);
+out:
+       if (err) {
+               kfree(var);
+               iput(inode);
+       }
+       return err;
+}
+
+static int efivarfs_unlink(struct inode *dir, struct dentry *dentry)
+{
+       struct efivar_entry *var = dentry->d_inode->i_private;
+       struct efivars *efivars = var->efivars;
+       efi_status_t status;
+
+       spin_lock(&efivars->lock);
+
+       status = efivars->ops->set_variable(var->var.VariableName,
+                                           &var->var.VendorGuid,
+                                           0, 0, NULL);
+
+       if (status == EFI_SUCCESS || status == EFI_NOT_FOUND) {
+               list_del(&var->list);
+               spin_unlock(&efivars->lock);
+               efivar_unregister(var);
+               drop_nlink(dir);
+               dput(dentry);
+               return 0;
+       }
+
+       spin_unlock(&efivars->lock);
+       return -EINVAL;
+};
+
+int efivarfs_fill_super(struct super_block *sb, void *data, int silent)
+{
+       struct inode *inode = NULL;
+       struct dentry *root;
+       struct efivar_entry *entry, *n;
+       struct efivars *efivars = &__efivars;
+       char *name;
+
+       efivarfs_sb = sb;
+
+       sb->s_maxbytes          = MAX_LFS_FILESIZE;
+       sb->s_blocksize         = PAGE_CACHE_SIZE;
+       sb->s_blocksize_bits    = PAGE_CACHE_SHIFT;
+       sb->s_magic             = PSTOREFS_MAGIC;
+       sb->s_op                = &efivarfs_ops;
+       sb->s_time_gran         = 1;
+
+       inode = efivarfs_get_inode(sb, NULL, S_IFDIR | 0755, 0);
+       if (!inode)
+               return -ENOMEM;
+       inode->i_op = &efivarfs_dir_inode_operations;
+
+       root = d_make_root(inode);
+       sb->s_root = root;
+       if (!root)
+               return -ENOMEM;
+
+       list_for_each_entry_safe(entry, n, &efivars->list, list) {
+               struct dentry *dentry, *root = efivarfs_sb->s_root;
+               unsigned long size = 0;
+               int len, i;
+
+               inode = NULL;
+
+               len = utf16_strlen(entry->var.VariableName);
+
+               /* 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;
+
+               name[len] = '-';
+
+               efi_guid_unparse(&entry->var.VendorGuid, name + len + 1);
+
+               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;
+               i_size_write(inode, size+4);
+               mutex_unlock(&inode->i_mutex);
+               d_add(dentry, inode);
+       }
+
+       return 0;
+
+fail_inode:
+       iput(inode);
+fail_name:
+       kfree(name);
+fail:
+       return -ENOMEM;
+}
+
+static struct dentry *efivarfs_mount(struct file_system_type *fs_type,
+                                   int flags, const char *dev_name, void *data)
+{
+       return mount_single(fs_type, flags, data, efivarfs_fill_super);
+}
+
+static void efivarfs_kill_sb(struct super_block *sb)
+{
+       kill_litter_super(sb);
+       efivarfs_sb = NULL;
+}
+
+static struct file_system_type efivarfs_type = {
+       .name    = "efivarfs",
+       .mount   = efivarfs_mount,
+       .kill_sb = efivarfs_kill_sb,
+};
+
+static const struct inode_operations efivarfs_dir_inode_operations = {
+       .lookup = simple_lookup,
+       .unlink = efivarfs_unlink,
+       .create = efivarfs_create,
+};
+
+static struct pstore_info efi_pstore_info;
+
 #ifdef CONFIG_PSTORE
 
 static int efi_pstore_open(struct pstore_info *psi)
@@ -1001,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)  {
@@ -1125,6 +1576,7 @@ void unregister_efivars(struct efivars *efivars)
                sysfs_remove_bin_file(&efivars->kset->kobj, efivars->del_var);
        kfree(efivars->new_var);
        kfree(efivars->del_var);
+       kobject_put(efivars->kobject);
        kset_unregister(efivars->kset);
 }
 EXPORT_SYMBOL_GPL(unregister_efivars);
@@ -1156,6 +1608,14 @@ int register_efivars(struct efivars *efivars,
                goto out;
        }
 
+       efivars->kobject = kobject_create_and_add("efivars", parent_kobj);
+       if (!efivars->kobject) {
+               pr_err("efivars: Subsystem registration failed.\n");
+               error = -ENOMEM;
+               kset_unregister(efivars->kset);
+               goto out;
+       }
+
        /*
         * Per EFI spec, the maximum storage allocated for both
         * the variable name and variable data is 1024 bytes.
@@ -1198,6 +1658,8 @@ int register_efivars(struct efivars *efivars,
                pstore_register(&efivars->efi_pstore_info);
        }
 
+       register_filesystem(&efivarfs_type);
+
 out:
        kfree(variable_name);
 
@@ -1205,9 +1667,6 @@ out:
 }
 EXPORT_SYMBOL_GPL(register_efivars);
 
-static struct efivars __efivars;
-static struct efivar_operations ops;
-
 /*
  * For now we register the efi subsystem with the firmware subsystem
  * and the vars subsystem with the efi subsystem.  In the future, it