Loading fs/efivarfs/file.c +50 −9 Original line number Diff line number Diff line Loading @@ -36,28 +36,41 @@ static ssize_t efivarfs_file_write(struct file *file, if (IS_ERR(data)) return PTR_ERR(data); inode_lock(inode); if (var->removed) { /* * file got removed; don't allow a set. Caused by an * unsuccessful create or successful delete write * racing with us. */ bytes = -EIO; goto out; } bytes = efivar_entry_set_get_size(var, attributes, &datasize, data, &set); if (!set && bytes) { if (!set) { if (bytes == -ENOENT) bytes = -EIO; goto out; } if (bytes == -ENOENT) { drop_nlink(inode); d_delete(file->f_path.dentry); dput(file->f_path.dentry); /* * zero size signals to release that the write deleted * the variable */ i_size_write(inode, 0); } else { inode_lock(inode); i_size_write(inode, datasize + sizeof(attributes)); inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); inode_unlock(inode); } bytes = count; out: inode_unlock(inode); kfree(data); return bytes; Loading Loading @@ -106,8 +119,36 @@ static ssize_t efivarfs_file_read(struct file *file, char __user *userbuf, return size; } static int efivarfs_file_release(struct inode *inode, struct file *file) { struct efivar_entry *var = inode->i_private; inode_lock(inode); var->removed = (--var->open_count == 0 && i_size_read(inode) == 0); inode_unlock(inode); if (var->removed) simple_recursive_removal(file->f_path.dentry, NULL); return 0; } static int efivarfs_file_open(struct inode *inode, struct file *file) { struct efivar_entry *entry = inode->i_private; file->private_data = entry; inode_lock(inode); entry->open_count++; inode_unlock(inode); return 0; } const struct file_operations efivarfs_file_operations = { .open = simple_open, .open = efivarfs_file_open, .read = efivarfs_file_read, .write = efivarfs_file_write, .release = efivarfs_file_release, }; fs/efivarfs/inode.c +13 −28 Original line number Diff line number Diff line Loading @@ -77,39 +77,34 @@ static bool efivarfs_valid_name(const char *str, int len) static int efivarfs_create(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode, bool excl) { struct efivarfs_fs_info *info = dir->i_sb->s_fs_info; struct inode *inode = NULL; struct efivar_entry *var; int namelen, i = 0, err = 0; bool is_removable = false; efi_guid_t vendor; if (!efivarfs_valid_name(dentry->d_name.name, dentry->d_name.len)) return -EINVAL; var = kzalloc(sizeof(struct efivar_entry), GFP_KERNEL); if (!var) return -ENOMEM; /* length of the variable name itself: remove GUID and separator */ namelen = dentry->d_name.len - EFI_VARIABLE_GUID_LEN - 1; err = guid_parse(dentry->d_name.name + namelen + 1, &var->var.VendorGuid); err = guid_parse(dentry->d_name.name + namelen + 1, &vendor); if (err) goto out; if (guid_equal(&var->var.VendorGuid, &LINUX_EFI_RANDOM_SEED_TABLE_GUID)) { err = -EPERM; goto out; } return err; if (guid_equal(&vendor, &LINUX_EFI_RANDOM_SEED_TABLE_GUID)) return -EPERM; if (efivar_variable_is_removable(var->var.VendorGuid, if (efivar_variable_is_removable(vendor, dentry->d_name.name, namelen)) is_removable = true; inode = efivarfs_get_inode(dir->i_sb, dir, mode, 0, is_removable); if (!inode) { err = -ENOMEM; goto out; } if (!inode) return -ENOMEM; var = efivar_entry(inode); var->var.VendorGuid = vendor; for (i = 0; i < namelen; i++) var->var.VariableName[i] = dentry->d_name.name[i]; Loading @@ -117,21 +112,11 @@ static int efivarfs_create(struct mnt_idmap *idmap, struct inode *dir, var->var.VariableName[i] = '\0'; inode->i_private = var; kmemleak_ignore(var); err = efivar_entry_add(var, &info->efivarfs_list); if (err) goto out; d_instantiate(dentry, inode); dget(dentry); out: if (err) { kfree(var); if (inode) iput(inode); } return err; return 0; } static int efivarfs_unlink(struct inode *dir, struct dentry *dentry) Loading fs/efivarfs/internal.h +13 −13 Original line number Diff line number Diff line Loading @@ -6,7 +6,6 @@ #ifndef EFIVAR_FS_INTERNAL_H #define EFIVAR_FS_INTERNAL_H #include <linux/list.h> #include <linux/efi.h> struct efivarfs_mount_opts { Loading @@ -16,7 +15,6 @@ struct efivarfs_mount_opts { struct efivarfs_fs_info { struct efivarfs_mount_opts mount_opts; struct list_head efivarfs_list; struct super_block *sb; struct notifier_block nb; }; Loading @@ -24,22 +22,23 @@ struct efivarfs_fs_info { struct efi_variable { efi_char16_t VariableName[EFI_VAR_NAME_LEN/sizeof(efi_char16_t)]; efi_guid_t VendorGuid; __u32 Attributes; }; struct efivar_entry { struct efi_variable var; struct list_head list; struct kobject kobj; struct inode vfs_inode; unsigned long open_count; bool removed; }; int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *, struct list_head *), void *data, struct list_head *head); static inline struct efivar_entry *efivar_entry(struct inode *inode) { return container_of(inode, struct efivar_entry, vfs_inode); } int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *), void *data); int efivar_entry_add(struct efivar_entry *entry, struct list_head *head); void __efivar_entry_add(struct efivar_entry *entry, struct list_head *head); void efivar_entry_remove(struct efivar_entry *entry); int efivar_entry_delete(struct efivar_entry *entry); int efivar_entry_size(struct efivar_entry *entry, unsigned long *size); Loading @@ -50,13 +49,14 @@ int efivar_entry_get(struct efivar_entry *entry, u32 *attributes, int efivar_entry_set_get_size(struct efivar_entry *entry, u32 attributes, unsigned long *size, void *data, bool *set); int efivar_entry_iter(int (*func)(struct efivar_entry *, void *), struct list_head *head, void *data); bool efivar_validate(efi_guid_t vendor, efi_char16_t *var_name, u8 *data, unsigned long data_size); bool efivar_variable_is_removable(efi_guid_t vendor, const char *name, size_t len); char *efivar_get_utf8name(const efi_char16_t *name16, efi_guid_t *vendor); bool efivarfs_variable_is_present(efi_char16_t *variable_name, efi_guid_t *vendor, void *data); extern const struct file_operations efivarfs_file_operations; extern const struct inode_operations efivarfs_dir_inode_operations; Loading fs/efivarfs/super.c +62 −44 Original line number Diff line number Diff line Loading @@ -39,9 +39,24 @@ static int efivarfs_ops_notifier(struct notifier_block *nb, unsigned long event, return NOTIFY_OK; } static void efivarfs_evict_inode(struct inode *inode) static struct inode *efivarfs_alloc_inode(struct super_block *sb) { clear_inode(inode); struct efivar_entry *entry = kzalloc(sizeof(*entry), GFP_KERNEL); if (!entry) return NULL; inode_init_once(&entry->vfs_inode); entry->removed = false; return &entry->vfs_inode; } static void efivarfs_free_inode(struct inode *inode) { struct efivar_entry *entry = efivar_entry(inode); kfree(entry); } static int efivarfs_show_options(struct seq_file *m, struct dentry *root) Loading Loading @@ -106,7 +121,8 @@ static int efivarfs_statfs(struct dentry *dentry, struct kstatfs *buf) static const struct super_operations efivarfs_ops = { .statfs = efivarfs_statfs, .drop_inode = generic_delete_inode, .evict_inode = efivarfs_evict_inode, .alloc_inode = efivarfs_alloc_inode, .free_inode = efivarfs_free_inode, .show_options = efivarfs_show_options, }; Loading Loading @@ -181,9 +197,37 @@ static struct dentry *efivarfs_alloc_dentry(struct dentry *parent, char *name) return ERR_PTR(-ENOMEM); } bool efivarfs_variable_is_present(efi_char16_t *variable_name, efi_guid_t *vendor, void *data) { char *name = efivar_get_utf8name(variable_name, vendor); struct super_block *sb = data; struct dentry *dentry; struct qstr qstr; if (!name) /* * If the allocation failed there'll already be an * error in the log (and likely a huge and growing * number of them since they system will be under * extreme memory pressure), so simply assume * collision for safety but don't add to the log * flood. */ return true; qstr.name = name; qstr.len = strlen(name); dentry = d_hash_and_lookup(sb->s_root, &qstr); kfree(name); if (!IS_ERR_OR_NULL(dentry)) dput(dentry); return dentry != NULL; } static int efivarfs_callback(efi_char16_t *name16, efi_guid_t vendor, unsigned long name_size, void *data, struct list_head *list) unsigned long name_size, void *data) { struct super_block *sb = (struct super_block *)data; struct efivar_entry *entry; Loading @@ -198,39 +242,26 @@ static int efivarfs_callback(efi_char16_t *name16, efi_guid_t vendor, if (guid_equal(&vendor, &LINUX_EFI_RANDOM_SEED_TABLE_GUID)) return 0; entry = kzalloc(sizeof(*entry), GFP_KERNEL); if (!entry) return err; memcpy(entry->var.VariableName, name16, name_size); memcpy(&(entry->var.VendorGuid), &vendor, sizeof(efi_guid_t)); len = ucs2_utf8size(entry->var.VariableName); /* name, plus '-', plus GUID, plus NUL*/ name = kmalloc(len + 1 + EFI_VARIABLE_GUID_LEN + 1, GFP_KERNEL); name = efivar_get_utf8name(name16, &vendor); if (!name) goto fail; return err; ucs2_as_utf8(name, entry->var.VariableName, len); /* length of the variable name itself: remove GUID and separator */ len = strlen(name) - EFI_VARIABLE_GUID_LEN - 1; if (efivar_variable_is_removable(entry->var.VendorGuid, name, len)) if (efivar_variable_is_removable(vendor, name, len)) is_removable = true; name[len] = '-'; efi_guid_to_str(&entry->var.VendorGuid, name + len + 1); name[len + EFI_VARIABLE_GUID_LEN+1] = '\0'; /* replace invalid slashes like kobject_set_name_vargs does for /sys/firmware/efi/vars. */ strreplace(name, '/', '!'); inode = efivarfs_get_inode(sb, d_inode(root), S_IFREG | 0644, 0, is_removable); if (!inode) goto fail_name; entry = efivar_entry(inode); memcpy(entry->var.VariableName, name16, name_size); memcpy(&(entry->var.VendorGuid), &vendor, sizeof(efi_guid_t)); dentry = efivarfs_alloc_dentry(root, name); if (IS_ERR(dentry)) { err = PTR_ERR(dentry); Loading @@ -238,14 +269,13 @@ static int efivarfs_callback(efi_char16_t *name16, efi_guid_t vendor, } __efivar_entry_get(entry, NULL, &size, NULL); __efivar_entry_add(entry, list); /* copied by the above to local storage in the dentry. */ kfree(name); inode_lock(inode); inode->i_private = entry; i_size_write(inode, size + sizeof(entry->var.Attributes)); i_size_write(inode, size + sizeof(__u32)); /* attributes + data */ inode_unlock(inode); d_add(dentry, inode); Loading @@ -255,16 +285,8 @@ static int efivarfs_callback(efi_char16_t *name16, efi_guid_t vendor, iput(inode); fail_name: kfree(name); fail: kfree(entry); return err; } static int efivarfs_destroy(struct efivar_entry *entry, void *data) { efivar_entry_remove(entry); kfree(entry); return 0; return err; } enum { Loading Loading @@ -336,7 +358,7 @@ static int efivarfs_fill_super(struct super_block *sb, struct fs_context *fc) if (err) return err; return efivar_init(efivarfs_callback, sb, &sfi->efivarfs_list); return efivar_init(efivarfs_callback, sb); } static int efivarfs_get_tree(struct fs_context *fc) Loading Loading @@ -371,8 +393,6 @@ static int efivarfs_init_fs_context(struct fs_context *fc) if (!sfi) return -ENOMEM; INIT_LIST_HEAD(&sfi->efivarfs_list); sfi->mount_opts.uid = GLOBAL_ROOT_UID; sfi->mount_opts.gid = GLOBAL_ROOT_GID; Loading @@ -388,8 +408,6 @@ static void efivarfs_kill_sb(struct super_block *sb) blocking_notifier_chain_unregister(&efivar_ops_nh, &sfi->nb); kill_litter_super(sb); /* Remove all entries and destroy */ efivar_entry_iter(efivarfs_destroy, &sfi->efivarfs_list, NULL); kfree(sfi); } Loading fs/efivarfs/vars.c +41 −138 Original line number Diff line number Diff line Loading @@ -225,6 +225,31 @@ variable_matches(const char *var_name, size_t len, const char *match_name, } } char * efivar_get_utf8name(const efi_char16_t *name16, efi_guid_t *vendor) { int len = ucs2_utf8size(name16); char *name; /* name, plus '-', plus GUID, plus NUL*/ name = kmalloc(len + 1 + EFI_VARIABLE_GUID_LEN + 1, GFP_KERNEL); if (!name) return NULL; ucs2_as_utf8(name, name16, len); name[len] = '-'; efi_guid_to_str(vendor, name + len + 1); name[len + EFI_VARIABLE_GUID_LEN+1] = '\0'; /* replace invalid slashes like kobject_set_name_vargs does for /sys/firmware/efi/vars. */ strreplace(name, '/', '!'); return name; } bool efivar_validate(efi_guid_t vendor, efi_char16_t *var_name, u8 *data, unsigned long data_size) Loading Loading @@ -288,28 +313,6 @@ efivar_variable_is_removable(efi_guid_t vendor, const char *var_name, return found; } static bool variable_is_present(efi_char16_t *variable_name, efi_guid_t *vendor, struct list_head *head) { struct efivar_entry *entry, *n; unsigned long strsize1, strsize2; bool found = false; strsize1 = ucs2_strsize(variable_name, EFI_VAR_NAME_LEN); list_for_each_entry_safe(entry, n, head, list) { strsize2 = ucs2_strsize(entry->var.VariableName, EFI_VAR_NAME_LEN); if (strsize1 == strsize2 && !memcmp(variable_name, &(entry->var.VariableName), strsize2) && !efi_guidcmp(entry->var.VendorGuid, *vendor)) { found = true; break; } } return found; } /* * Returns the size of variable_name, in bytes, including the * terminating NULL character, or variable_name_size if no NULL Loading Loading @@ -361,16 +364,14 @@ static void dup_variable_bug(efi_char16_t *str16, efi_guid_t *vendor_guid, * efivar_init - build the initial list of EFI variables * @func: callback function to invoke for every variable * @data: function-specific data to pass to @func * @head: initialised head of variable list * * Get every EFI variable from the firmware and invoke @func. @func * should call efivar_entry_add() to build the list of variables. * should populate the initial dentry and inode tree. * * Returns 0 on success, or a kernel error code on failure. */ int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *, struct list_head *), void *data, struct list_head *head) int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *), void *data) { unsigned long variable_name_size = 512; efi_char16_t *variable_name; Loading Loading @@ -414,14 +415,14 @@ int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *, * we'll ever see a different variable name, * and may end up looping here forever. */ if (variable_is_present(variable_name, &vendor_guid, head)) { if (efivarfs_variable_is_present(variable_name, &vendor_guid, data)) { dup_variable_bug(variable_name, &vendor_guid, variable_name_size); status = EFI_NOT_FOUND; } else { err = func(variable_name, vendor_guid, variable_name_size, data, head); variable_name_size, data); if (err) status = EFI_NOT_FOUND; } Loading Loading @@ -453,70 +454,12 @@ int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *, } /** * efivar_entry_add - add entry to variable list * @entry: entry to add to list * @head: list head * * Returns 0 on success, or a kernel error code on failure. */ int efivar_entry_add(struct efivar_entry *entry, struct list_head *head) { int err; err = efivar_lock(); if (err) return err; list_add(&entry->list, head); efivar_unlock(); return 0; } /** * __efivar_entry_add - add entry to variable list * @entry: entry to add to list * @head: list head */ void __efivar_entry_add(struct efivar_entry *entry, struct list_head *head) { list_add(&entry->list, head); } /** * efivar_entry_remove - remove entry from variable list * @entry: entry to remove from list * * Returns 0 on success, or a kernel error code on failure. */ void efivar_entry_remove(struct efivar_entry *entry) { list_del(&entry->list); } /* * efivar_entry_list_del_unlock - remove entry from variable list * @entry: entry to remove * * Remove @entry from the variable list and release the list lock. * * NOTE: slightly weird locking semantics here - we expect to be * called with the efivars lock already held, and we release it before * returning. This is because this function is usually called after * set_variable() while the lock is still held. */ static void efivar_entry_list_del_unlock(struct efivar_entry *entry) { list_del(&entry->list); efivar_unlock(); } /** * efivar_entry_delete - delete variable and remove entry from list * efivar_entry_delete - delete variable * @entry: entry containing variable to delete * * Delete the variable from the firmware and remove @entry from the * variable list. It is the caller's responsibility to free @entry * once we return. * Delete the variable from the firmware. It is the caller's * responsibility to free @entry (by deleting the dentry/inode) once * we return. * * Returns 0 on success, -EINTR if we can't grab the semaphore, * converted EFI status code if set_variable() fails. Loading @@ -533,12 +476,10 @@ int efivar_entry_delete(struct efivar_entry *entry) status = efivar_set_variable_locked(entry->var.VariableName, &entry->var.VendorGuid, 0, 0, NULL, false); if (!(status == EFI_SUCCESS || status == EFI_NOT_FOUND)) { efivar_unlock(); if (!(status == EFI_SUCCESS || status == EFI_NOT_FOUND)) return efi_status_to_err(status); } efivar_entry_list_del_unlock(entry); return 0; } Loading Loading @@ -632,7 +573,7 @@ int efivar_entry_get(struct efivar_entry *entry, u32 *attributes, * get_variable() fail. * * If the EFI variable does not exist when calling set_variable() * (EFI_NOT_FOUND), @entry is removed from the variable list. * (EFI_NOT_FOUND). */ int efivar_entry_set_get_size(struct efivar_entry *entry, u32 attributes, unsigned long *size, void *data, bool *set) Loading @@ -648,9 +589,8 @@ int efivar_entry_set_get_size(struct efivar_entry *entry, u32 attributes, return -EINVAL; /* * 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). * The lock here protects the get_variable call and the * conditional set_variable call */ err = efivar_lock(); if (err) Loading @@ -676,9 +616,6 @@ int efivar_entry_set_get_size(struct efivar_entry *entry, u32 attributes, &entry->var.VendorGuid, NULL, size, NULL); if (status == EFI_NOT_FOUND) efivar_entry_list_del_unlock(entry); else efivar_unlock(); if (status && status != EFI_BUFFER_TOO_SMALL) Loading @@ -691,37 +628,3 @@ int efivar_entry_set_get_size(struct efivar_entry *entry, u32 attributes, return err; } /** * efivar_entry_iter - iterate over variable list * @func: callback function * @head: head of variable list * @data: function-specific data to pass to callback * * Iterate over the list of EFI variables and call @func with every * entry on the list. It is safe for @func to remove entries in the * list via efivar_entry_delete() while iterating. * * Some notes for the callback function: * - a non-zero return value indicates an error and terminates the loop * - @func is called from atomic context */ int efivar_entry_iter(int (*func)(struct efivar_entry *, void *), struct list_head *head, void *data) { struct efivar_entry *entry, *n; int err = 0; err = efivar_lock(); if (err) return err; list_for_each_entry_safe(entry, n, head, list) { err = func(entry, data); if (err) break; } efivar_unlock(); return err; } Loading
fs/efivarfs/file.c +50 −9 Original line number Diff line number Diff line Loading @@ -36,28 +36,41 @@ static ssize_t efivarfs_file_write(struct file *file, if (IS_ERR(data)) return PTR_ERR(data); inode_lock(inode); if (var->removed) { /* * file got removed; don't allow a set. Caused by an * unsuccessful create or successful delete write * racing with us. */ bytes = -EIO; goto out; } bytes = efivar_entry_set_get_size(var, attributes, &datasize, data, &set); if (!set && bytes) { if (!set) { if (bytes == -ENOENT) bytes = -EIO; goto out; } if (bytes == -ENOENT) { drop_nlink(inode); d_delete(file->f_path.dentry); dput(file->f_path.dentry); /* * zero size signals to release that the write deleted * the variable */ i_size_write(inode, 0); } else { inode_lock(inode); i_size_write(inode, datasize + sizeof(attributes)); inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); inode_unlock(inode); } bytes = count; out: inode_unlock(inode); kfree(data); return bytes; Loading Loading @@ -106,8 +119,36 @@ static ssize_t efivarfs_file_read(struct file *file, char __user *userbuf, return size; } static int efivarfs_file_release(struct inode *inode, struct file *file) { struct efivar_entry *var = inode->i_private; inode_lock(inode); var->removed = (--var->open_count == 0 && i_size_read(inode) == 0); inode_unlock(inode); if (var->removed) simple_recursive_removal(file->f_path.dentry, NULL); return 0; } static int efivarfs_file_open(struct inode *inode, struct file *file) { struct efivar_entry *entry = inode->i_private; file->private_data = entry; inode_lock(inode); entry->open_count++; inode_unlock(inode); return 0; } const struct file_operations efivarfs_file_operations = { .open = simple_open, .open = efivarfs_file_open, .read = efivarfs_file_read, .write = efivarfs_file_write, .release = efivarfs_file_release, };
fs/efivarfs/inode.c +13 −28 Original line number Diff line number Diff line Loading @@ -77,39 +77,34 @@ static bool efivarfs_valid_name(const char *str, int len) static int efivarfs_create(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode, bool excl) { struct efivarfs_fs_info *info = dir->i_sb->s_fs_info; struct inode *inode = NULL; struct efivar_entry *var; int namelen, i = 0, err = 0; bool is_removable = false; efi_guid_t vendor; if (!efivarfs_valid_name(dentry->d_name.name, dentry->d_name.len)) return -EINVAL; var = kzalloc(sizeof(struct efivar_entry), GFP_KERNEL); if (!var) return -ENOMEM; /* length of the variable name itself: remove GUID and separator */ namelen = dentry->d_name.len - EFI_VARIABLE_GUID_LEN - 1; err = guid_parse(dentry->d_name.name + namelen + 1, &var->var.VendorGuid); err = guid_parse(dentry->d_name.name + namelen + 1, &vendor); if (err) goto out; if (guid_equal(&var->var.VendorGuid, &LINUX_EFI_RANDOM_SEED_TABLE_GUID)) { err = -EPERM; goto out; } return err; if (guid_equal(&vendor, &LINUX_EFI_RANDOM_SEED_TABLE_GUID)) return -EPERM; if (efivar_variable_is_removable(var->var.VendorGuid, if (efivar_variable_is_removable(vendor, dentry->d_name.name, namelen)) is_removable = true; inode = efivarfs_get_inode(dir->i_sb, dir, mode, 0, is_removable); if (!inode) { err = -ENOMEM; goto out; } if (!inode) return -ENOMEM; var = efivar_entry(inode); var->var.VendorGuid = vendor; for (i = 0; i < namelen; i++) var->var.VariableName[i] = dentry->d_name.name[i]; Loading @@ -117,21 +112,11 @@ static int efivarfs_create(struct mnt_idmap *idmap, struct inode *dir, var->var.VariableName[i] = '\0'; inode->i_private = var; kmemleak_ignore(var); err = efivar_entry_add(var, &info->efivarfs_list); if (err) goto out; d_instantiate(dentry, inode); dget(dentry); out: if (err) { kfree(var); if (inode) iput(inode); } return err; return 0; } static int efivarfs_unlink(struct inode *dir, struct dentry *dentry) Loading
fs/efivarfs/internal.h +13 −13 Original line number Diff line number Diff line Loading @@ -6,7 +6,6 @@ #ifndef EFIVAR_FS_INTERNAL_H #define EFIVAR_FS_INTERNAL_H #include <linux/list.h> #include <linux/efi.h> struct efivarfs_mount_opts { Loading @@ -16,7 +15,6 @@ struct efivarfs_mount_opts { struct efivarfs_fs_info { struct efivarfs_mount_opts mount_opts; struct list_head efivarfs_list; struct super_block *sb; struct notifier_block nb; }; Loading @@ -24,22 +22,23 @@ struct efivarfs_fs_info { struct efi_variable { efi_char16_t VariableName[EFI_VAR_NAME_LEN/sizeof(efi_char16_t)]; efi_guid_t VendorGuid; __u32 Attributes; }; struct efivar_entry { struct efi_variable var; struct list_head list; struct kobject kobj; struct inode vfs_inode; unsigned long open_count; bool removed; }; int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *, struct list_head *), void *data, struct list_head *head); static inline struct efivar_entry *efivar_entry(struct inode *inode) { return container_of(inode, struct efivar_entry, vfs_inode); } int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *), void *data); int efivar_entry_add(struct efivar_entry *entry, struct list_head *head); void __efivar_entry_add(struct efivar_entry *entry, struct list_head *head); void efivar_entry_remove(struct efivar_entry *entry); int efivar_entry_delete(struct efivar_entry *entry); int efivar_entry_size(struct efivar_entry *entry, unsigned long *size); Loading @@ -50,13 +49,14 @@ int efivar_entry_get(struct efivar_entry *entry, u32 *attributes, int efivar_entry_set_get_size(struct efivar_entry *entry, u32 attributes, unsigned long *size, void *data, bool *set); int efivar_entry_iter(int (*func)(struct efivar_entry *, void *), struct list_head *head, void *data); bool efivar_validate(efi_guid_t vendor, efi_char16_t *var_name, u8 *data, unsigned long data_size); bool efivar_variable_is_removable(efi_guid_t vendor, const char *name, size_t len); char *efivar_get_utf8name(const efi_char16_t *name16, efi_guid_t *vendor); bool efivarfs_variable_is_present(efi_char16_t *variable_name, efi_guid_t *vendor, void *data); extern const struct file_operations efivarfs_file_operations; extern const struct inode_operations efivarfs_dir_inode_operations; Loading
fs/efivarfs/super.c +62 −44 Original line number Diff line number Diff line Loading @@ -39,9 +39,24 @@ static int efivarfs_ops_notifier(struct notifier_block *nb, unsigned long event, return NOTIFY_OK; } static void efivarfs_evict_inode(struct inode *inode) static struct inode *efivarfs_alloc_inode(struct super_block *sb) { clear_inode(inode); struct efivar_entry *entry = kzalloc(sizeof(*entry), GFP_KERNEL); if (!entry) return NULL; inode_init_once(&entry->vfs_inode); entry->removed = false; return &entry->vfs_inode; } static void efivarfs_free_inode(struct inode *inode) { struct efivar_entry *entry = efivar_entry(inode); kfree(entry); } static int efivarfs_show_options(struct seq_file *m, struct dentry *root) Loading Loading @@ -106,7 +121,8 @@ static int efivarfs_statfs(struct dentry *dentry, struct kstatfs *buf) static const struct super_operations efivarfs_ops = { .statfs = efivarfs_statfs, .drop_inode = generic_delete_inode, .evict_inode = efivarfs_evict_inode, .alloc_inode = efivarfs_alloc_inode, .free_inode = efivarfs_free_inode, .show_options = efivarfs_show_options, }; Loading Loading @@ -181,9 +197,37 @@ static struct dentry *efivarfs_alloc_dentry(struct dentry *parent, char *name) return ERR_PTR(-ENOMEM); } bool efivarfs_variable_is_present(efi_char16_t *variable_name, efi_guid_t *vendor, void *data) { char *name = efivar_get_utf8name(variable_name, vendor); struct super_block *sb = data; struct dentry *dentry; struct qstr qstr; if (!name) /* * If the allocation failed there'll already be an * error in the log (and likely a huge and growing * number of them since they system will be under * extreme memory pressure), so simply assume * collision for safety but don't add to the log * flood. */ return true; qstr.name = name; qstr.len = strlen(name); dentry = d_hash_and_lookup(sb->s_root, &qstr); kfree(name); if (!IS_ERR_OR_NULL(dentry)) dput(dentry); return dentry != NULL; } static int efivarfs_callback(efi_char16_t *name16, efi_guid_t vendor, unsigned long name_size, void *data, struct list_head *list) unsigned long name_size, void *data) { struct super_block *sb = (struct super_block *)data; struct efivar_entry *entry; Loading @@ -198,39 +242,26 @@ static int efivarfs_callback(efi_char16_t *name16, efi_guid_t vendor, if (guid_equal(&vendor, &LINUX_EFI_RANDOM_SEED_TABLE_GUID)) return 0; entry = kzalloc(sizeof(*entry), GFP_KERNEL); if (!entry) return err; memcpy(entry->var.VariableName, name16, name_size); memcpy(&(entry->var.VendorGuid), &vendor, sizeof(efi_guid_t)); len = ucs2_utf8size(entry->var.VariableName); /* name, plus '-', plus GUID, plus NUL*/ name = kmalloc(len + 1 + EFI_VARIABLE_GUID_LEN + 1, GFP_KERNEL); name = efivar_get_utf8name(name16, &vendor); if (!name) goto fail; return err; ucs2_as_utf8(name, entry->var.VariableName, len); /* length of the variable name itself: remove GUID and separator */ len = strlen(name) - EFI_VARIABLE_GUID_LEN - 1; if (efivar_variable_is_removable(entry->var.VendorGuid, name, len)) if (efivar_variable_is_removable(vendor, name, len)) is_removable = true; name[len] = '-'; efi_guid_to_str(&entry->var.VendorGuid, name + len + 1); name[len + EFI_VARIABLE_GUID_LEN+1] = '\0'; /* replace invalid slashes like kobject_set_name_vargs does for /sys/firmware/efi/vars. */ strreplace(name, '/', '!'); inode = efivarfs_get_inode(sb, d_inode(root), S_IFREG | 0644, 0, is_removable); if (!inode) goto fail_name; entry = efivar_entry(inode); memcpy(entry->var.VariableName, name16, name_size); memcpy(&(entry->var.VendorGuid), &vendor, sizeof(efi_guid_t)); dentry = efivarfs_alloc_dentry(root, name); if (IS_ERR(dentry)) { err = PTR_ERR(dentry); Loading @@ -238,14 +269,13 @@ static int efivarfs_callback(efi_char16_t *name16, efi_guid_t vendor, } __efivar_entry_get(entry, NULL, &size, NULL); __efivar_entry_add(entry, list); /* copied by the above to local storage in the dentry. */ kfree(name); inode_lock(inode); inode->i_private = entry; i_size_write(inode, size + sizeof(entry->var.Attributes)); i_size_write(inode, size + sizeof(__u32)); /* attributes + data */ inode_unlock(inode); d_add(dentry, inode); Loading @@ -255,16 +285,8 @@ static int efivarfs_callback(efi_char16_t *name16, efi_guid_t vendor, iput(inode); fail_name: kfree(name); fail: kfree(entry); return err; } static int efivarfs_destroy(struct efivar_entry *entry, void *data) { efivar_entry_remove(entry); kfree(entry); return 0; return err; } enum { Loading Loading @@ -336,7 +358,7 @@ static int efivarfs_fill_super(struct super_block *sb, struct fs_context *fc) if (err) return err; return efivar_init(efivarfs_callback, sb, &sfi->efivarfs_list); return efivar_init(efivarfs_callback, sb); } static int efivarfs_get_tree(struct fs_context *fc) Loading Loading @@ -371,8 +393,6 @@ static int efivarfs_init_fs_context(struct fs_context *fc) if (!sfi) return -ENOMEM; INIT_LIST_HEAD(&sfi->efivarfs_list); sfi->mount_opts.uid = GLOBAL_ROOT_UID; sfi->mount_opts.gid = GLOBAL_ROOT_GID; Loading @@ -388,8 +408,6 @@ static void efivarfs_kill_sb(struct super_block *sb) blocking_notifier_chain_unregister(&efivar_ops_nh, &sfi->nb); kill_litter_super(sb); /* Remove all entries and destroy */ efivar_entry_iter(efivarfs_destroy, &sfi->efivarfs_list, NULL); kfree(sfi); } Loading
fs/efivarfs/vars.c +41 −138 Original line number Diff line number Diff line Loading @@ -225,6 +225,31 @@ variable_matches(const char *var_name, size_t len, const char *match_name, } } char * efivar_get_utf8name(const efi_char16_t *name16, efi_guid_t *vendor) { int len = ucs2_utf8size(name16); char *name; /* name, plus '-', plus GUID, plus NUL*/ name = kmalloc(len + 1 + EFI_VARIABLE_GUID_LEN + 1, GFP_KERNEL); if (!name) return NULL; ucs2_as_utf8(name, name16, len); name[len] = '-'; efi_guid_to_str(vendor, name + len + 1); name[len + EFI_VARIABLE_GUID_LEN+1] = '\0'; /* replace invalid slashes like kobject_set_name_vargs does for /sys/firmware/efi/vars. */ strreplace(name, '/', '!'); return name; } bool efivar_validate(efi_guid_t vendor, efi_char16_t *var_name, u8 *data, unsigned long data_size) Loading Loading @@ -288,28 +313,6 @@ efivar_variable_is_removable(efi_guid_t vendor, const char *var_name, return found; } static bool variable_is_present(efi_char16_t *variable_name, efi_guid_t *vendor, struct list_head *head) { struct efivar_entry *entry, *n; unsigned long strsize1, strsize2; bool found = false; strsize1 = ucs2_strsize(variable_name, EFI_VAR_NAME_LEN); list_for_each_entry_safe(entry, n, head, list) { strsize2 = ucs2_strsize(entry->var.VariableName, EFI_VAR_NAME_LEN); if (strsize1 == strsize2 && !memcmp(variable_name, &(entry->var.VariableName), strsize2) && !efi_guidcmp(entry->var.VendorGuid, *vendor)) { found = true; break; } } return found; } /* * Returns the size of variable_name, in bytes, including the * terminating NULL character, or variable_name_size if no NULL Loading Loading @@ -361,16 +364,14 @@ static void dup_variable_bug(efi_char16_t *str16, efi_guid_t *vendor_guid, * efivar_init - build the initial list of EFI variables * @func: callback function to invoke for every variable * @data: function-specific data to pass to @func * @head: initialised head of variable list * * Get every EFI variable from the firmware and invoke @func. @func * should call efivar_entry_add() to build the list of variables. * should populate the initial dentry and inode tree. * * Returns 0 on success, or a kernel error code on failure. */ int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *, struct list_head *), void *data, struct list_head *head) int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *), void *data) { unsigned long variable_name_size = 512; efi_char16_t *variable_name; Loading Loading @@ -414,14 +415,14 @@ int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *, * we'll ever see a different variable name, * and may end up looping here forever. */ if (variable_is_present(variable_name, &vendor_guid, head)) { if (efivarfs_variable_is_present(variable_name, &vendor_guid, data)) { dup_variable_bug(variable_name, &vendor_guid, variable_name_size); status = EFI_NOT_FOUND; } else { err = func(variable_name, vendor_guid, variable_name_size, data, head); variable_name_size, data); if (err) status = EFI_NOT_FOUND; } Loading Loading @@ -453,70 +454,12 @@ int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *, } /** * efivar_entry_add - add entry to variable list * @entry: entry to add to list * @head: list head * * Returns 0 on success, or a kernel error code on failure. */ int efivar_entry_add(struct efivar_entry *entry, struct list_head *head) { int err; err = efivar_lock(); if (err) return err; list_add(&entry->list, head); efivar_unlock(); return 0; } /** * __efivar_entry_add - add entry to variable list * @entry: entry to add to list * @head: list head */ void __efivar_entry_add(struct efivar_entry *entry, struct list_head *head) { list_add(&entry->list, head); } /** * efivar_entry_remove - remove entry from variable list * @entry: entry to remove from list * * Returns 0 on success, or a kernel error code on failure. */ void efivar_entry_remove(struct efivar_entry *entry) { list_del(&entry->list); } /* * efivar_entry_list_del_unlock - remove entry from variable list * @entry: entry to remove * * Remove @entry from the variable list and release the list lock. * * NOTE: slightly weird locking semantics here - we expect to be * called with the efivars lock already held, and we release it before * returning. This is because this function is usually called after * set_variable() while the lock is still held. */ static void efivar_entry_list_del_unlock(struct efivar_entry *entry) { list_del(&entry->list); efivar_unlock(); } /** * efivar_entry_delete - delete variable and remove entry from list * efivar_entry_delete - delete variable * @entry: entry containing variable to delete * * Delete the variable from the firmware and remove @entry from the * variable list. It is the caller's responsibility to free @entry * once we return. * Delete the variable from the firmware. It is the caller's * responsibility to free @entry (by deleting the dentry/inode) once * we return. * * Returns 0 on success, -EINTR if we can't grab the semaphore, * converted EFI status code if set_variable() fails. Loading @@ -533,12 +476,10 @@ int efivar_entry_delete(struct efivar_entry *entry) status = efivar_set_variable_locked(entry->var.VariableName, &entry->var.VendorGuid, 0, 0, NULL, false); if (!(status == EFI_SUCCESS || status == EFI_NOT_FOUND)) { efivar_unlock(); if (!(status == EFI_SUCCESS || status == EFI_NOT_FOUND)) return efi_status_to_err(status); } efivar_entry_list_del_unlock(entry); return 0; } Loading Loading @@ -632,7 +573,7 @@ int efivar_entry_get(struct efivar_entry *entry, u32 *attributes, * get_variable() fail. * * If the EFI variable does not exist when calling set_variable() * (EFI_NOT_FOUND), @entry is removed from the variable list. * (EFI_NOT_FOUND). */ int efivar_entry_set_get_size(struct efivar_entry *entry, u32 attributes, unsigned long *size, void *data, bool *set) Loading @@ -648,9 +589,8 @@ int efivar_entry_set_get_size(struct efivar_entry *entry, u32 attributes, return -EINVAL; /* * 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). * The lock here protects the get_variable call and the * conditional set_variable call */ err = efivar_lock(); if (err) Loading @@ -676,9 +616,6 @@ int efivar_entry_set_get_size(struct efivar_entry *entry, u32 attributes, &entry->var.VendorGuid, NULL, size, NULL); if (status == EFI_NOT_FOUND) efivar_entry_list_del_unlock(entry); else efivar_unlock(); if (status && status != EFI_BUFFER_TOO_SMALL) Loading @@ -691,37 +628,3 @@ int efivar_entry_set_get_size(struct efivar_entry *entry, u32 attributes, return err; } /** * efivar_entry_iter - iterate over variable list * @func: callback function * @head: head of variable list * @data: function-specific data to pass to callback * * Iterate over the list of EFI variables and call @func with every * entry on the list. It is safe for @func to remove entries in the * list via efivar_entry_delete() while iterating. * * Some notes for the callback function: * - a non-zero return value indicates an error and terminates the loop * - @func is called from atomic context */ int efivar_entry_iter(int (*func)(struct efivar_entry *, void *), struct list_head *head, void *data) { struct efivar_entry *entry, *n; int err = 0; err = efivar_lock(); if (err) return err; list_for_each_entry_safe(entry, n, head, list) { err = func(entry, data); if (err) break; } efivar_unlock(); return err; }