[opensuse-kernel] [PATCH 0/19] Backported patches for support UEFI variable filesystem
Patch-mainline: v3.8-rc1..v3.9-rc2 Target: openSUSE 12.3 Test steps: + build; make modules_install; make install + mount -t efivarfs none /sys/firmware/efi/efivars/ or create file /lib/systemd/system/sys-firmware-efi-efivars.mount [1] + ls /sys/firmware/efi/efivars will show up all EFI variables + Try the small create[2]/delete[3] programs from Gary Lin The create program will create a EFI variable is TestVar, then we can see it show up in /sys/firmware/efi/efivars. And, delete program can remove it. Backported 33 patches: 0001-efi-Add-support-for-a-UEFI-variable-filesystem.patch 0002-efi-Handle-deletions-and-size-changes-in-efivarfs_w.patch 0003-efi-add-efivars-kobject-to-efi-sysfs-folder.patch 0004-efivarfs-Add-documentation-for-the-EFI-variable-fil.patch 0005-efivarfs-efivarfs_file_read-ensure-we-free-data-in.patch 0006-efivarfs-efivarfs_create-ensure-we-drop-our-refer.patch 0007-efivarfs-efivarfs_fill_super-fix-inode-reference.patch 0008-efivarfs-efivarfs_fill_super-ensure-we-free-our-t.patch 0009-efivarfs-efivarfs_fill_super-ensure-we-clean-up-c.patch 0010-efivarfs-Implement-exclusive-access-for-get-set-_v.patch 0011-efivarfs-Return-an-error-if-we-fail-to-read-a-variab.patch 0012-efi-Clarify-GUID-length-calculations.patch 0013-efivarfs-Replace-magic-number-with-sizeof-attributes.patch 0014-efivarfs-Add-unique-magic-number.patch 0015-efivarfs-Make-datasize-unsigned-long.patch 0016-efivarfs-Return-a-consistent-error-when-efivarfs_get.patch 0017-efivarfs-Fix-return-value-of-efivarfs_file_write.patch 0018-efivarfs-Use-query_variable_info-to-limit-kmalloc.patch 0019-efivarfs-Make-efivarfs_fill_super-static.patch 0020-efivarfs-Drop-link-count-of-the-right-inode.patch 0021-efivarfs-Never-return-ENOENT-from-firmware.patch 0022-efivarfs-Delete-dentry-from-dcache-in-efivarfs_file_.patch 0023-efi_pstore-Check-remaining-space-with-QueryVariableI.patch 0024-pstore-Avoid-deadlock-in-panic-and-emergency-restart.patch 0025-efi_pstore-Avoid-deadlock-in-non-blocking-paths.patch 0026-efivarfs-guid-part-of-filenames-are-case-insensitive.patch 0027-efivars-Disable-external-interrupt-while-holding-efi.patch 0028-efi_pstore-Introducing-workqueue-updating-sysfs.patch 0029-efivarfs-Validate-filenames-much-more-aggressively.patch 0030-efi-be-more-paranoid-about-available-space-when-crea.patch 0031-efivars-efivarfs_valid_name-should-handle-pstore-syn.patch 0032-efivarfs-return-accurate-error-code-in-efivarfs_fill.patch 0033-efivars-Sanitise-string-length-returned-by.patch [1] /lib/systemd/system/sys-firmware-efi-efivars.mount (already sent to systemd mailing list for review) [Unit] Description=EFI Variables File System Documentation=https://www.kernel.org/doc/Documentation/filesystems/efivarfs.txt DefaultDependencies=no ConditionPathExists=/sys/firmware/efi/efivars Before=sysinit.target [Mount] What=efivarfs Where=/sys/firmware/efi/efivars Type=efivarfs [2] create.c #include <stdio.h> #include <string.h> #include <stdint.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <linux/limits.h> #include "def.h" int main () { const char *variable_name = "TestVar"; char file_path[PATH_MAX]; int fd, flags; mode_t mode; uint32_t attribute; char buffer[1024 + 4]; int i; snprintf (file_path, PATH_MAX, "%s%s-%s", EFIVARS_FS, variable_name, MY_GUID); flags = O_CREAT | O_WRONLY; mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; fd = open (file_path, flags, mode); if (fd < 0) { fprintf (stderr, "Failed to open %s\n", file_path); return -1; } attribute = EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS; memcpy (buffer, &attribute, sizeof(uint32_t)); for (i = 0; i < 1024; i++) buffer[i+4] = 'a'; if (write (fd, buffer, 1024 + 4) != (1024 + 4)) { fprintf (stderr, "Failed to write\n"); } close (fd); return 0; } [3] delete.c #include <stdio.h> #include <unistd.h> #include <linux/limits.h> #include "def.h" int main () { const char *variable_name = "TestVar"; char file_path[PATH_MAX]; snprintf (file_path, PATH_MAX, "%s%s-%s", EFIVARS_FS, variable_name, MY_GUID); unlink (file_path); return 0; } -- To unsubscribe, e-mail: opensuse-kernel+unsubscribe@opensuse.org To contact the owner, e-mail: opensuse-kernel+owner@opensuse.org
From: Matthew Garrett <mjg@redhat.com> Git-commit: 5d9db883761ad1bc2245fd3018715549b974203d Patch-mainline: v3.8 References: bnc#808680 Target: openSUSE 12.3 The existing EFI variables code only supports variables of up to 1024 bytes. This limitation existed in version 0.99 of the EFI specification, but was removed before any full releases. Since variables can now be larger than a single page, sysfs isn't the best interface for this. So, instead, let's add a filesystem. Variables can be read, written and created, with the first 4 bytes of each variable representing its UEFI attributes. The create() method doesn't actually commit to flash since zero-length variables can't exist per-spec. Updates from Jeremy Kerr <jeremy.kerr@canonical.com>. Signed-off-by: Matthew Garrett <mjg@redhat.com> Signed-off-by: Jeremy Kerr <jeremy.kerr@canonical.com> Signed-off-by: Matt Fleming <matt.fleming@intel.com> Acked-by: Lee, Chun-Yi <jlee@suse.com> --- drivers/firmware/efivars.c | 384 ++++++++++++++++++++++++++++++++++++++++++++- include/linux/efi.h | 5 2 files changed, 383 insertions(+), 6 deletions(-) --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -80,6 +80,10 @@ #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" @@ -91,6 +95,7 @@ MODULE_LICENSE("GPL"); MODULE_VERSION(EFIVARS_VERSION); #define DUMP_NAME_LEN 52 +#define GUID_LEN 37 /* * The maximum size of VariableName + Data = 1024 @@ -108,7 +113,6 @@ struct efi_variable { __u32 Attributes; } __attribute__((packed)); - struct efivar_entry { struct efivars *efivars; struct efi_variable var; @@ -122,6 +126,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 +636,380 @@ 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); + + 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; + } + + status = efivars->ops->set_variable(var->var.VariableName, + &var->var.VendorGuid, + attributes, datasize, + data); + + switch (status) { + case EFI_SUCCESS: + mutex_lock(&inode->i_mutex); + i_size_write(inode, count); + mutex_unlock(&inode->i_mutex); + break; + 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; + break; + } +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; + + status = efivars->ops->get_variable(var->var.VariableName, + &var->var.VendorGuid, + &attributes, &datasize, NULL); + + if (status != EFI_BUFFER_TOO_SMALL) + return 0; + + data = kmalloc(datasize + 4, GFP_KERNEL); + + if (!data) + return 0; + + status = efivars->ops->get_variable(var->var.VariableName, + &var->var.VendorGuid, + &attributes, &datasize, + (data + 4)); + + if (status != EFI_SUCCESS) + return 0; + + memcpy(data, &attributes, 4); + size = simple_read_from_buffer(userbuf, count, ppos, + data, datasize + 4); + 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 = default_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 = efivarfs_get_inode(dir->i_sb, dir, mode, 0); + struct efivars *efivars = &__efivars; + struct efivar_entry *var; + int namelen, i = 0, err = 0; + + if (dentry->d_name.len < 38) + return -EINVAL; + + if (!inode) + return -ENOSPC; + + var = kzalloc(sizeof(struct efivar_entry), GFP_KERNEL); + + if (!var) + return -ENOMEM; + + namelen = dentry->d_name.len - GUID_LEN; + + 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); + 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; + int err; + + 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) { + err = -ENOMEM; + goto fail; + } + inode->i_op = &efivarfs_dir_inode_operations; + + root = d_make_root(inode); + sb->s_root = root; + if (!root) { + err = -ENOMEM; + goto fail; + } + + 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; + + len = utf16_strlen(entry->var.VariableName); + + /* GUID plus trailing NULL */ + name = kmalloc(len + 38, GFP_ATOMIC); + + 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] = '\0'; + + inode = efivarfs_get_inode(efivarfs_sb, root->d_inode, + S_IFREG | 0644, 0); + dentry = d_alloc_name(root, name); + + efivars->ops->get_variable(entry->var.VariableName, + &entry->var.VendorGuid, + &entry->var.Attributes, + &size, + NULL); + + 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: + iput(inode); + return err; +} + +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) @@ -1198,6 +1571,8 @@ int register_efivars(struct efivars *efi pstore_register(&efivars->efi_pstore_info); } + register_filesystem(&efivarfs_type); + out: kfree(variable_name); @@ -1205,9 +1580,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 --- a/include/linux/efi.h +++ b/include/linux/efi.h @@ -29,7 +29,12 @@ #define EFI_UNSUPPORTED ( 3 | (1UL << (BITS_PER_LONG-1))) #define EFI_BAD_BUFFER_SIZE ( 4 | (1UL << (BITS_PER_LONG-1))) #define EFI_BUFFER_TOO_SMALL ( 5 | (1UL << (BITS_PER_LONG-1))) +#define EFI_NOT_READY ( 6 | (1UL << (BITS_PER_LONG-1))) +#define EFI_DEVICE_ERROR ( 7 | (1UL << (BITS_PER_LONG-1))) +#define EFI_WRITE_PROTECTED ( 8 | (1UL << (BITS_PER_LONG-1))) +#define EFI_OUT_OF_RESOURCES ( 9 | (1UL << (BITS_PER_LONG-1))) #define EFI_NOT_FOUND (14 | (1UL << (BITS_PER_LONG-1))) +#define EFI_SECURITY_VIOLATION (26 | (1UL << (BITS_PER_LONG-1))) typedef unsigned long efi_status_t; typedef u8 efi_bool_t; -- To unsubscribe, e-mail: opensuse-kernel+unsubscribe@opensuse.org To contact the owner, e-mail: opensuse-kernel+owner@opensuse.org
From: Jeremy Kerr <jeremy.kerr@canonical.com> Git-commit: 0c542edde3cecc99b180a440ae33dcb7f28642ce Patch-mainline: v3.8 References: bnc#808680 Target: openSUSE 12.3 A write to an efivarfs file will not always result in a variable of 'count' size after the EFI SetVariable() call. We may have appended to the existing data (ie, with the EFI_VARIABLE_APPEND_WRITE attribute), or even have deleted the variable (with an authenticated variable update, with a zero datasize). This change re-reads the updated variable from firmware, to check for size changes and deletions. In the latter case, we need to drop the dentry. Signed-off-by: Jeremy Kerr <jeremy.kerr@canonical.com> Signed-off-by: Matt Fleming <matt.fleming@intel.com> Acked-by: Lee, Chun-Yi <jlee@suse.com> --- drivers/firmware/efivars.c | 49 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 10 deletions(-) --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -658,6 +658,7 @@ static ssize_t efivarfs_file_write(struc u32 attributes; struct inode *inode = file->f_mapping->host; int datasize = count - sizeof(attributes); + unsigned long newdatasize; if (count < sizeof(attributes)) return -EINVAL; @@ -696,32 +697,60 @@ static ssize_t efivarfs_file_write(struc switch (status) { case EFI_SUCCESS: - mutex_lock(&inode->i_mutex); - i_size_write(inode, count); - mutex_unlock(&inode->i_mutex); break; case EFI_INVALID_PARAMETER: count = -EINVAL; - break; + goto out; case EFI_OUT_OF_RESOURCES: count = -ENOSPC; - break; + goto out; case EFI_DEVICE_ERROR: count = -EIO; - break; + goto out; case EFI_WRITE_PROTECTED: count = -EROFS; - break; + goto out; case EFI_SECURITY_VIOLATION: count = -EACCES; - break; + goto out; case EFI_NOT_FOUND: count = -ENOENT; - break; + goto out; default: count = -EINVAL; - break; + goto out; + } + + /* + * 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) { + 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); + drop_nlink(inode); + dput(file->f_dentry); + + } else { + pr_warn("efivarfs: inconsistent EFI variable implementation? " + "status = %lx\n", status); } + out: kfree(data); -- To unsubscribe, e-mail: opensuse-kernel+unsubscribe@opensuse.org To contact the owner, e-mail: opensuse-kernel+owner@opensuse.org
From: Lee, Chun-Yi <joeyli.kernel@gmail.com> Git-commit: 605e70c7aa1b7b0d554baf945630c1d606bbfbc3 Patch-mainline: v3.8 References: bnc#808680 Target: openSUSE 12.3 UEFI variable filesystem need a new mount point, so this patch add efivars kobject to efi_kobj for create a /sys/firmware/efi/efivars folder. Cc: Matthew Garrett <mjg@redhat.com> Cc: H. Peter Anvin <hpa@zytor.com> Signed-off-by: Lee, Chun-Yi <jlee@suse.com> Signed-off-by: Jeremy Kerr <jeremy.kerr@canonical.com> Signed-off-by: Matt Fleming <matt.fleming@intel.com> --- drivers/firmware/efivars.c | 9 +++++++++ include/linux/efi.h | 1 + 2 files changed, 10 insertions(+) --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -1527,6 +1527,7 @@ void unregister_efivars(struct 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); @@ -1558,6 +1559,14 @@ int register_efivars(struct efivars *efi 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. --- a/include/linux/efi.h +++ b/include/linux/efi.h @@ -678,6 +678,7 @@ struct efivars { spinlock_t lock; struct list_head list; struct kset *kset; + struct kobject *kobject; struct bin_attribute *new_var, *del_var; const struct efivar_operations *ops; struct efivar_entry *walk_entry; -- To unsubscribe, e-mail: opensuse-kernel+unsubscribe@opensuse.org To contact the owner, e-mail: opensuse-kernel+owner@opensuse.org
From: Matt Fleming <matt.fleming@intel.com> Git-commit: e913ca7d16d70b75367ff56a3b201980501d542c Patch-mainline: v3.8 References: bnc#808680 Target: openSUSE 12.3 Signed-off-by: Matt Fleming <matt.fleming@intel.com> Acked-by: Lee, Chun-Yi <jlee@suse.com> --- Documentation/filesystems/00-INDEX | 2 ++ Documentation/filesystems/efivarfs.txt | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 Documentation/filesystems/efivarfs.txt --- a/Documentation/filesystems/00-INDEX +++ b/Documentation/filesystems/00-INDEX @@ -38,6 +38,8 @@ dnotify_test.c - example program for dnotify ecryptfs.txt - docs on eCryptfs: stacked cryptographic filesystem for Linux. +efivarfs.txt + - info for the efivarfs filesystem. exofs.txt - info, usage, mount options, design about EXOFS. ext2.txt --- /dev/null +++ b/Documentation/filesystems/efivarfs.txt @@ -0,0 +1,16 @@ + +efivarfs - a (U)EFI variable filesystem + +The efivarfs filesystem was created to address the shortcomings of +using entries in sysfs to maintain EFI variables. The old sysfs EFI +variables code only supported variables of up to 1024 bytes. This +limitation existed in version 0.99 of the EFI specification, but was +removed before any full releases. Since variables can now be larger +than a single page, sysfs isn't the best interface for this. + +Variables can be created, deleted and modified with the efivarfs +filesystem. + +efivarfs is typically mounted like this, + + mount -t efivarfs none /sys/firmware/efi/efivars -- To unsubscribe, e-mail: opensuse-kernel+unsubscribe@opensuse.org To contact the owner, e-mail: opensuse-kernel+owner@opensuse.org
From: Andy Whitcroft <apw@canonical.com> Git-commit: d142df03a798ee7d2db10a1f20945110ea6067ff Patch-mainline: v3.8 References: bnc#808680 Target: openSUSE 12.3 Signed-off-by: Andy Whitcroft <apw@canonical.com> Acked-by: Matthew Garrett <mjg@redhat.com> Acked-by: Jeremy Kerr <jeremy.kerr@canonical.com> Signed-off-by: Matt Fleming <matt.fleming@intel.com> Acked-by: Lee, Chun-Yi <jlee@suse.com> --- drivers/firmware/efivars.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -766,7 +766,7 @@ static ssize_t efivarfs_file_read(struct unsigned long datasize = 0; u32 attributes; void *data; - ssize_t size; + ssize_t size = 0; status = efivars->ops->get_variable(var->var.VariableName, &var->var.VendorGuid, @@ -784,13 +784,13 @@ static ssize_t efivarfs_file_read(struct &var->var.VendorGuid, &attributes, &datasize, (data + 4)); - if (status != EFI_SUCCESS) - return 0; + goto out_free; memcpy(data, &attributes, 4); size = simple_read_from_buffer(userbuf, count, ppos, data, datasize + 4); +out_free: kfree(data); return size; -- To unsubscribe, e-mail: opensuse-kernel+unsubscribe@opensuse.org To contact the owner, e-mail: opensuse-kernel+owner@opensuse.org
From: Andy Whitcroft <apw@canonical.com> Git-commit: 45a937a883c4411648b80e87341b237cc48009c1 Patch-mainline: v3.8 References: bnc#808680 Target: openSUSE 12.3 Signed-off-by: Andy Whitcroft <apw@canonical.com> Acked-by: Matthew Garrett <mjg@redhat.com> Acked-by: Jeremy Kerr <jeremy.kerr@canonical.com> Signed-off-by: Matt Fleming <matt.fleming@intel.com> Acked-by: Lee, Chun-Yi <jlee@suse.com> --- drivers/firmware/efivars.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -866,7 +866,7 @@ static void efivarfs_hex_to_guid(const c 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; @@ -874,13 +874,15 @@ static int efivarfs_create(struct inode if (dentry->d_name.len < 38) 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) - return -ENOMEM; + if (!var) { + err = -ENOMEM; + goto out; + } namelen = dentry->d_name.len - GUID_LEN; @@ -908,8 +910,10 @@ static int efivarfs_create(struct inode d_instantiate(dentry, inode); dget(dentry); out: - if (err) + if (err) { kfree(var); + iput(inode); + } return err; } -- To unsubscribe, e-mail: opensuse-kernel+unsubscribe@opensuse.org To contact the owner, e-mail: opensuse-kernel+owner@opensuse.org
From: Andy Whitcroft <apw@canonical.com> Git-commit: 5c9b50ab8ca962bc085cfb49bae947910d73f794 Patch-mainline: v3.8 References: bnc#808680 Target: openSUSE 12.3 When d_make_root() fails it will automatically drop the reference on the root inode. We should not be doing so as well. Signed-off-by: Andy Whitcroft <apw@canonical.com> Acked-by: Matthew Garrett <mjg@redhat.com> Acked-by: Jeremy Kerr <jeremy.kerr@canonical.com> Signed-off-by: Matt Fleming <matt.fleming@intel.com> Acked-by: Lee, Chun-Yi <jlee@suse.com> --- drivers/firmware/efivars.c | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -948,7 +948,6 @@ int efivarfs_fill_super(struct super_blo struct dentry *root; struct efivar_entry *entry, *n; struct efivars *efivars = &__efivars; - int err; efivarfs_sb = sb; @@ -960,18 +959,14 @@ int efivarfs_fill_super(struct super_blo 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; @@ -1012,9 +1007,6 @@ int efivarfs_fill_super(struct super_blo } return 0; -fail: - iput(inode); - return err; } static struct dentry *efivarfs_mount(struct file_system_type *fs_type, -- To unsubscribe, e-mail: opensuse-kernel+unsubscribe@opensuse.org To contact the owner, e-mail: opensuse-kernel+owner@opensuse.org
From: Andy Whitcroft <apw@canonical.com> Git-commit: c0359db1a1c484ea810f4d5a877d3b00d107908b Patch-mainline: v3.8 References: bnc#808680 Target: openSUSE 12.3 d_alloc_name() copies the passed name to new storage, once complete we no longer need our name. Signed-off-by: Andy Whitcroft <apw@canonical.com> Acked-by: Matthew Garrett <mjg@redhat.com> Acked-by: Jeremy Kerr <jeremy.kerr@canonical.com> Signed-off-by: Matt Fleming <matt.fleming@intel.com> Acked-by: Lee, Chun-Yi <jlee@suse.com> --- drivers/firmware/efivars.c | 2 ++ 1 file changed, 2 insertions(+) --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -992,6 +992,8 @@ int efivarfs_fill_super(struct super_blo inode = efivarfs_get_inode(efivarfs_sb, root->d_inode, S_IFREG | 0644, 0); dentry = d_alloc_name(root, name); + /* copied by the above to local storage in the dentry. */ + kfree(name); efivars->ops->get_variable(entry->var.VariableName, &entry->var.VendorGuid, -- To unsubscribe, e-mail: opensuse-kernel+unsubscribe@opensuse.org To contact the owner, e-mail: opensuse-kernel+owner@opensuse.org
From: Andy Whitcroft <apw@canonical.com> Git-commit: 5ba6e2919b9e18a051e5bdd6c21f52ee4976513f Patch-mainline: v3.8 References: bnc#808680 Target: openSUSE 12.3 Ensure we free both the name and inode on error when building the individual variables. Signed-off-by: Andy Whitcroft <apw@canonical.com> Acked-by: Matthew Garrett <mjg@redhat.com> Acked-by: Jeremy Kerr <jeremy.kerr@canonical.com> Signed-off-by: Matt Fleming <matt.fleming@intel.com> Acked-by: Lee, Chun-Yi <jlee@suse.com> --- drivers/firmware/efivars.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -948,6 +948,7 @@ int efivarfs_fill_super(struct super_blo struct dentry *root; struct efivar_entry *entry, *n; struct efivars *efivars = &__efivars; + char *name; efivarfs_sb = sb; @@ -969,16 +970,18 @@ int efivarfs_fill_super(struct super_blo 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); + if (!name) + goto fail; for (i = 0; i < len; i++) name[i] = entry->var.VariableName[i] & 0xFF; @@ -991,7 +994,13 @@ int efivarfs_fill_super(struct super_blo 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); @@ -1009,6 +1018,13 @@ int efivarfs_fill_super(struct super_blo } return 0; + +fail_inode: + iput(inode); +fail_name: + kfree(name); +fail: + return -ENOMEM; } static struct dentry *efivarfs_mount(struct file_system_type *fs_type, -- To unsubscribe, e-mail: opensuse-kernel+unsubscribe@opensuse.org To contact the owner, e-mail: opensuse-kernel+owner@opensuse.org
From: Jeremy Kerr <jeremy.kerr@canonical.com> Git-commit: f5f6a60ad52fc786c517303590f1efaea614c69b Patch-mainline: v3.8 References: bnc#808680 Target: openSUSE 12.3 Currently, efivarfs does not enforce exclusion over the get_variable and set_variable operations. Section 7.1 of UEFI requires us to only allow a single processor to enter {get,set}_variable services at once. This change acquires the efivars->lock over calls to these operations from the efivarfs paths. Signed-off-by: Jeremy Kerr <jeremy.kerr@canonical.com> Signed-off-by: Matt Fleming <matt.fleming@intel.com> Acked-by: Lee, Chun-Yi <jlee@suse.com> --- drivers/firmware/efivars.c | 68 ++++++++++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 25 deletions(-) --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -690,35 +690,45 @@ static ssize_t efivarfs_file_write(struc 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 +744,12 @@ static ssize_t efivarfs_file_write(struc 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 +757,7 @@ static ssize_t efivarfs_file_write(struc dput(file->f_dentry); } else { + spin_unlock(&efivars->lock); pr_warn("efivarfs: inconsistent EFI variable implementation? " "status = %lx\n", status); } @@ -768,9 +779,11 @@ static ssize_t efivarfs_file_read(struct 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 +793,13 @@ static ssize_t efivarfs_file_read(struct 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; @@ -1004,11 +1020,13 @@ int efivarfs_fill_super(struct super_blo /* 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; -- To unsubscribe, e-mail: opensuse-kernel+unsubscribe@opensuse.org To contact the owner, e-mail: opensuse-kernel+owner@opensuse.org
From: Matt Fleming <matt.fleming@intel.com> Git-commit: 7253eaba7b179db2e07e67c5b78d5f10b332291d Patch-mainline: v3.8 References: bnc#808680 Target: openSUSE 12.3 Instead of always returning 0 in efivarfs_file_read(), even when we fail to successfully read the variable, convert the EFI status to something meaningful and return that to the caller. This way the user will have some hint as to why the read failed. Acked-by: Jeremy Kerr <jeremy.kerr@canonical.com> Signed-off-by: Matt Fleming <matt.fleming@intel.com> Acked-by: Lee, Chun-Yi <jlee@suse.com> --- drivers/firmware/efivars.c | 62 ++++++++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 26 deletions(-) --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -648,6 +648,36 @@ static int efivarfs_file_open(struct ino return 0; } +static int efi_status_to_err(efi_status_t status) +{ + int err; + + switch (status) { + case EFI_INVALID_PARAMETER: + err = -EINVAL; + break; + case EFI_OUT_OF_RESOURCES: + err = -ENOSPC; + break; + case EFI_DEVICE_ERROR: + err = -EIO; + break; + case EFI_WRITE_PROTECTED: + err = -EROFS; + break; + case EFI_SECURITY_VIOLATION: + err = -EACCES; + break; + case EFI_NOT_FOUND: + err = -ENOENT; + break; + default: + err = -EINVAL; + } + + return err; +} + static ssize_t efivarfs_file_write(struct file *file, const char __user *userbuf, size_t count, loff_t *ppos) { @@ -706,29 +736,7 @@ static ssize_t efivarfs_file_write(struc 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; + return efi_status_to_err(status); } /* @@ -786,12 +794,12 @@ static ssize_t efivarfs_file_read(struct spin_unlock(&efivars->lock); if (status != EFI_BUFFER_TOO_SMALL) - return 0; + return efi_status_to_err(status); data = kmalloc(datasize + 4, GFP_KERNEL); if (!data) - return 0; + return -ENOMEM; spin_lock(&efivars->lock); status = efivars->ops->get_variable(var->var.VariableName, @@ -800,8 +808,10 @@ static ssize_t efivarfs_file_read(struct (data + 4)); spin_unlock(&efivars->lock); - if (status != EFI_SUCCESS) + if (status != EFI_SUCCESS) { + size = efi_status_to_err(status); goto out_free; + } memcpy(data, &attributes, 4); size = simple_read_from_buffer(userbuf, count, ppos, -- To unsubscribe, e-mail: opensuse-kernel+unsubscribe@opensuse.org To contact the owner, e-mail: opensuse-kernel+owner@opensuse.org
From: Jeremy Kerr <jeremy.kerr@canonical.com> Git-commit: 310ad75448329f167af3c4a10e4a9de4d80058ff Patch-mainline: v3.8 References: bnc#808680 Target: openSUSE 12.3 At present, the handling of GUIDs in efivar file names isn't consistent. We use GUID_LEN in some places, and 38 in others (GUID_LEN plus separator), and implicitly use the presence of the trailing NUL. This change removes the trailing NUL from GUID_LEN, so that we're explicitly adding it when required. We also replace magic numbers with GUID_LEN, and clarify the comments where appropriate. We also fix the allocation size in efivar_create_sysfs_entry, where we're allocating one byte too much, due to counting the trailing NUL twice - once when calculating short_name_size, and once in the kzalloc. Signed-off-by: Jeremy Kerr <jeremy.kerr@canonical.com> Signed-off-by: Matt Fleming <matt.fleming@intel.com> Acked-by: Lee, Chun-Yi <jlee@suse.com> --- drivers/firmware/efivars.c | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -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 @@ -897,7 +902,11 @@ static int efivarfs_create(struct inode 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); @@ -910,7 +919,8 @@ static int efivarfs_create(struct inode goto out; } - 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); @@ -1004,8 +1014,8 @@ int efivarfs_fill_super(struct super_blo 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; @@ -1016,7 +1026,7 @@ int efivarfs_fill_super(struct super_blo 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); @@ -1445,11 +1455,18 @@ efivar_create_sysfs_entry(struct 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) { -- To unsubscribe, e-mail: opensuse-kernel+unsubscribe@opensuse.org To contact the owner, e-mail: opensuse-kernel+owner@opensuse.org
From: Matt Fleming <matt.fleming@intel.com> Git-commit: d292384152fdd465dba792d555ce5db83e94efa0 Patch-mainline: v3.8 References: bnc#808680 Target: openSUSE 12.3 Seeing "+ 4" littered throughout the functions gets a bit confusing. Use "sizeof(attributes)" which clearly explains what quantity we're adding. Acked-by: Jeremy Kerr <jeremy.kerr@canonical.com> Signed-off-by: Matt Fleming <matt.fleming@intel.com> Acked-by: Lee, Chun-Yi <jlee@suse.com> --- drivers/firmware/efivars.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -801,7 +801,7 @@ static ssize_t efivarfs_file_read(struct if (status != EFI_BUFFER_TOO_SMALL) return efi_status_to_err(status); - data = kmalloc(datasize + 4, GFP_KERNEL); + data = kmalloc(datasize + sizeof(attributes), GFP_KERNEL); if (!data) return -ENOMEM; @@ -810,7 +810,7 @@ static ssize_t efivarfs_file_read(struct status = efivars->ops->get_variable(var->var.VariableName, &var->var.VendorGuid, &attributes, &datasize, - (data + 4)); + (data + sizeof(attributes))); spin_unlock(&efivars->lock); if (status != EFI_SUCCESS) { @@ -818,9 +818,9 @@ static ssize_t efivarfs_file_read(struct goto out_free; } - memcpy(data, &attributes, 4); + memcpy(data, &attributes, sizeof(attributes)); size = simple_read_from_buffer(userbuf, count, ppos, - data, datasize + 4); + data, datasize + sizeof(attributes)); out_free: kfree(data); -- To unsubscribe, e-mail: opensuse-kernel+unsubscribe@opensuse.org To contact the owner, e-mail: opensuse-kernel+owner@opensuse.org
From: Matt Fleming <matt.fleming@intel.com> Git-commit: 91716322d834cba34f4a7ed5e4a39673eb90862b Patch-mainline: v3.8 References: bnc#808680 Target: openSUSE 12.3 Using pstore's superblock magic number is no doubt going to cause problems in the future. Give efivarfs its own magic number. Acked-by: Jeremy Kerr <jeremy.kerr@canonical.com> Signed-off-by: Matt Fleming <matt.fleming@intel.com> Acked-by: Lee, Chun-Yi <jlee@suse.com> --- drivers/firmware/efivars.c | 2 +- include/uapi/linux/magic.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -991,7 +991,7 @@ int efivarfs_fill_super(struct super_blo 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_magic = EFIVARFS_MAGIC; sb->s_op = &efivarfs_ops; sb->s_time_gran = 1; --- a/include/uapi/linux/magic.h +++ b/include/uapi/linux/magic.h @@ -27,6 +27,7 @@ #define ISOFS_SUPER_MAGIC 0x9660 #define JFFS2_SUPER_MAGIC 0x72b6 #define PSTOREFS_MAGIC 0x6165676C +#define EFIVARFS_MAGIC 0xde5e81e4 #define MINIX_SUPER_MAGIC 0x137F /* minix v1 fs, 14 char names */ #define MINIX_SUPER_MAGIC2 0x138F /* minix v1 fs, 30 char names */ -- To unsubscribe, e-mail: opensuse-kernel+unsubscribe@opensuse.org To contact the owner, e-mail: opensuse-kernel+owner@opensuse.org
From: Matt Fleming <matt.fleming@intel.com> Git-commit: 07b1c5bc64cff9c880261a1fef562ef7ea7f6575 Patch-mainline: v3.8 References: bnc#808680 Target: openSUSE 12.3 There's no reason to declare 'datasize' as an int, since the majority of the functions it's passed to expect an unsigned long anyway. Plus, this way we avoid any sign problems during arithmetic. Acked-by: Jeremy Kerr <jeremy.kerr@canonical.com> Signed-off-by: Matt Fleming <matt.fleming@intel.com> Acked-by: Lee, Chun-Yi <jlee@suse.com> --- drivers/firmware/efivars.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -692,7 +692,7 @@ static ssize_t efivarfs_file_write(struc void *data; u32 attributes; struct inode *inode = file->f_mapping->host; - int datasize = count - sizeof(attributes); + unsigned long datasize = count - sizeof(attributes); unsigned long newdatasize; if (count < sizeof(attributes)) -- To unsubscribe, e-mail: opensuse-kernel+unsubscribe@opensuse.org To contact the owner, e-mail: opensuse-kernel+owner@opensuse.org
From: Matt Fleming <matt.fleming@intel.com> Git-commit: aeeaa8d46aa38c9cc5fac23feb9b1f91bdbf5dd3 Patch-mainline: v3.8 References: bnc#808680 Target: openSUSE 12.3 Instead of returning -ENOSPC if efivarfs_get_inode() fails we should be returning -ENOMEM, since running out of memory is the only reason it can fail. Furthermore, that's the error value used everywhere else in this file. It's also less likely to confuse users that hit this error case. Acked-by: Jeremy Kerr <jeremy.kerr@canonical.com> Signed-off-by: Matt Fleming <matt.fleming@intel.com> Acked-by: Lee, Chun-Yi <jlee@suse.com> --- drivers/firmware/efivars.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -911,7 +911,7 @@ static int efivarfs_create(struct inode inode = efivarfs_get_inode(dir->i_sb, dir, mode, 0); if (!inode) - return -ENOSPC; + return -ENOMEM; var = kzalloc(sizeof(struct efivar_entry), GFP_KERNEL); if (!var) { -- To unsubscribe, e-mail: opensuse-kernel+unsubscribe@opensuse.org To contact the owner, e-mail: opensuse-kernel+owner@opensuse.org
From: Matt Fleming <matt.fleming@intel.com> Git-commit: cfcf2f11708f934d2bd294f973c2fcb0cc54f293 Patch-mainline: v3.8 References: bnc#808680 Target: openSUSE 12.3 We're stuffing a variable of type size_t (unsigned) into a ssize_t (signed) which, even though both types should be the same number of bits, it's just asking for sign issues to be introduced. Cc: Jeremy Kerr <jeremy.kerr@canonical.com> Reported-by: Alan Cox <alan@lxorguk.ukuu.org.uk> Signed-off-by: Matt Fleming <matt.fleming@intel.com> Acked-by: Lee, Chun-Yi <jlee@suse.com> --- drivers/firmware/efivars.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -694,6 +694,7 @@ static ssize_t efivarfs_file_write(struc struct inode *inode = file->f_mapping->host; unsigned long datasize = count - sizeof(attributes); unsigned long newdatasize; + ssize_t bytes = 0; if (count < sizeof(attributes)) return -EINVAL; @@ -706,22 +707,22 @@ static ssize_t efivarfs_file_write(struc efivars = var->efivars; if (copy_from_user(&attributes, userbuf, sizeof(attributes))) { - count = -EFAULT; + bytes = -EFAULT; goto out; } if (attributes & ~(EFI_VARIABLE_MASK)) { - count = -EINVAL; + bytes = -EINVAL; goto out; } if (copy_from_user(data, userbuf + sizeof(attributes), datasize)) { - count = -EFAULT; + bytes = -EFAULT; goto out; } if (validate_var(&var->var, data, datasize) == false) { - count = -EINVAL; + bytes = -EINVAL; goto out; } @@ -744,6 +745,8 @@ static ssize_t efivarfs_file_write(struc return efi_status_to_err(status); } + bytes = 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 @@ -778,7 +781,7 @@ static ssize_t efivarfs_file_write(struc out: kfree(data); - return count; + return bytes; } static ssize_t efivarfs_file_read(struct file *file, char __user *userbuf, -- To unsubscribe, e-mail: opensuse-kernel+unsubscribe@opensuse.org To contact the owner, e-mail: opensuse-kernel+owner@opensuse.org
From: Matt Fleming <matt.fleming@intel.com> Git-commit: 89d16665d388837b30972081d97b814be26d68a2 Patch-mainline: v3.8 References: bnc#808680 Target: openSUSE 12.3 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> Acked-by: Lee, Chun-Yi <jlee@suse.com> --- drivers/firmware/efivars.c | 43 ++++++++++++++++++++++++++++++++++--------- include/linux/efi.h | 1 + 2 files changed, 35 insertions(+), 9 deletions(-) --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -694,28 +694,51 @@ static ssize_t efivarfs_file_write(struc 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; --- a/include/linux/efi.h +++ b/include/linux/efi.h @@ -664,6 +664,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 { -- To unsubscribe, e-mail: opensuse-kernel+unsubscribe@opensuse.org To contact the owner, e-mail: opensuse-kernel+owner@opensuse.org
From: Matt Fleming <matt.fleming@intel.com> Git-commit: e83af1f18c78c7b6aa720beecc927ecc8afd3647 Patch-mainline: v3.8 References: bnc#808680 Target: openSUSE 12.3 sparse is complaining that efivarfs_fill_super() doesn't have a prototype. Make it static to avoid the warning. Cc: Xie ChanglongX <changlongx.xie@intel.com> Cc: Matthew Garrett <mjg@redhat.com> Cc: Jeremy Kerr <jeremy.kerr@canonical.com> Signed-off-by: Matt Fleming <matt.fleming@intel.com> Acked-by: Lee, Chun-Yi <jlee@suse.com> --- drivers/firmware/efivars.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -1004,7 +1004,7 @@ static int efivarfs_unlink(struct inode return -EINVAL; }; -int efivarfs_fill_super(struct super_block *sb, void *data, int silent) +static int efivarfs_fill_super(struct super_block *sb, void *data, int silent) { struct inode *inode = NULL; struct dentry *root; -- To unsubscribe, e-mail: opensuse-kernel+unsubscribe@opensuse.org To contact the owner, e-mail: opensuse-kernel+owner@opensuse.org
From: Lingzhu Xiang <lxiang@redhat.com> Git-commit: de5fe95587b4dcc86cc451fce2909dfa504fce10 Patch-mainline: v3.8 References: bnc#808680 Target: openSUSE 12.3 efivarfs_unlink() should drop the file's link count, not the directory's. Signed-off-by: Lingzhu Xiang <lxiang@redhat.com> Cc: Jeremy Kerr <jeremy.kerr@canonical.com> Tested-by: Lee, Chun-Yi <jlee@suse.com> Signed-off-by: Matt Fleming <matt.fleming@intel.com> Acked-by: Lee, Chun-Yi <jlee@suse.com> --- drivers/firmware/efivars.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -995,7 +995,7 @@ static int efivarfs_unlink(struct inode list_del(&var->list); spin_unlock(&efivars->lock); efivar_unregister(var); - drop_nlink(dir); + drop_nlink(dentry->d_inode); dput(dentry); return 0; } -- To unsubscribe, e-mail: opensuse-kernel+unsubscribe@opensuse.org To contact the owner, e-mail: opensuse-kernel+owner@opensuse.org
From: Matt Fleming <matt.fleming@intel.com> Git-commit: 1fa7e6958c5f82cc75c594e3ffaf73cc26fc94c1 Patch-mainline: v3.8 References: bnc#808680 Target: openSUSE 12.3 Files are created in efivarfs_create() before a corresponding variable is created in the firmware. This leads to users being able to read/write to the file without the variable existing in the firmware. Reading a non-existent variable currently returns -ENOENT, which is confusing because the file obviously *does* exist. Convert EFI_NOT_FOUND into -EIO which is the closest thing to "error while interacting with firmware", and should hopefully indicate to the caller that the variable is in some uninitialised state. Cc: Josh Boyer <jwboyer@redhat.com> Acked-by: Jeremy Kerr <jeremy.kerr@canonical.com> Cc: Lee, Chun-Yi <jlee@suse.com> Cc: Andy Whitcroft <apw@canonical.com> Reported-by: Lingzhu Xiang <lxiang@redhat.com> Signed-off-by: Matt Fleming <matt.fleming@intel.com> Acked-by: Lee, Chun-Yi <jlee@suse.com> --- drivers/firmware/efivars.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c index fa9fa03..807dad4 100644 --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -674,7 +674,7 @@ static int efi_status_to_err(efi_status_t status) err = -EACCES; break; case EFI_NOT_FOUND: - err = -ENOENT; + err = -EIO; break; default: err = -EINVAL; -- 1.6.4.2 -- To unsubscribe, e-mail: opensuse-kernel+unsubscribe@opensuse.org To contact the owner, e-mail: opensuse-kernel+owner@opensuse.org
From: Matt Fleming <matt.fleming@intel.com> Git-commit: 791eb564d218dabe0f9a2212916fe54240b97afb Patch-mainline: v3.8 References: bnc#808680 Target: openSUSE 12.3 Unlike the unlink path that is called from the VFS layer, we need to call d_delete() ourselves when a variable is deleted in efivarfs_file_write(). Failure to do so means we can access a stale struct efivar_entry when reading/writing the file, which can result in the following oops, [ 59.978216] general protection fault: 0000 [#1] SMP [ 60.038660] CPU 9 [ 60.040501] Pid: 1001, comm: cat Not tainted 3.7.0-2.fc19.x86_64 #1 IBM System x3550 M3 -[7944I21]-/69Y4438 [ 60.050840] RIP: 0010:[<ffffffff810d5d1e>] [<ffffffff810d5d1e>] __lock_acquire+0x5e/0x1bb0 [ 60.059198] RSP: 0018:ffff880270595ce8 EFLAGS: 00010046 [ 60.064500] RAX: 0000000000000046 RBX: 0000000000000002 RCX: 0000000000000000 [ 60.071617] RDX: 0000000000000000 RSI: 0000000000000000 RDI: 6b6b6b6b6b6b6b83 [ 60.078735] RBP: ffff880270595dd8 R08: 0000000000000002 R09: 0000000000000000 [ 60.085852] R10: 6b6b6b6b6b6b6b83 R11: 0000000000000000 R12: 0000000000000000 [ 60.092971] R13: ffff88027170cd20 R14: 0000000000000000 R15: 0000000000000000 [ 60.100091] FS: 00007fc0c8ff3740(0000) GS:ffff880277000000(0000) knlGS:0000000000000000 [ 60.108164] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 60.113899] CR2: 0000000001520000 CR3: 000000026d594000 CR4: 00000000000007e0 [ 60.121016] DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000 [ 60.128135] DR3: 0000000000000000 DR6: 00000000ffff0ff0 DR7: 0000000000000400 [ 60.135254] Process cat (pid: 1001, threadinfo ffff880270594000, task ffff88027170cd20) [ 60.143239] Stack: [ 60.145251] ffff880270595cf8 ffffffff81021da3 ffff880270595d08 ffffffff81021e19 [ 60.152714] ffff880270595d38 ffffffff810acdb5 ffff880200000168 0000000000000086 [ 60.160175] ffff88027170d5e8 ffffffff810d25ed ffff880270595d58 ffffffff810ace7f [ 60.167638] Call Trace: [ 60.170088] [<ffffffff81021da3>] ? native_sched_clock+0x13/0x80 [ 60.176085] [<ffffffff81021e19>] ? sched_clock+0x9/0x10 [ 60.181389] [<ffffffff810acdb5>] ? sched_clock_cpu+0xc5/0x120 [ 60.187211] [<ffffffff810d25ed>] ? trace_hardirqs_off+0xd/0x10 [ 60.193121] [<ffffffff810ace7f>] ? local_clock+0x6f/0x80 [ 60.198513] [<ffffffff810d2f6f>] ? lock_release_holdtime.part.26+0xf/0x180 [ 60.205465] [<ffffffff810d7b57>] ? lock_release_non_nested+0x2e7/0x320 [ 60.212073] [<ffffffff815638bb>] ? efivarfs_file_write+0x5b/0x280 [ 60.218242] [<ffffffff810d7f41>] lock_acquire+0xa1/0x1f0 [ 60.223633] [<ffffffff81563971>] ? efivarfs_file_write+0x111/0x280 [ 60.229892] [<ffffffff8118b47c>] ? might_fault+0x5c/0xb0 [ 60.235287] [<ffffffff816f1bf6>] _raw_spin_lock+0x46/0x80 [ 60.240762] [<ffffffff81563971>] ? efivarfs_file_write+0x111/0x280 [ 60.247018] [<ffffffff81563971>] efivarfs_file_write+0x111/0x280 [ 60.253103] [<ffffffff811d307f>] vfs_write+0xaf/0x190 [ 60.258233] [<ffffffff811d33d5>] sys_write+0x55/0xa0 [ 60.263278] [<ffffffff816fbd19>] system_call_fastpath+0x16/0x1b [ 60.269271] Code: 41 0f 45 d8 4c 89 75 f0 4c 89 7d f8 85 c0 0f 84 09 01 00 00 8b 05 a3 f9 ff 00 49 89 fa 41 89 f6 41 89 d3 85 c0 0f 84 12 01 00 00 <49> 8b 02 ba 01 00 00 00 48 3d a0 07 14 82 0f 44 da 41 83 fe 01 [ 60.289431] RIP [<ffffffff810d5d1e>] __lock_acquire+0x5e/0x1bb0 [ 60.295444] RSP <ffff880270595ce8> [ 60.298928] ---[ end trace 1bbfd41a2cf6a0d8 ]--- Cc: Josh Boyer <jwboyer@redhat.com> Acked-by: Jeremy Kerr <jeremy.kerr@canonical.com> Cc: Lee, Chun-Yi <jlee@suse.com> Cc: Andy Whitcroft <apw@canonical.com> Reported-by: Lingzhu Xiang <lxiang@redhat.com> Signed-off-by: Matt Fleming <matt.fleming@intel.com> Acked-by: Lee, Chun-Yi <jlee@suse.com> --- drivers/firmware/efivars.c | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c index 807dad4..2ed59dc 100644 --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -793,6 +793,7 @@ static ssize_t efivarfs_file_write(struct file *file, spin_unlock(&efivars->lock); efivar_unregister(var); drop_nlink(inode); + d_delete(file->f_dentry); dput(file->f_dentry); } else { -- 1.6.4.2 -- To unsubscribe, e-mail: opensuse-kernel+unsubscribe@opensuse.org To contact the owner, e-mail: opensuse-kernel+owner@opensuse.org
From: Seiji Aguchi <seiji.aguchi@hds.com> Git-commit: d80a361d779a9f19498943d1ca84243209cd5647 Patch-mainline: v3.8 References: bnc#808680 Target: openSUSE 12.3 [Issue] As discussed in a thread below, Running out of space in EFI isn't a well-tested scenario. And we wouldn't expect all firmware to handle it gracefully. http://marc.info/?l=linux-kernel&m=134305325801789&w=2 On the other hand, current efi_pstore doesn't check a remaining space of storage at writing time. Therefore, efi_pstore may not work if it tries to write a large amount of data. [Patch Description] To avoid handling the situation above, this patch checks if there is a space enough to log with QueryVariableInfo() before writing data. Signed-off-by: Seiji Aguchi <seiji.aguchi@hds.com> Acked-by: Mike Waychison <mikew@google.com> Signed-off-by: Tony Luck <tony.luck@intel.com> Acked-by: Lee, Chun-Yi <jlee@suse.com> --- drivers/firmware/efivars.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -1188,12 +1188,29 @@ static int efi_pstore_write(enum pstore_ struct efivars *efivars = psi->data; struct efivar_entry *entry, *found = NULL; int i, ret = 0; + u64 storage_space, remaining_space, max_variable_size; + efi_status_t status = EFI_NOT_FOUND; sprintf(stub_name, "dump-type%u-%u-", type, part); sprintf(name, "%s%lu", stub_name, get_seconds()); spin_lock(&efivars->lock); + /* + * 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); + *id = part; + return -ENOSPC; + } + for (i = 0; i < DUMP_NAME_LEN; i++) efi_name[i] = stub_name[i]; -- To unsubscribe, e-mail: opensuse-kernel+unsubscribe@opensuse.org To contact the owner, e-mail: opensuse-kernel+owner@opensuse.org
From: Seiji Aguchi <seiji.aguchi@hds.com> Git-commit: 9f244e9cfd70c7c0f82d3c92ce772ab2a92d9f64 Patch-mainline: v3.9-rc1 References: bnc#808680 Target: openSUSE 12.3 [Issue] When pstore is in panic and emergency-restart paths, it may be blocked in those paths because it simply takes spin_lock. This is an example scenario which pstore may hang up in a panic path: - cpuA grabs psinfo->buf_lock - cpuB panics and calls smp_send_stop - smp_send_stop sends IRQ to cpuA - after 1 second, cpuB gives up on cpuA and sends an NMI instead - cpuA is now in an NMI handler while still holding buf_lock - cpuB is deadlocked This case may happen if a firmware has a bug and cpuA is stuck talking with it more than one second. Also, this is a similar scenario in an emergency-restart path: - cpuA grabs psinfo->buf_lock and stucks in a firmware - cpuB kicks emergency-restart via either sysrq-b or hangcheck timer. And then, cpuB is deadlocked by taking psinfo->buf_lock again. [Solution] This patch avoids the deadlocking issues in both panic and emergency_restart paths by introducing a function, is_non_blocking_path(), to check if a cpu can be blocked in current path. With this patch, pstore is not blocked even if another cpu has taken a spin_lock, in those paths by changing from spin_lock_irqsave to spin_trylock_irqsave. In addition, according to a comment of emergency_restart() in kernel/sys.c, spin_lock shouldn't be taken in an emergency_restart path to avoid deadlock. This patch fits the comment below. <snip> /** * emergency_restart - reboot the system * * Without shutting down any hardware or taking any locks * reboot the system. This is called when we know we are in * trouble so this is our best effort to reboot. This is * safe to call in interrupt context. */ void emergency_restart(void) <snip> Signed-off-by: Seiji Aguchi <seiji.aguchi@hds.com> Acked-by: Don Zickus <dzickus@redhat.com> Signed-off-by: Tony Luck <tony.luck@intel.com> Acked-by: Lee, Chun-Yi <jlee@suse.com> --- fs/pstore/platform.c | 35 +++++++++++++++++++++++++++++------ include/linux/pstore.h | 6 ++++++ 2 files changed, 35 insertions(+), 6 deletions(-) --- a/fs/pstore/platform.c +++ b/fs/pstore/platform.c @@ -56,6 +56,27 @@ static DEFINE_TIMER(pstore_timer, pstore static void pstore_dowork(struct work_struct *); static DECLARE_WORK(pstore_work, pstore_dowork); +bool pstore_cannot_block_path(enum kmsg_dump_reason reason) +{ + /* + * In case of NMI path, pstore shouldn't be blocked + * regardless of reason. + */ + if (in_nmi()) + return true; + + switch (reason) { + /* In panic case, other cpus are stopped by smp_send_stop(). */ + case KMSG_DUMP_PANIC: + /* Emergency restart shouldn't be blocked by spin lock. */ + case KMSG_DUMP_EMERG: + return true; + default: + return false; + } +} +EXPORT_SYMBOL_GPL(pstore_cannot_block_path); + /* * pstore_lock just protects "psinfo" during * calls to pstore_register() @@ -114,10 +135,12 @@ static void pstore_dump(struct kmsg_dump why = get_reason_str(reason); - if (in_nmi()) { - is_locked = spin_trylock(&psinfo->buf_lock); - if (!is_locked) - pr_err("pstore dump routine blocked in NMI, may corrupt error record\n"); + if (pstore_cannot_block_path(reason)) { + is_locked = spin_trylock_irqsave(&psinfo->buf_lock, flags); + if (!is_locked) { + pr_err("pstore dump routine blocked in %s path, may corrupt error record\n" + , in_nmi() ? "NMI" : why); + } } else spin_lock_irqsave(&psinfo->buf_lock, flags); oopscount++; @@ -143,9 +166,9 @@ static void pstore_dump(struct kmsg_dump total += hsize + len; part++; } - if (in_nmi()) { + if (pstore_cannot_block_path(reason)) { if (is_locked) - spin_unlock(&psinfo->buf_lock); + spin_unlock_irqrestore(&psinfo->buf_lock, flags); } else spin_unlock_irqrestore(&psinfo->buf_lock, flags); } --- a/include/linux/pstore.h +++ b/include/linux/pstore.h @@ -66,12 +66,18 @@ struct pstore_info { #ifdef CONFIG_PSTORE extern int pstore_register(struct pstore_info *); +extern bool pstore_cannot_block_path(enum kmsg_dump_reason reason); #else static inline int pstore_register(struct pstore_info *psi) { return -ENODEV; } +static inline bool +pstore_cannot_block_path(enum kmsg_dump_reason reason) +{ + return false; +} #endif #endif /*_LINUX_PSTORE_H*/ -- To unsubscribe, e-mail: opensuse-kernel+unsubscribe@opensuse.org To contact the owner, e-mail: opensuse-kernel+owner@opensuse.org
From: Seiji Aguchi <seiji.aguchi@hds.com> Git-commit: e59310adf5eebce108f78b6c47bb330aae2e1666 Patch-mainline: v3.9-rc1 References: bnc#808680 Target: openSUSE 12.3 [Issue] There is a scenario which efi_pstore may hang up: - cpuA grabs efivars->lock - cpuB panics and calls smp_send_stop - smp_send_stop sends IRQ to cpuA - after 1 second, cpuB gives up on cpuA and sends an NMI instead - cpuA is now in an NMI handler while still holding efivars->lock - cpuB is deadlocked This case may happen if a firmware has a bug and cpuA is stuck talking with it. [Solution] This patch changes a spin_lock to a spin_trylock in non-blocking paths. and if the spin_lock has already taken by another cpu, it returns without accessing to a firmware to avoid the deadlock. Signed-off-by: Seiji Aguchi <seiji.aguchi@hds.com> Acked-by: Don Zickus <dzickus@redhat.com> Signed-off-by: Tony Luck <tony.luck@intel.com> Acked-by: Lee, Chun-Yi <jlee@suse.com> --- drivers/firmware/efivars.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -1194,7 +1194,16 @@ static int efi_pstore_write(enum pstore_ sprintf(stub_name, "dump-type%u-%u-", type, part); sprintf(name, "%s%lu", stub_name, get_seconds()); - spin_lock(&efivars->lock); + 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(&efivars->lock)) + return -EBUSY; + } else + spin_lock(&efivars->lock); /* * Check if there is a space enough to log. -- To unsubscribe, e-mail: opensuse-kernel+unsubscribe@opensuse.org To contact the owner, e-mail: opensuse-kernel+owner@opensuse.org
From: Matt Fleming <matt.fleming@intel.com> Git-commit: da27a24383b2b10bf6ebd0db29b325548aafecb4 Patch-mainline: v3.9-rc1 References: bnc#808680 Target: openSUSE 12.3 It makes no sense to treat the following filenames as unique, VarName-abcdefab-abcd-abcd-abcd-abcdefabcdef VarName-ABCDEFAB-ABCD-ABCD-ABCD-ABCDEFABCDEF VarName-ABcDEfAB-ABcD-ABcD-ABcD-ABcDEfABcDEf VarName-aBcDEfAB-aBcD-aBcD-aBcD-aBcDEfaBcDEf ... etc ... since the guid will be converted into a binary representation, which has no case. Roll our own dentry operations so that we can treat the variable name part of filenames ("VarName" in the above example) as case-sensitive, but the guid portion as case-insensitive. That way, efivarfs will refuse to create the above files if any one already exists. Reported-by: Lingzhu Xiang <lxiang@redhat.com> Cc: Matthew Garrett <mjg59@srcf.ucam.org> Cc: Jeremy Kerr <jeremy.kerr@canonical.com> Cc: Al Viro <viro@zeniv.linux.org.uk> Signed-off-by: Matt Fleming <matt.fleming@intel.com> Acked-by: Lee, Chun-Yi <jlee@suse.com> --- drivers/firmware/efivars.c | 95 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 2 deletions(-) --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -1005,6 +1005,84 @@ static int efivarfs_unlink(struct inode 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 qstr q; + + q.name = name; + q.len = strlen(name); + + if (efivarfs_d_hash(NULL, NULL, &q)) + return NULL; + + return d_alloc(parent, &q); +} + static int efivarfs_fill_super(struct super_block *sb, void *data, int silent) { struct inode *inode = NULL; @@ -1020,6 +1098,7 @@ static int efivarfs_fill_super(struct su 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); @@ -1060,7 +1139,7 @@ static int efivarfs_fill_super(struct su if (!inode) goto fail_name; - dentry = d_alloc_name(root, name); + dentry = efivarfs_alloc_dentry(root, name); if (!dentry) goto fail_inode; @@ -1110,8 +1189,20 @@ static struct file_system_type efivarfs_ .kill_sb = efivarfs_kill_sb, }; +/* + * 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, }; -- To unsubscribe, e-mail: opensuse-kernel+unsubscribe@opensuse.org To contact the owner, e-mail: opensuse-kernel+owner@opensuse.org
From: Seiji Aguchi <seiji.aguchi@hds.com> Git-commit: 81fa4e581d9283f7992a0d8c534bb141eb840a14 Patch-mainline: v3.9-rc1 References: bnc#808680 Target: openSUSE 12.3 [Problem] There is a scenario which efi_pstore fails to log messages in a panic case. - CPUA holds an efi_var->lock in either efivarfs parts or efi_pstore with interrupt enabled. - CPUB panics and sends IPI to CPUA in smp_send_stop(). - CPUA stops with holding the lock. - CPUB kicks efi_pstore_write() via kmsg_dump(KSMG_DUMP_PANIC) but it returns without logging messages. [Patch Description] This patch disables an external interruption while holding efivars->lock as follows. In efi_pstore_write() and get_var_data(), spin_lock/spin_unlock is replaced by spin_lock_irqsave/spin_unlock_irqrestore because they may be called in an interrupt context. In other functions, they are replaced by spin_lock_irq/spin_unlock_irq. because they are all called from a process context. By applying this patch, we can avoid the problem above with a following senario. - CPUA holds an efi_var->lock with interrupt disabled. - CPUB panics and sends IPI to CPUA in smp_send_stop(). - CPUA receives the IPI after releasing the lock because it is disabling interrupt while holding the lock. - CPUB waits for one sec until CPUA releases the lock. - CPUB kicks efi_pstore_write() via kmsg_dump(KSMG_DUMP_PANIC) And it can hold the lock successfully. Signed-off-by: Seiji Aguchi <seiji.aguchi@hds.com> Acked-by: Mike Waychison <mikew@google.com> Acked-by: Matt Fleming <matt.fleming@intel.com> Signed-off-by: Tony Luck <tony.luck@intel.com> Acked-by: Lee, Chun-Yi <jlee@suse.com> --- drivers/firmware/efivars.c | 82 +++++++++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 40 deletions(-) --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -405,10 +405,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", @@ -537,14 +538,14 @@ efivar_store_raw(struct efivar_entry *en return -EINVAL; } - spin_lock(&efivars->lock); + spin_lock_irq(&efivars->lock); 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", @@ -713,7 +714,7 @@ static ssize_t efivarfs_file_write(struc * amounts of memory. Pick a default size of 64K if * QueryVariableInfo() isn't supported by the firmware. */ - spin_lock(&efivars->lock); + spin_lock_irq(&efivars->lock); if (!efivars->ops->query_variable_info) status = EFI_UNSUPPORTED; @@ -723,7 +724,7 @@ static ssize_t efivarfs_file_write(struc &remaining_size, &max_size); } - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); if (status != EFI_SUCCESS) { if (status != EFI_UNSUPPORTED) @@ -754,7 +755,7 @@ static ssize_t efivarfs_file_write(struc * 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); status = efivars->ops->set_variable(var->var.VariableName, &var->var.VendorGuid, @@ -762,7 +763,7 @@ static ssize_t efivarfs_file_write(struc data); if (status != EFI_SUCCESS) { - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); kfree(data); return efi_status_to_err(status); @@ -783,21 +784,21 @@ static ssize_t efivarfs_file_write(struc 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 +820,11 @@ static ssize_t efivarfs_file_read(struct 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 +834,12 @@ static ssize_t efivarfs_file_read(struct 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); @@ -967,9 +968,9 @@ static int efivarfs_create(struct inode 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: @@ -986,7 +987,7 @@ static int efivarfs_unlink(struct inode 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, @@ -994,14 +995,14 @@ static int efivarfs_unlink(struct inode 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; }; @@ -1146,13 +1147,13 @@ static int efivarfs_fill_super(struct su /* 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; @@ -1215,7 +1216,7 @@ static int efi_pstore_open(struct pstore { 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; @@ -1225,7 +1226,7 @@ static int efi_pstore_close(struct pstor { struct efivars *efivars = psi->data; - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); return 0; } @@ -1281,6 +1282,7 @@ static int efi_pstore_write(enum pstore_ int i, ret = 0; u64 storage_space, remaining_space, max_variable_size; efi_status_t status = EFI_NOT_FOUND; + unsigned long flags; sprintf(stub_name, "dump-type%u-%u-", type, part); sprintf(name, "%s%lu", stub_name, get_seconds()); @@ -1291,10 +1293,10 @@ static int efi_pstore_write(enum pstore_ * this driver returns without entering firmware to avoid * hanging up. */ - if (!spin_trylock(&efivars->lock)) + if (!spin_trylock_irqsave(&efivars->lock, flags)) return -EBUSY; } else - spin_lock(&efivars->lock); + spin_lock_irqsave(&efivars->lock, flags); /* * Check if there is a space enough to log. @@ -1306,7 +1308,7 @@ static int efi_pstore_write(enum pstore_ &remaining_space, &max_variable_size); if (status || remaining_space < size + DUMP_NAME_LEN * 2) { - spin_unlock(&efivars->lock); + spin_unlock_irqrestore(&efivars->lock, flags); *id = part; return -ENOSPC; } @@ -1347,7 +1349,7 @@ static int efi_pstore_write(enum pstore_ efivars->ops->set_variable(efi_name, &vendor, PSTORE_EFI_ATTRIBUTES, size, psi->buf); - spin_unlock(&efivars->lock); + spin_unlock_irqrestore(&efivars->lock, flags); if (found) efivar_unregister(found); @@ -1431,7 +1433,7 @@ static ssize_t efivar_create(struct file return -EINVAL; } - spin_lock(&efivars->lock); + spin_lock_irq(&efivars->lock); /* * Does this variable already exist? @@ -1449,7 +1451,7 @@ static ssize_t efivar_create(struct file } } if (found) { - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); return -EINVAL; } @@ -1463,10 +1465,10 @@ static ssize_t efivar_create(struct file 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, @@ -1494,7 +1496,7 @@ static ssize_t efivar_delete(struct file if (!capable(CAP_SYS_ADMIN)) return -EACCES; - spin_lock(&efivars->lock); + spin_lock_irq(&efivars->lock); /* * Does this variable already exist? @@ -1512,7 +1514,7 @@ static ssize_t efivar_delete(struct file } } if (!found) { - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); return -EINVAL; } /* force the Attributes/DataSize to 0 to ensure deletion */ @@ -1528,12 +1530,12 @@ static ssize_t efivar_delete(struct file 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.... */ @@ -1648,9 +1650,9 @@ efivar_create_sysfs_entry(struct 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; } @@ -1719,9 +1721,9 @@ void unregister_efivars(struct 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) -- To unsubscribe, e-mail: opensuse-kernel+unsubscribe@opensuse.org To contact the owner, e-mail: opensuse-kernel+owner@opensuse.org
From: Seiji Aguchi <seiji.aguchi@hds.com> Git-commit: a93bc0c6e07ed9bac44700280e65e2945d864fd4 Patch-mainline: v3.9-rc1 References: bnc#808680 Target: openSUSE 12.3 [Problem] efi_pstore creates sysfs entries, which enable users to access to NVRAM, in a write callback. If a kernel panic happens in an interrupt context, it may fail because it could sleep due to dynamic memory allocations during creating sysfs entries. [Patch Description] This patch removes sysfs operations from a write callback by introducing a workqueue updating sysfs entries which is scheduled after the write callback is called. Also, the workqueue is kicked in a just oops case. A system will go down in other cases such as panic, clean shutdown and emergency restart. And we don't need to create sysfs entries because there is no chance for users to access to them. efi_pstore will be robust against a kernel panic in an interrupt context with this patch. Signed-off-by: Seiji Aguchi <seiji.aguchi@hds.com> Acked-by: Matt Fleming <matt.fleming@intel.com> Signed-off-by: Tony Luck <tony.luck@intel.com> Acked-by: Lee, Chun-Yi <jlee@suse.com> --- drivers/firmware/efivars.c | 85 ++++++++++++++++++++++++++++++++++++++++++--- include/linux/efi.h | 3 + 2 files changed, 82 insertions(+), 6 deletions(-) --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -158,6 +158,13 @@ efivar_create_sysfs_entry(struct 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) @@ -1354,11 +1361,8 @@ static int efi_pstore_write(enum pstore_ if (found) efivar_unregister(found); - 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; @@ -1542,6 +1546,75 @@ static ssize_t efivar_delete(struct file 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 @@ -1879,6 +1952,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); --- a/include/linux/efi.h +++ b/include/linux/efi.h @@ -673,7 +673,8 @@ struct efivars { * 1) ->list - adds, removals, reads, writes * 2) ops.[gs]et_variable() calls. * It must not be held when creating sysfs entries or calling kmalloc. - * ops.get_next_variable() is only called from register_efivars(), + * ops.get_next_variable() is only called from register_efivars() + * or efivar_update_sysfs_entries(), * which is protected by the BKL, so that path is safe. */ spinlock_t lock; -- To unsubscribe, e-mail: opensuse-kernel+unsubscribe@opensuse.org To contact the owner, e-mail: opensuse-kernel+owner@opensuse.org
From: Matt Fleming <matt.fleming@intel.com> Git-commit: 47f531e8ba3bc3901a0c493f4252826c41dea1a1 Patch-mainline: v3.9-rc1 References: bnc#808680 Target: openSUSE 12.3 The only thing that efivarfs does to enforce a valid filename is ensure that the name isn't too short. We need to strongly sanitise any filenames, not least because variable creation is delayed until efivarfs_file_write(), which means we can't rely on the firmware to inform us of an invalid name, because if the file is never written to we'll never know it's invalid. Perform a couple of steps before agreeing to create a new file, * hex_to_bin() returns a value indicating whether or not it was able to convert its arguments to a binary representation - we should check it. * Ensure that the GUID portion of the filename is the correct length and format. * The variable name portion of the filename needs to be at least one character in size. Reported-by: Lingzhu Xiang <lxiang@redhat.com> Cc: Matthew Garrett <mjg59@srcf.ucam.org> Cc: Jeremy Kerr <jeremy.kerr@canonical.com> Cc: Al Viro <viro@zeniv.linux.org.uk> Signed-off-by: Matt Fleming <matt.fleming@intel.com> Acked-by: Lee, Chun-Yi <jlee@suse.com> --- drivers/firmware/efivars.c | 49 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 5 deletions(-) --- 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> @@ -909,6 +910,48 @@ static struct inode *efivarfs_get_inode( 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 should be right after the first '-' */ + if (s - 1 != strchr(str, '-')) + 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]); @@ -937,11 +980,7 @@ static int efivarfs_create(struct inode 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); -- To unsubscribe, e-mail: opensuse-kernel+unsubscribe@opensuse.org To contact the owner, e-mail: opensuse-kernel+owner@opensuse.org
From: Matthew Garrett <matthew.garrett@nebula.com> Git-commit: 68d929862e29a8b52a7f2f2f86a0600423b093cd Patch-mainline: v3.9-rc2 References: bnc#808680 Target: openSUSE 12.3 UEFI variables are typically stored in flash. For various reasons, avaiable space is typically not reclaimed immediately upon the deletion of a variable - instead, the system will garbage collect during initialisation after a reboot. Some systems appear to handle this garbage collection extremely poorly, failing if more than 50% of the system flash is in use. This can result in the machine refusing to boot. The safest thing to do for the moment is to forbid writes if they'd end up using more than half of the storage space. We can make this more finegrained later if we come up with a method for identifying the broken machines. Signed-off-by: Matthew Garrett <matthew.garrett@nebula.com> Cc: Josh Boyer <jwboyer@redhat.com> Cc: <stable@vger.kernel.org> Signed-off-by: Matt Fleming <matt.fleming@intel.com> Acked-by: Lee, Chun-Yi <jlee@suse.com> --- drivers/firmware/efivars.c | 106 +++++++++++++++++++++++++++++++++------------ 1 file changed, 79 insertions(+), 27 deletions(-) --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -426,6 +426,44 @@ get_var_data(struct efivars *efivars, st 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) { @@ -547,11 +585,16 @@ efivar_store_raw(struct efivar_entry *en } spin_lock_irq(&efivars->lock); - status = efivars->ops->set_variable(new_var->VariableName, - &new_var->VendorGuid, - new_var->Attributes, - new_var->DataSize, - new_var->Data); + + 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_irq(&efivars->lock); @@ -702,8 +745,7 @@ static ssize_t efivarfs_file_write(struc 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)) @@ -722,28 +764,18 @@ static ssize_t efivarfs_file_write(struc * amounts of memory. Pick a default size of 64K if * QueryVariableInfo() isn't supported by the firmware. */ - spin_lock_irq(&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_irq(&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; @@ -765,6 +797,19 @@ static ssize_t efivarfs_file_write(struc */ 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, attributes, datasize, @@ -1326,7 +1371,6 @@ static int efi_pstore_write(enum pstore_ struct efivars *efivars = psi->data; struct efivar_entry *entry, *found = NULL; int i, ret = 0; - u64 storage_space, remaining_space, max_variable_size; efi_status_t status = EFI_NOT_FOUND; unsigned long flags; @@ -1349,11 +1393,11 @@ static int efi_pstore_write(enum pstore_ * 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) { + + 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; @@ -1498,6 +1542,14 @@ static ssize_t efivar_create(struct file 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, -- To unsubscribe, e-mail: opensuse-kernel+unsubscribe@opensuse.org To contact the owner, e-mail: opensuse-kernel+owner@opensuse.org
From: Matt Fleming <matt.fleming@intel.com> Git-commit: 123abd76edf56c02a76b46d3d673897177ef067b Patch-mainline: v3.9-rc2 References: bnc#808680 Target: openSUSE 12.3 Stricter validation was introduced with commit da27a24383b2b ("efivarfs: guid part of filenames are case-insensitive") and commit 47f531e8ba3b ("efivarfs: Validate filenames much more aggressively"), which is necessary for the guid portion of efivarfs filenames, but we don't need to be so strict with the first part, the variable name. The UEFI specification doesn't impose any constraints on variable names other than they be a NULL-terminated string. The above commits caused a regression that resulted in users seeing the following message, $ sudo mount -v /sys/firmware/efi/efivars mount: Cannot allocate memory whenever pstore EFI variables were present in the variable store, since their variable names failed to pass the following check, /* GUID should be right after the first '-' */ if (s - 1 != strchr(str, '-')) as a typical pstore filename is of the form, dump-type0-10-1-<guid>. The fix is trivial since the guid portion of the filename is GUID_LEN bytes, we can use (len - GUID_LEN) to ensure the '-' character is where we expect it to be. (The bogus ENOMEM error value will be fixed in a separate patch.) Reported-by: Joseph Yasi <joe.yasi@gmail.com> Tested-by: Joseph Yasi <joe.yasi@gmail.com> Reported-by: Lingzhu Xiang <lxiang@redhat.com> Cc: Josh Boyer <jwboyer@redhat.com> Cc: Jeremy Kerr <jk@ozlabs.org> Cc: Matthew Garrett <mjg59@srcf.ucam.org> Cc: <stable@vger.kernel.org> # v3.8 Signed-off-by: Matt Fleming <matt.fleming@intel.com> Acked-by: Lee, Chun-Yi <jlee@suse.com> --- drivers/firmware/efivars.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -975,8 +975,8 @@ static bool efivarfs_valid_name(const ch if (len < GUID_LEN + 2) return false; - /* GUID should be right after the first '-' */ - if (s - 1 != strchr(str, '-')) + /* GUID must be preceded by a '-' */ + if (*(s - 1) != '-') return false; /* -- To unsubscribe, e-mail: opensuse-kernel+unsubscribe@opensuse.org To contact the owner, e-mail: opensuse-kernel+owner@opensuse.org
From: Matt Fleming <matt.fleming@intel.com> Git-commit: feff5dc4f98330d8152b521acc2e18c16712e6c8 Patch-mainline: v3.9-rc2 References: bnc#808680 Target: openSUSE 12.3 Joseph was hitting a failure case when mounting efivarfs which resulted in an incorrect error message, $ sudo mount -v /sys/firmware/efi/efivars mount: Cannot allocate memory triggered when efivarfs_valid_name() returned -EINVAL. Make sure we pass accurate return values up the stack if efivarfs_fill_super() fails to build inodes for EFI variables. Reported-by: Joseph Yasi <joe.yasi@gmail.com> Reported-by: Lingzhu Xiang <lxiang@redhat.com> Cc: Josh Boyer <jwboyer@redhat.com> Cc: Jeremy Kerr <jk@ozlabs.org> Cc: Matthew Garrett <mjg59@srcf.ucam.org> Cc: <stable@vger.kernel.org> # v3.8 Signed-off-by: Matt Fleming <matt.fleming@intel.com> Acked-by: Lee, Chun-Yi <jlee@suse.com> --- drivers/firmware/efivars.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -1164,15 +1164,22 @@ static struct dentry_operations efivarfs 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); - if (efivarfs_d_hash(NULL, NULL, &q)) - return NULL; + err = efivarfs_d_hash(NULL, NULL, &q); + if (err) + return ERR_PTR(err); + + d = d_alloc(parent, &q); + if (d) + return d; - return d_alloc(parent, &q); + return ERR_PTR(-ENOMEM); } static int efivarfs_fill_super(struct super_block *sb, void *data, int silent) @@ -1182,6 +1189,7 @@ static int efivarfs_fill_super(struct su struct efivar_entry *entry, *n; struct efivars *efivars = &__efivars; char *name; + int err = -ENOMEM; efivarfs_sb = sb; @@ -1232,8 +1240,10 @@ static int efivarfs_fill_super(struct su goto fail_name; dentry = efivarfs_alloc_dentry(root, name); - if (!dentry) + if (IS_ERR(dentry)) { + err = PTR_ERR(dentry); goto fail_inode; + } /* copied by the above to local storage in the dentry. */ kfree(name); @@ -1260,7 +1270,7 @@ fail_inode: fail_name: kfree(name); fail: - return -ENOMEM; + return err; } static struct dentry *efivarfs_mount(struct file_system_type *fs_type, -- To unsubscribe, e-mail: opensuse-kernel+unsubscribe@opensuse.org To contact the owner, e-mail: opensuse-kernel+owner@opensuse.org
From: Matt Fleming <matt.fleming@intel.com> Git-commit: ec50bd32f1672d38ddce10fb1841cbfda89cfe9a Patch-mainline: v3.9-rc4 References: bnc#808680 Target: openSUSE 12.3 Some buggy firmware implementations return a string length from GetNextVariableName() that is actually larger than the string in 'variable_name', as Michael Schroeder writes,
On HP z220 system (firmware version 1.54), some EFI variables are incorrectly named :
ls -d /sys/firmware/efi/vars/*8be4d* | grep -v -- -8be returns /sys/firmware/efi/vars/dbxDefault-pport8be4df61-93ca-11d2-aa0d-00e098032b8c /sys/firmware/efi/vars/KEKDefault-pport8be4df61-93ca-11d2-aa0d-00e098032b8c /sys/firmware/efi/vars/SecureBoot-pport8be4df61-93ca-11d2-aa0d-00e098032b8c /sys/firmware/efi/vars/SetupMode-Information8be4df61-93ca-11d2-aa0d-00e098032b8c
Since 'variable_name' is a string, we can validate its size by searching for the terminating NULL character. Reported-by: Frederic Crozat <fcrozat@suse.com> Cc: Matthew Garrett <mjg59@srcf.ucam.org> Cc: Josh Boyer <jwboyer@redhat.com> Cc: Michael Schroeder <mls@suse.com> Cc: Lee, Chun-Yi <jlee@suse.com> Cc: Lingzhu Xiang <lxiang@redhat.com> Signed-off-by: Matt Fleming <matt.fleming@intel.com> Acked-by: Lee, Chun-Yi <jlee@suse.com> --- drivers/firmware/efivars.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -1911,6 +1911,33 @@ void unregister_efivars(struct efivars * } EXPORT_SYMBOL_GPL(unregister_efivars); +/* + * Sanity check size of a variable name. + */ +static unsigned long sanity_check_size(efi_char16_t *variable_name, + unsigned long variable_name_size) +{ + unsigned long len; + efi_char16_t c; + + /* + * The variable name is, by definition, a NULL-terminated + * string, so make absolutely sure that variable_name_size is + * the value we expect it to be. If not, return the real size. + */ + for (len = 2; len <= variable_name_size; len += sizeof(c)) { + c = variable_name[(len / sizeof(c)) - 1]; + if (!c) + break; + } + + + if (len != variable_name_size) + printk(KERN_WARNING "efivars: bogus variable_name_size: %lu %lu\n", len, variable_name_size); + + return min(len, variable_name_size); +} + int register_efivars(struct efivars *efivars, const struct efivar_operations *ops, struct kobject *parent_kobj) @@ -1957,8 +1984,11 @@ int register_efivars(struct efivars *efi status = ops->get_next_variable(&variable_name_size, variable_name, &vendor_guid); + switch (status) { case EFI_SUCCESS: + variable_name_size = sanity_check_size(variable_name, + variable_name_size); efivar_create_sysfs_entry(efivars, variable_name_size, variable_name, -- To unsubscribe, e-mail: opensuse-kernel+unsubscribe@opensuse.org To contact the owner, e-mail: opensuse-kernel+owner@opensuse.org
participants (1)
-
Lee, Chun-Yi