diff options
Diffstat (limited to 'drivers/firmware/efivars.c')
-rw-r--r-- | drivers/firmware/efivars.c | 443 |
1 files changed, 361 insertions, 82 deletions
diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c index f5596db0cf58..fe62aa392239 100644 --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -79,6 +79,7 @@ #include <linux/device.h> #include <linux/slab.h> #include <linux/pstore.h> +#include <linux/ctype.h> #include <linux/fs.h> #include <linux/ramfs.h> @@ -158,6 +159,13 @@ efivar_create_sysfs_entry(struct efivars *efivars, efi_char16_t *variable_name, efi_guid_t *vendor_guid); +/* + * Prototype for workqueue functions updating sysfs entry + */ + +static void efivar_update_sysfs_entries(struct work_struct *); +static DECLARE_WORK(efivar_work, efivar_update_sysfs_entries); + /* Return the number of unicode characters in data */ static unsigned long utf16_strnlen(efi_char16_t *s, size_t maxlength) @@ -405,10 +413,11 @@ static efi_status_t get_var_data(struct efivars *efivars, struct efi_variable *var) { efi_status_t status; + unsigned long flags; - spin_lock(&efivars->lock); + spin_lock_irqsave(&efivars->lock, flags); status = get_var_data_locked(efivars, var); - spin_unlock(&efivars->lock); + spin_unlock_irqrestore(&efivars->lock, flags); if (status != EFI_SUCCESS) { printk(KERN_WARNING "efivars: get_variable() failed 0x%lx!\n", @@ -417,6 +426,44 @@ get_var_data(struct efivars *efivars, struct efi_variable *var) 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 efivar_guid_read(struct efivar_entry *entry, char *buf) { @@ -537,14 +584,19 @@ efivar_store_raw(struct efivar_entry *entry, const char *buf, size_t count) return -EINVAL; } - spin_lock(&efivars->lock); - status = efivars->ops->set_variable(new_var->VariableName, - &new_var->VendorGuid, - new_var->Attributes, - new_var->DataSize, - new_var->Data); + spin_lock_irq(&efivars->lock); + + status = check_var_size_locked(efivars, new_var->Attributes, + new_var->DataSize + utf16_strsize(new_var->VariableName, 1024)); + + 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(&efivars->lock); + spin_unlock_irq(&efivars->lock); if (status != EFI_SUCCESS) { printk(KERN_WARNING "efivars: set_variable() failed: status=%lx\n", @@ -693,8 +745,7 @@ static ssize_t efivarfs_file_write(struct file *file, u32 attributes; struct inode *inode = file->f_mapping->host; unsigned long datasize = count - sizeof(attributes); - unsigned long newdatasize; - u64 storage_size, remaining_size, max_size; + unsigned long newdatasize, varsize; ssize_t bytes = 0; if (count < sizeof(attributes)) @@ -713,28 +764,18 @@ static ssize_t efivarfs_file_write(struct file *file, * 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); - } - spin_unlock(&efivars->lock); + varsize = datasize + utf16_strsize(var->var.VariableName, 1024); + status = check_var_size(efivars, attributes, varsize); if (status != EFI_SUCCESS) { if (status != EFI_UNSUPPORTED) 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); if (!data) return -ENOMEM; @@ -754,7 +795,20 @@ static ssize_t efivarfs_file_write(struct file *file, * set_variable call, and removal of the variable from the efivars * list (in the case of an authenticated delete). */ - spin_lock(&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, &var->var.VendorGuid, @@ -762,7 +816,7 @@ static ssize_t efivarfs_file_write(struct file *file, data); if (status != EFI_SUCCESS) { - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); kfree(data); return efi_status_to_err(status); @@ -783,21 +837,21 @@ static ssize_t efivarfs_file_write(struct file *file, NULL); if (status == EFI_BUFFER_TOO_SMALL) { - spin_unlock(&efivars->lock); + spin_unlock_irq(&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); + spin_unlock_irq(&efivars->lock); efivar_unregister(var); drop_nlink(inode); d_delete(file->f_dentry); dput(file->f_dentry); } else { - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); pr_warn("efivarfs: inconsistent EFI variable implementation? " "status = %lx\n", status); } @@ -819,11 +873,11 @@ static ssize_t efivarfs_file_read(struct file *file, char __user *userbuf, void *data; ssize_t size = 0; - spin_lock(&efivars->lock); + spin_lock_irq(&efivars->lock); status = efivars->ops->get_variable(var->var.VariableName, &var->var.VendorGuid, &attributes, &datasize, NULL); - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); if (status != EFI_BUFFER_TOO_SMALL) return efi_status_to_err(status); @@ -833,12 +887,12 @@ static ssize_t efivarfs_file_read(struct file *file, char __user *userbuf, if (!data) return -ENOMEM; - spin_lock(&efivars->lock); + spin_lock_irq(&efivars->lock); status = efivars->ops->get_variable(var->var.VariableName, &var->var.VendorGuid, &attributes, &datasize, (data + sizeof(attributes))); - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); if (status != EFI_SUCCESS) { size = efi_status_to_err(status); @@ -900,6 +954,48 @@ static struct inode *efivarfs_get_inode(struct super_block *sb, return inode; } +/* + * Return true if 'str' is a valid efivarfs filename of the form, + * + * VariableName-12345678-1234-1234-1234-1234567891bc + */ +static bool efivarfs_valid_name(const char *str, int len) +{ + static const char dashes[GUID_LEN] = { + [8] = 1, [13] = 1, [18] = 1, [23] = 1 + }; + const char *s = str + len - GUID_LEN; + int i; + + /* + * We need a GUID, plus at least one letter for the variable name, + * plus the '-' separator + */ + if (len < GUID_LEN + 2) + return false; + + /* GUID must be preceded by a '-' */ + if (*(s - 1) != '-') + return false; + + /* + * Validate that 's' is of the correct format, e.g. + * + * 12345678-1234-1234-1234-123456789abc + */ + for (i = 0; i < GUID_LEN; i++) { + if (dashes[i]) { + if (*s++ != '-') + return false; + } else { + if (!isxdigit(*s++)) + return false; + } + } + + return true; +} + 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]); @@ -928,11 +1024,7 @@ static int efivarfs_create(struct inode *dir, struct dentry *dentry, 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) + if (!efivarfs_valid_name(dentry->d_name.name, dentry->d_name.len)) return -EINVAL; inode = efivarfs_get_inode(dir->i_sb, dir, mode, 0); @@ -966,9 +1058,9 @@ static int efivarfs_create(struct inode *dir, struct dentry *dentry, goto out; kobject_uevent(&var->kobj, KOBJ_ADD); - spin_lock(&efivars->lock); + spin_lock_irq(&efivars->lock); list_add(&var->list, &efivars->list); - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); d_instantiate(dentry, inode); dget(dentry); out: @@ -985,7 +1077,7 @@ static int efivarfs_unlink(struct inode *dir, struct dentry *dentry) struct efivars *efivars = var->efivars; efi_status_t status; - spin_lock(&efivars->lock); + spin_lock_irq(&efivars->lock); status = efivars->ops->set_variable(var->var.VariableName, &var->var.VendorGuid, @@ -993,17 +1085,102 @@ static int efivarfs_unlink(struct inode *dir, struct dentry *dentry) if (status == EFI_SUCCESS || status == EFI_NOT_FOUND) { list_del(&var->list); - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); efivar_unregister(var); drop_nlink(dentry->d_inode); dput(dentry); return 0; } - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); return -EINVAL; }; +/* + * Compare two efivarfs file names. + * + * An efivarfs filename is composed of two parts, + * + * 1. A case-sensitive variable name + * 2. A case-insensitive GUID + * + * So we need to perform a case-sensitive match on part 1 and a + * case-insensitive match on part 2. + */ +static int efivarfs_d_compare(const struct dentry *parent, const struct inode *pinode, + const struct dentry *dentry, const struct inode *inode, + unsigned int len, const char *str, + const struct qstr *name) +{ + int guid = len - GUID_LEN; + + if (name->len != len) + return 1; + + /* Case-sensitive compare for the variable name */ + if (memcmp(str, name->name, guid)) + return 1; + + /* Case-insensitive compare for the GUID */ + return strncasecmp(name->name + guid, str + guid, GUID_LEN); +} + +static int efivarfs_d_hash(const struct dentry *dentry, + const struct inode *inode, struct qstr *qstr) +{ + unsigned long hash = init_name_hash(); + const unsigned char *s = qstr->name; + unsigned int len = qstr->len; + + if (!efivarfs_valid_name(s, len)) + return -EINVAL; + + while (len-- > GUID_LEN) + hash = partial_name_hash(*s++, hash); + + /* GUID is case-insensitive. */ + while (len--) + hash = partial_name_hash(tolower(*s++), hash); + + qstr->hash = end_name_hash(hash); + return 0; +} + +/* + * Retaining negative dentries for an in-memory filesystem just wastes + * memory and lookup time: arrange for them to be deleted immediately. + */ +static int efivarfs_delete_dentry(const struct dentry *dentry) +{ + return 1; +} + +static struct dentry_operations efivarfs_d_ops = { + .d_compare = efivarfs_d_compare, + .d_hash = efivarfs_d_hash, + .d_delete = efivarfs_delete_dentry, +}; + +static struct dentry *efivarfs_alloc_dentry(struct dentry *parent, char *name) +{ + struct dentry *d; + struct qstr q; + int err; + + q.name = name; + q.len = strlen(name); + + err = efivarfs_d_hash(NULL, NULL, &q); + if (err) + return ERR_PTR(err); + + d = d_alloc(parent, &q); + if (d) + return d; + + return ERR_PTR(-ENOMEM); +} + static int efivarfs_fill_super(struct super_block *sb, void *data, int silent) { struct inode *inode = NULL; @@ -1011,6 +1188,7 @@ static int efivarfs_fill_super(struct super_block *sb, void *data, int silent) struct efivar_entry *entry, *n; struct efivars *efivars = &__efivars; char *name; + int err = -ENOMEM; efivarfs_sb = sb; @@ -1019,6 +1197,7 @@ static int efivarfs_fill_super(struct super_block *sb, void *data, int silent) sb->s_blocksize_bits = PAGE_CACHE_SHIFT; sb->s_magic = EFIVARFS_MAGIC; sb->s_op = &efivarfs_ops; + sb->s_d_op = &efivarfs_d_ops; sb->s_time_gran = 1; inode = efivarfs_get_inode(sb, NULL, S_IFDIR | 0755, 0); @@ -1059,24 +1238,26 @@ static int efivarfs_fill_super(struct super_block *sb, void *data, int silent) if (!inode) goto fail_name; - dentry = d_alloc_name(root, name); - if (!dentry) + dentry = efivarfs_alloc_dentry(root, name); + if (IS_ERR(dentry)) { + err = PTR_ERR(dentry); goto fail_inode; + } /* copied by the above to local storage in the dentry. */ kfree(name); - spin_lock(&efivars->lock); + spin_lock_irq(&efivars->lock); efivars->ops->get_variable(entry->var.VariableName, &entry->var.VendorGuid, &entry->var.Attributes, &size, NULL); - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); mutex_lock(&inode->i_mutex); inode->i_private = entry; - i_size_write(inode, size+4); + i_size_write(inode, size + sizeof(entry->var.Attributes)); mutex_unlock(&inode->i_mutex); d_add(dentry, inode); } @@ -1088,7 +1269,7 @@ fail_inode: fail_name: kfree(name); fail: - return -ENOMEM; + return err; } static struct dentry *efivarfs_mount(struct file_system_type *fs_type, @@ -1108,9 +1289,22 @@ static struct file_system_type efivarfs_type = { .mount = efivarfs_mount, .kill_sb = efivarfs_kill_sb, }; +MODULE_ALIAS_FS("efivarfs"); + +/* + * Handle negative dentry. + */ +static struct dentry *efivarfs_lookup(struct inode *dir, struct dentry *dentry, + unsigned int flags) +{ + if (dentry->d_name.len > NAME_MAX) + return ERR_PTR(-ENAMETOOLONG); + d_add(dentry, NULL); + return NULL; +} static const struct inode_operations efivarfs_dir_inode_operations = { - .lookup = simple_lookup, + .lookup = efivarfs_lookup, .unlink = efivarfs_unlink, .create = efivarfs_create, }; @@ -1123,7 +1317,7 @@ static int efi_pstore_open(struct pstore_info *psi) { struct efivars *efivars = psi->data; - spin_lock(&efivars->lock); + spin_lock_irq(&efivars->lock); efivars->walk_entry = list_first_entry(&efivars->list, struct efivar_entry, list); return 0; @@ -1133,7 +1327,7 @@ static int efi_pstore_close(struct pstore_info *psi) { struct efivars *efivars = psi->data; - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); return 0; } @@ -1207,22 +1401,31 @@ static int efi_pstore_write(enum pstore_type_id type, efi_guid_t vendor = LINUX_EFI_CRASH_GUID; struct efivars *efivars = psi->data; int i, ret = 0; - u64 storage_space, remaining_space, max_variable_size; efi_status_t status = EFI_NOT_FOUND; - - spin_lock(&efivars->lock); + unsigned long flags; + + if (pstore_cannot_block_path(reason)) { + /* + * If the lock is taken by another cpu in non-blocking path, + * this driver returns without entering firmware to avoid + * hanging up. + */ + if (!spin_trylock_irqsave(&efivars->lock, flags)) + return -EBUSY; + } else + spin_lock_irqsave(&efivars->lock, flags); /* * Check if there is a space enough to log. * size: a size of logging data * DUMP_NAME_LEN * 2: a maximum size of variable name */ - status = efivars->ops->query_variable_info(PSTORE_EFI_ATTRIBUTES, - &storage_space, - &remaining_space, - &max_variable_size); - if (status || remaining_space < size + DUMP_NAME_LEN * 2) { - spin_unlock(&efivars->lock); + + status = check_var_size_locked(efivars, PSTORE_EFI_ATTRIBUTES, + size + DUMP_NAME_LEN * 2); + + if (status) { + spin_unlock_irqrestore(&efivars->lock, flags); *id = part; return -ENOSPC; } @@ -1236,13 +1439,10 @@ static int efi_pstore_write(enum pstore_type_id type, efivars->ops->set_variable(efi_name, &vendor, PSTORE_EFI_ATTRIBUTES, size, psi->buf); - spin_unlock(&efivars->lock); + spin_unlock_irqrestore(&efivars->lock, flags); - if (size) - ret = efivar_create_sysfs_entry(efivars, - utf16_strsize(efi_name, - DUMP_NAME_LEN * 2), - efi_name, &vendor); + if (reason == KMSG_DUMP_OOPS) + schedule_work(&efivar_work); *id = part; return ret; @@ -1263,7 +1463,7 @@ static int efi_pstore_erase(enum pstore_type_id type, u64 id, int count, sprintf(name, "dump-type%u-%u-%d-%lu", type, (unsigned int)id, count, time.tv_sec); - spin_lock(&efivars->lock); + spin_lock_irq(&efivars->lock); for (i = 0; i < DUMP_NAME_LEN; i++) efi_name[i] = name[i]; @@ -1307,7 +1507,7 @@ static int efi_pstore_erase(enum pstore_type_id type, u64 id, int count, if (found) list_del(&found->list); - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); if (found) efivar_unregister(found); @@ -1377,7 +1577,7 @@ static ssize_t efivar_create(struct file *filp, struct kobject *kobj, return -EINVAL; } - spin_lock(&efivars->lock); + spin_lock_irq(&efivars->lock); /* * Does this variable already exist? @@ -1395,10 +1595,18 @@ static ssize_t efivar_create(struct file *filp, struct kobject *kobj, } } if (found) { - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); 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 */ status = efivars->ops->set_variable(new_var->VariableName, &new_var->VendorGuid, @@ -1409,10 +1617,10 @@ static ssize_t efivar_create(struct file *filp, struct kobject *kobj, if (status != EFI_SUCCESS) { printk(KERN_WARNING "efivars: set_variable() failed: status=%lx\n", status); - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); return -EIO; } - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); /* Create the entry in sysfs. Locking is not required here */ status = efivar_create_sysfs_entry(efivars, @@ -1440,7 +1648,7 @@ static ssize_t efivar_delete(struct file *filp, struct kobject *kobj, if (!capable(CAP_SYS_ADMIN)) return -EACCES; - spin_lock(&efivars->lock); + spin_lock_irq(&efivars->lock); /* * Does this variable already exist? @@ -1458,7 +1666,7 @@ static ssize_t efivar_delete(struct file *filp, struct kobject *kobj, } } if (!found) { - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); return -EINVAL; } /* force the Attributes/DataSize to 0 to ensure deletion */ @@ -1474,18 +1682,87 @@ static ssize_t efivar_delete(struct file *filp, struct kobject *kobj, if (status != EFI_SUCCESS) { printk(KERN_WARNING "efivars: set_variable() failed: status=%lx\n", status); - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); return -EIO; } list_del(&search_efivar->list); /* We need to release this lock before unregistering. */ - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); efivar_unregister(search_efivar); /* It's dead Jim.... */ return count; } +static bool variable_is_present(efi_char16_t *variable_name, efi_guid_t *vendor) +{ + struct efivar_entry *entry, *n; + struct efivars *efivars = &__efivars; + unsigned long strsize1, strsize2; + bool found = false; + + strsize1 = utf16_strsize(variable_name, 1024); + list_for_each_entry_safe(entry, n, &efivars->list, list) { + strsize2 = utf16_strsize(entry->var.VariableName, 1024); + if (strsize1 == strsize2 && + !memcmp(variable_name, &(entry->var.VariableName), + strsize2) && + !efi_guidcmp(entry->var.VendorGuid, + *vendor)) { + found = true; + break; + } + } + return found; +} + +static void efivar_update_sysfs_entries(struct work_struct *work) +{ + struct efivars *efivars = &__efivars; + efi_guid_t vendor; + efi_char16_t *variable_name; + unsigned long variable_name_size = 1024; + efi_status_t status = EFI_NOT_FOUND; + bool found; + + /* Add new sysfs entries */ + while (1) { + variable_name = kzalloc(variable_name_size, GFP_KERNEL); + if (!variable_name) { + pr_err("efivars: Memory allocation failed.\n"); + return; + } + + spin_lock_irq(&efivars->lock); + found = false; + while (1) { + variable_name_size = 1024; + status = efivars->ops->get_next_variable( + &variable_name_size, + variable_name, + &vendor); + if (status != EFI_SUCCESS) { + break; + } else { + if (!variable_is_present(variable_name, + &vendor)) { + found = true; + break; + } + } + } + spin_unlock_irq(&efivars->lock); + + if (!found) { + kfree(variable_name); + break; + } else + efivar_create_sysfs_entry(efivars, + variable_name_size, + variable_name, &vendor); + } +} + /* * Let's not leave out systab information that snuck into * the efivars driver @@ -1594,9 +1871,9 @@ efivar_create_sysfs_entry(struct efivars *efivars, kfree(short_name); short_name = NULL; - spin_lock(&efivars->lock); + spin_lock_irq(&efivars->lock); list_add(&new_efivar->list, &efivars->list); - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); return 0; } @@ -1665,9 +1942,9 @@ void unregister_efivars(struct efivars *efivars) struct efivar_entry *entry, *n; list_for_each_entry_safe(entry, n, &efivars->list, list) { - spin_lock(&efivars->lock); + spin_lock_irq(&efivars->lock); list_del(&entry->list); - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); efivar_unregister(entry); } if (efivars->new_var) @@ -1823,6 +2100,8 @@ err_put: static void __exit efivars_exit(void) { + cancel_work_sync(&efivar_work); + if (efi_enabled(EFI_RUNTIME_SERVICES)) { unregister_efivars(&__efivars); kobject_put(efi_kobj); |