Skip to content

Commit e971318

Browse files
author
Matt Fleming
committed
efivars: Handle duplicate names from get_next_variable()
Some firmware exhibits a bug where the same VariableName and VendorGuid values are returned on multiple invocations of GetNextVariableName(). See, https://bugzilla.kernel.org/show_bug.cgi?id=47631 As a consequence of such a bug, Andre reports hitting the following WARN_ON() in the sysfs code after updating the BIOS on his, "Gigabyte Technology Co., Ltd. To be filled by O.E.M./Z77X-UD3H, BIOS F19e 11/21/2012)" machine, [ 0.581554] EFI Variables Facility v0.08 2004-May-17 [ 0.584914] ------------[ cut here ]------------ [ 0.585639] WARNING: at /home/andre/linux/fs/sysfs/dir.c:536 sysfs_add_one+0xd4/0x100() [ 0.586381] Hardware name: To be filled by O.E.M. [ 0.587123] sysfs: cannot create duplicate filename '/firmware/efi/vars/SbAslBufferPtrVar-01f33c25-764d-43ea-aeea-6b5a41f3f3e8' [ 0.588694] Modules linked in: [ 0.589484] Pid: 1, comm: swapper/0 Not tainted 3.8.0+ #7 [ 0.590280] Call Trace: [ 0.591066] [<ffffffff81208954>] ? sysfs_add_one+0xd4/0x100 [ 0.591861] [<ffffffff810587bf>] warn_slowpath_common+0x7f/0xc0 [ 0.592650] [<ffffffff810588bc>] warn_slowpath_fmt+0x4c/0x50 [ 0.593429] [<ffffffff8134dd85>] ? strlcat+0x65/0x80 [ 0.594203] [<ffffffff81208954>] sysfs_add_one+0xd4/0x100 [ 0.594979] [<ffffffff81208b78>] create_dir+0x78/0xd0 [ 0.595753] [<ffffffff81208ec6>] sysfs_create_dir+0x86/0xe0 [ 0.596532] [<ffffffff81347e4c>] kobject_add_internal+0x9c/0x220 [ 0.597310] [<ffffffff81348307>] kobject_init_and_add+0x67/0x90 [ 0.598083] [<ffffffff81584a71>] ? efivar_create_sysfs_entry+0x61/0x1c0 [ 0.598859] [<ffffffff81584b2b>] efivar_create_sysfs_entry+0x11b/0x1c0 [ 0.599631] [<ffffffff8158517e>] register_efivars+0xde/0x420 [ 0.600395] [<ffffffff81d430a7>] ? edd_init+0x2f5/0x2f5 [ 0.601150] [<ffffffff81d4315f>] efivars_init+0xb8/0x104 [ 0.601903] [<ffffffff8100215a>] do_one_initcall+0x12a/0x180 [ 0.602659] [<ffffffff81d05d80>] kernel_init_freeable+0x13e/0x1c6 [ 0.603418] [<ffffffff81d05586>] ? loglevel+0x31/0x31 [ 0.604183] [<ffffffff816a6530>] ? rest_init+0x80/0x80 [ 0.604936] [<ffffffff816a653e>] kernel_init+0xe/0xf0 [ 0.605681] [<ffffffff816ce7ec>] ret_from_fork+0x7c/0xb0 [ 0.606414] [<ffffffff816a6530>] ? rest_init+0x80/0x80 [ 0.607143] ---[ end trace 1609741ab737eb29 ]--- There's not much we can do to work around and keep traversing the variable list once we hit this firmware bug. Our only solution is to terminate the loop because, as Lingzhu reports, some machines get stuck when they encounter duplicate names, > I had an IBM System x3100 M4 and x3850 X5 on which kernel would > get stuck in infinite loop creating duplicate sysfs files because, > for some reason, there are several duplicate boot entries in nvram > getting GetNextVariableName into a circle of iteration (with > period > 2). Also disable the workqueue, as efivar_update_sysfs_entries() uses GetNextVariableName() to figure out which variables have been created since the last iteration. That algorithm isn't going to work if GetNextVariableName() returns duplicates. Note that we don't disable EFI variable creation completely on the affected machines, it's just that any pstore dump-* files won't appear in sysfs until the next boot. Reported-by: Andre Heider <[email protected]> Reported-by: Lingzhu Xiang <[email protected]> Tested-by: Lingzhu Xiang <[email protected]> Cc: Seiji Aguchi <[email protected]> Cc: <[email protected]> Signed-off-by: Matt Fleming <[email protected]>
1 parent ec50bd3 commit e971318

