From: Ard Biesheuvel <ardb@xxxxxxxxxx> commit cdb46a8aefbf7fd36772bb206aaaf7e45d7cf8f6 upstream. syzbot reports issues with concurrent fsopen()/fsconfig() invocations on efivarfs, which are the result of the fact that the efivarfs list (which caches the names and GUIDs of existing EFI variables) is a global structure. In normal use, these issues are unlikely to trigger, even in the presence of multiple mounts of efivarfs, but the execution pattern used by the syzkaller reproducer may result in multiple instances of the superblock that share the global efivarfs list, and this causes list corruption when the list is reinitialized by one user while another is traversing it. So let's move the list head into the superblock s_fs_info field, so that it will never be shared between distinct instances of the superblock. In the common case, there will still be a single instance of this list, but in the artificial syzkaller case, no list corruption can occur any longer. Reported-by: syzbot+1902c359bfcaf39c46f2@xxxxxxxxxxxxxxxxxxxxxxxxx Signed-off-by: Ard Biesheuvel <ardb@xxxxxxxxxx> Reported-by: syzbot+246ea4feed277471958a@xxxxxxxxxxxxxxxxxxxxxxxxx Closes: https://syzkaller.appspot.com/bug?extid=246ea4feed277471958a Signed-off-by: Alexey Nepomnyashih <sdl@xxxxxxxx> --- fs/efivarfs/inode.c | 3 ++- fs/efivarfs/internal.h | 9 ++++++--- fs/efivarfs/super.c | 26 +++++++++++++++++--------- fs/efivarfs/vars.c | 5 +++-- 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/fs/efivarfs/inode.c b/fs/efivarfs/inode.c index b3dc7ff42400..a999351e6615 100644 --- a/fs/efivarfs/inode.c +++ b/fs/efivarfs/inode.c @@ -73,6 +73,7 @@ static bool efivarfs_valid_name(const char *str, int len) static int efivarfs_create(struct user_namespace *mnt_userns, 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; @@ -110,7 +111,7 @@ static int efivarfs_create(struct user_namespace *mnt_userns, struct inode *dir, inode->i_private = var; kmemleak_ignore(var); - err = efivar_entry_add(var, &efivarfs_list); + err = efivar_entry_add(var, &info->efivarfs_list); if (err) goto out; diff --git a/fs/efivarfs/internal.h b/fs/efivarfs/internal.h index dcb973d8736c..1745be16ff65 100644 --- a/fs/efivarfs/internal.h +++ b/fs/efivarfs/internal.h @@ -9,6 +9,10 @@ #include <linux/list.h> #include <linux/efi.h> +struct efivarfs_fs_info { + struct list_head efivarfs_list; +}; + struct efi_variable { efi_char16_t VariableName[EFI_VAR_NAME_LEN/sizeof(efi_char16_t)]; efi_guid_t VendorGuid; @@ -24,7 +28,8 @@ struct efivar_entry { struct kobject kobj; }; -int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *), +int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *, + struct list_head *), void *data, bool duplicates, struct list_head *head); int efivar_entry_add(struct efivar_entry *entry, struct list_head *head); @@ -54,6 +59,4 @@ extern struct inode *efivarfs_get_inode(struct super_block *sb, const struct inode *dir, int mode, dev_t dev, bool is_removable); -extern struct list_head efivarfs_list; - #endif /* EFIVAR_FS_INTERNAL_H */ diff --git a/fs/efivarfs/super.c b/fs/efivarfs/super.c index b8c4641ed152..a2c13af6c090 100644 --- a/fs/efivarfs/super.c +++ b/fs/efivarfs/super.c @@ -17,8 +17,6 @@ #include "internal.h" -LIST_HEAD(efivarfs_list); - static void efivarfs_evict_inode(struct inode *inode) { clear_inode(inode); @@ -102,7 +100,8 @@ static struct dentry *efivarfs_alloc_dentry(struct dentry *parent, char *name) } static int efivarfs_callback(efi_char16_t *name16, efi_guid_t vendor, - unsigned long name_size, void *data) + unsigned long name_size, void *data, + struct list_head *list) { struct super_block *sb = (struct super_block *)data; struct efivar_entry *entry; @@ -154,7 +153,7 @@ static int efivarfs_callback(efi_char16_t *name16, efi_guid_t vendor, } __efivar_entry_get(entry, NULL, &size, NULL); - __efivar_entry_add(entry, &efivarfs_list); + __efivar_entry_add(entry, list); /* copied by the above to local storage in the dentry. */ kfree(name); @@ -185,6 +184,7 @@ static int efivarfs_destroy(struct efivar_entry *entry, void *data) static int efivarfs_fill_super(struct super_block *sb, struct fs_context *fc) { + struct efivarfs_fs_info *sfi = sb->s_fs_info; struct inode *inode = NULL; struct dentry *root; int err; @@ -210,11 +210,10 @@ static int efivarfs_fill_super(struct super_block *sb, struct fs_context *fc) if (!root) return -ENOMEM; - INIT_LIST_HEAD(&efivarfs_list); - - err = efivar_init(efivarfs_callback, (void *)sb, true, &efivarfs_list); + err = efivar_init(efivarfs_callback, (void *)sb, true, + &sfi->efivarfs_list); if (err) - efivar_entry_iter(efivarfs_destroy, &efivarfs_list, NULL); + efivar_entry_iter(efivarfs_destroy, &sfi->efivarfs_list, NULL); return err; } @@ -241,6 +240,15 @@ static const struct fs_context_operations efivarfs_context_ops = { static int efivarfs_init_fs_context(struct fs_context *fc) { + struct efivarfs_fs_info *sfi; + + sfi = kzalloc(sizeof(*sfi), GFP_KERNEL); + if (!sfi) + return -ENOMEM; + + INIT_LIST_HEAD(&sfi->efivarfs_list); + + fc->s_fs_info = sfi; fc->ops = &efivarfs_context_ops; return 0; } @@ -252,7 +260,7 @@ static void efivarfs_kill_sb(struct super_block *sb) kill_litter_super(sb); /* Remove all entries and destroy */ - efivar_entry_iter(efivarfs_destroy, &efivarfs_list, NULL); + efivar_entry_iter(efivarfs_destroy, &sfi->efivarfs_list, NULL); kfree(sfi); } diff --git a/fs/efivarfs/vars.c b/fs/efivarfs/vars.c index 13bc60698955..2ad377818d0f 100644 --- a/fs/efivarfs/vars.c +++ b/fs/efivarfs/vars.c @@ -369,7 +369,8 @@ static void dup_variable_bug(efi_char16_t *str16, efi_guid_t *vendor_guid, * * 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 *), +int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *, + struct list_head *), void *data, bool duplicates, struct list_head *head) { unsigned long variable_name_size = 512; @@ -421,7 +422,7 @@ int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *), status = EFI_NOT_FOUND; } else { err = func(variable_name, vendor_guid, - variable_name_size, data); + variable_name_size, data, head); if (err) status = EFI_NOT_FOUND; } -- 2.43.0