File tree

1 file changed

+47
-1
lines changed

1 file changed

+47
-1
lines changed

drivers/firmware/efivars.c

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ efivar_create_sysfs_entry(struct efivars *efivars,
170170

171171
static void efivar_update_sysfs_entries(struct work_struct *);
172172
static DECLARE_WORK(efivar_work, efivar_update_sysfs_entries);
173+
static bool efivar_wq_enabled = true;
173174

174175
/* Return the number of unicode characters in data */
175176
static unsigned long
@@ -1444,7 +1445,7 @@ static int efi_pstore_write(enum pstore_type_id type,
14441445

14451446
spin_unlock_irqrestore(&efivars->lock, flags);
14461447

1447-
if (reason == KMSG_DUMP_OOPS)
1448+
if (reason == KMSG_DUMP_OOPS && efivar_wq_enabled)
14481449
schedule_work(&efivar_work);
14491450

14501451
*id = part;
@@ -1975,6 +1976,35 @@ void unregister_efivars(struct efivars *efivars)
19751976
}
19761977
EXPORT_SYMBOL_GPL(unregister_efivars);
19771978

1979+
/*
1980+
* Print a warning when duplicate EFI variables are encountered and
1981+
* disable the sysfs workqueue since the firmware is buggy.
1982+
*/
1983+
static void dup_variable_bug(efi_char16_t *s16, efi_guid_t *vendor_guid,
1984+
unsigned long len16)
1985+
{
1986+
size_t i, len8 = len16 / sizeof(efi_char16_t);
1987+
char *s8;
1988+
1989+
/*
1990+
* Disable the workqueue since the algorithm it uses for
1991+
* detecting new variables won't work with this buggy
1992+
* implementation of GetNextVariableName().
1993+
*/
1994+
efivar_wq_enabled = false;
1995+
1996+
s8 = kzalloc(len8, GFP_KERNEL);
1997+
if (!s8)
1998+
return;
1999+
2000+
for (i = 0; i < len8; i++)
2001+
s8[i] = s16[i];
2002+
2003+
printk(KERN_WARNING "efivars: duplicate variable: %s-%pUl\n",
2004+
s8, vendor_guid);
2005+
kfree(s8);
2006+
}
2007+
19782008
int register_efivars(struct efivars *efivars,
19792009
const struct efivar_operations *ops,
19802010
struct kobject *parent_kobj)
@@ -2025,6 +2055,22 @@ int register_efivars(struct efivars *efivars,
20252055
case EFI_SUCCESS:
20262056
variable_name_size = var_name_strnsize(variable_name,
20272057
variable_name_size);
2058+
2059+
/*
2060+
* Some firmware implementations return the
2061+
* same variable name on multiple calls to
2062+
* get_next_variable(). Terminate the loop
2063+
* immediately as there is no guarantee that
2064+
* we'll ever see a different variable name,
2065+
* and may end up looping here forever.
2066+
*/
2067+
if (variable_is_present(variable_name, &vendor_guid)) {
2068+
dup_variable_bug(variable_name, &vendor_guid,
2069+
variable_name_size);
2070+
status = EFI_NOT_FOUND;
2071+
break;
2072+
}
2073+
20282074
efivar_create_sysfs_entry(efivars,
20292075
variable_name_size,
20302076
variable_name,

0 commit comments

Comments
 (0)