This is marked as a v2 given that it is a followup to a RFC patch I sent
last week [0]. Since nobody commented that removing the old sysfs
efivars interface is a bad idea, I went ahead and performed the cleanup
that this enables.
Some of the prerequisites of this work have been posted separately and
have been queued up in efi/next already, mainly to move other users away
from the efivar API which they were using in the wrong way, or without a
good reason. [1]
The current state of things is that efi-pstore, efivarfs and the efivars
sysfs interface all share a common support layer which manages a linked
list containing efivar_entry items describing each EFI variable that
this support layer assumes to be present in the EFI variable store
managed by the firmware.
This shared layer also contains an efivars_operations pointer, which
carries function pointers that refer to the underlying EFI get/set
variable routines, but can be superseded by other implementations
(currently, this is only implemented for Google x86 systems that
implement the GSMI interface)
Each user of this shared layer has its own linked list, which means they
all have a different view of the underlying variable store, even though
they might operate on the same variables. For EFI pstore related
variables in particular, manipulating these behind the back of the other
drivers is likely to result in fun.
This shared layer as well as its 3 different users all use a single
semaphore to mediate access to the individual linked lists as well as
the ops pointer.
The shared layer carries a substantial amount of 'business logic'
related to which EFI variables are relevant to the firmware, to limit
whether and how they may be manipulated. This aspect of the code is
only relevant when such variables can be manipulated arbitrarily, e.g.
by user space, but EFI pstore, for example, has no need for this, as it
uses its own GUIDed namespace for EFI variables, and does not permit
other variables to be manipulated.
The two remaining users are efivars sysfs and efivarfs, both of which
provide a cached view of these 'important' variables. Given that the
former has been deprecated for a long time, and given the potential
concerns around using both concurrently, let's get rid of the sysfs
based one.
Then, we can restructure the efivars API so that this business logic
can be incorporated into the efivarfs driver, leaving only a minimal
wrapper around the get/set variable calls, allowing the GSMI replacement
to remain in use, as well as mediate access to the different services
using the existing semaphore. This is mainly useful to ensure that
set_variable() calls do no invalidate an enumeration of the EFI
variables that is in progress using get_next_variable() by another task.
[0] https://lore.kernel.org/linux-efi/[email protected]/T/#t
[1] https://git.kernel.org/pub/scm/linux/kernel/git/efi/efi.git/log/
Cc: Matthew Garrett <[email protected]>
Cc: Peter Jones <[email protected]>
Cc: Tony Luck <[email protected]>
Cc: Kees Cook <[email protected]>
Cc: Thomas Gleixner <[email protected]>
Cc: Ingo Molnar <[email protected]>
Cc: Borislav Petkov <[email protected]>
Cc: Dave Hansen <[email protected]>
Ard Biesheuvel (9):
pstore: Don't expose ECC metadata via pstore file system
efi: vars: Don't drop lock in the middle of efivar_init()
efi: vars: Add thin wrapper around EFI get/set variable interface
efi: pstore: Omit efivars caching EFI varstore access layer
efi: vars: Use locking version to iterate over efivars linked lists
efi: vars: Drop __efivar_entry_iter() helper which is no longer used
efi: vars: Remove deprecated 'efivars' sysfs interface
efi: vars: Switch to new wrapper layer
efi: vars: Move efivar caching layer into efivarfs
Documentation/x86/x86_64/uefi.rst | 2 +-
arch/arm/configs/milbeaut_m10v_defconfig | 1 -
arch/ia64/configs/bigsur_defconfig | 1 -
arch/ia64/configs/generic_defconfig | 1 -
arch/ia64/configs/gensparse_defconfig | 1 -
arch/ia64/configs/tiger_defconfig | 1 -
arch/ia64/configs/zx1_defconfig | 1 -
arch/x86/configs/i386_defconfig | 1 -
arch/x86/configs/x86_64_defconfig | 1 -
drivers/firmware/efi/Kconfig | 12 -
drivers/firmware/efi/Makefile | 1 -
drivers/firmware/efi/efi-pstore.c | 389 ++-----
drivers/firmware/efi/efi.c | 1 +
drivers/firmware/efi/efivars.c | 671 -----------
drivers/firmware/efi/vars.c | 1219 +++-----------------
fs/efivarfs/Makefile | 2 +-
fs/efivarfs/internal.h | 40 +
fs/efivarfs/super.c | 15 +-
fs/efivarfs/vars.c | 742 ++++++++++++
fs/pstore/inode.c | 2 +-
include/linux/efi.h | 80 +-
21 files changed, 1058 insertions(+), 2126 deletions(-)
delete mode 100644 drivers/firmware/efi/efivars.c
create mode 100644 fs/efivarfs/vars.c
--
2.35.1
Even though the efivars_lock lock is documented as protecting the
efivars->ops pointer (among other things), efivar_init() happily
releases and reacquires the lock for every EFI variable that it
enumerates. This used to be needed because the lock was originally a
spinlock, which prevented the callback that is invoked for every
variable from being able to sleep. However, releasing the lock could
potentially invalidate the ops pointer, but more importantly, it might
allow a SetVariable() runtime service call to take place concurrently,
and the UEFI spec does not define how this affects an enumeration that
is running in parallel using the GetNextVariable() runtime service,
which is what efivar_init() uses.
In the meantime, the lock has been converted into a semaphore, and the
only reason we need to drop the lock is because the efivarfs pseudo
filesystem driver will otherwise deadlock when it invokes the efivars
API from the callback to create the efivar_entry items and insert them
into the linked list. (EFI pstore is affected in a similar way)
So let's switch to helpers that can be used while the lock is already
taken. This way, we can hold on to the lock throughout the enumeration.
Signed-off-by: Ard Biesheuvel <[email protected]>
---
drivers/firmware/efi/efi-pstore.c | 7 ++-----
drivers/firmware/efi/efivars.c | 5 +----
drivers/firmware/efi/vars.c | 22 ++++++++++----------
fs/efivarfs/super.c | 6 ++----
include/linux/efi.h | 1 +
5 files changed, 17 insertions(+), 24 deletions(-)
diff --git a/drivers/firmware/efi/efi-pstore.c b/drivers/firmware/efi/efi-pstore.c
index 7e771c56c13c..0d80cc7ff6ca 100644
--- a/drivers/firmware/efi/efi-pstore.c
+++ b/drivers/firmware/efi/efi-pstore.c
@@ -364,7 +364,6 @@ static int efi_pstore_callback(efi_char16_t *name, efi_guid_t vendor,
unsigned long name_size, void *data)
{
struct efivar_entry *entry;
- int ret;
entry = kzalloc(sizeof(*entry), GFP_KERNEL);
if (!entry)
@@ -373,11 +372,9 @@ static int efi_pstore_callback(efi_char16_t *name, efi_guid_t vendor,
memcpy(entry->var.VariableName, name, name_size);
entry->var.VendorGuid = vendor;
- ret = efivar_entry_add(entry, &efi_pstore_list);
- if (ret)
- kfree(entry);
+ __efivar_entry_add(entry, &efi_pstore_list);
- return ret;
+ return 0;
}
static int efi_pstore_update_entry(efi_char16_t *name, efi_guid_t vendor,
diff --git a/drivers/firmware/efi/efivars.c b/drivers/firmware/efi/efivars.c
index ea0bc39dc965..c19db0b35c0d 100644
--- a/drivers/firmware/efi/efivars.c
+++ b/drivers/firmware/efi/efivars.c
@@ -527,10 +527,7 @@ efivar_create_sysfs_entry(struct efivar_entry *new_var)
}
kobject_uevent(&new_var->kobj, KOBJ_ADD);
- if (efivar_entry_add(new_var, &efivar_sysfs_list)) {
- efivar_unregister(new_var);
- return -EINTR;
- }
+ __efivar_entry_add(new_var, &efivar_sysfs_list);
return 0;
}
diff --git a/drivers/firmware/efi/vars.c b/drivers/firmware/efi/vars.c
index cae590bd08f2..146360e2f1cb 100644
--- a/drivers/firmware/efi/vars.c
+++ b/drivers/firmware/efi/vars.c
@@ -450,9 +450,6 @@ int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *),
&vendor_guid);
switch (status) {
case EFI_SUCCESS:
- if (duplicates)
- up(&efivars_lock);
-
variable_name_size = var_name_strnsize(variable_name,
variable_name_size);
@@ -476,14 +473,6 @@ int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *),
if (err)
status = EFI_NOT_FOUND;
}
-
- if (duplicates) {
- if (down_interruptible(&efivars_lock)) {
- err = -EINTR;
- goto free;
- }
- }
-
break;
case EFI_UNSUPPORTED:
err = -EOPNOTSUPP;
@@ -526,6 +515,17 @@ int efivar_entry_add(struct efivar_entry *entry, struct list_head *head)
}
EXPORT_SYMBOL_GPL(efivar_entry_add);
+/**
+ * __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);
+}
+EXPORT_SYMBOL_GPL(__efivar_entry_add);
+
/**
* efivar_entry_remove - remove entry from variable list
* @entry: entry to remove from list
diff --git a/fs/efivarfs/super.c b/fs/efivarfs/super.c
index 15880a68faad..09dfa8362f50 100644
--- a/fs/efivarfs/super.c
+++ b/fs/efivarfs/super.c
@@ -155,10 +155,8 @@ static int efivarfs_callback(efi_char16_t *name16, efi_guid_t vendor,
goto fail_inode;
}
- efivar_entry_size(entry, &size);
- err = efivar_entry_add(entry, &efivarfs_list);
- if (err)
- goto fail_inode;
+ __efivar_entry_get(entry, NULL, &size, NULL);
+ __efivar_entry_add(entry, &efivarfs_list);
/* copied by the above to local storage in the dentry. */
kfree(name);
diff --git a/include/linux/efi.h b/include/linux/efi.h
index 53f64c14a525..56f04b6daeb0 100644
--- a/include/linux/efi.h
+++ b/include/linux/efi.h
@@ -1064,6 +1064,7 @@ int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *),
void *data, bool duplicates, struct list_head *head);
int efivar_entry_add(struct efivar_entry *entry, struct list_head *head);
+void __efivar_entry_add(struct efivar_entry *entry, struct list_head *head);
int efivar_entry_remove(struct efivar_entry *entry);
int __efivar_entry_delete(struct efivar_entry *entry);
--
2.35.1
__efivar_entry_iter() uses a list iterator in a dubious way, i.e., it
assumes that the iteration variable always points to an object of the
appropriate type, even if the list traversal exhausts the list
completely, in which case it will point somewhere in the vicinity of the
list's anchor instead.
Fortunately, we no longer use this function so we can just get rid of it
entirely.
Signed-off-by: Ard Biesheuvel <[email protected]>
---
drivers/firmware/efi/vars.c | 61 +++-----------------
include/linux/efi.h | 3 -
2 files changed, 7 insertions(+), 57 deletions(-)
diff --git a/drivers/firmware/efi/vars.c b/drivers/firmware/efi/vars.c
index 29540013b358..932435945c85 100644
--- a/drivers/firmware/efi/vars.c
+++ b/drivers/firmware/efi/vars.c
@@ -1034,59 +1034,6 @@ void efivar_entry_iter_end(void)
}
EXPORT_SYMBOL_GPL(efivar_entry_iter_end);
-/**
- * __efivar_entry_iter - iterate over variable list
- * @func: callback function
- * @head: head of the variable list
- * @data: function-specific data to pass to callback
- * @prev: entry to begin iterating from
- *
- * 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().
- *
- * You MUST call efivar_entry_iter_begin() before this function, and
- * efivar_entry_iter_end() afterwards.
- *
- * It is possible to begin iteration from an arbitrary entry within
- * the list by passing @prev. @prev is updated on return to point to
- * the last entry passed to @func. To begin iterating from the
- * beginning of the list @prev must be %NULL.
- *
- * The restrictions for @func are the same as documented for
- * efivar_entry_iter().
- */
-int __efivar_entry_iter(int (*func)(struct efivar_entry *, void *),
- struct list_head *head, void *data,
- struct efivar_entry **prev)
-{
- struct efivar_entry *entry, *n;
- int err = 0;
-
- if (!prev || !*prev) {
- list_for_each_entry_safe(entry, n, head, list) {
- err = func(entry, data);
- if (err)
- break;
- }
-
- if (prev)
- *prev = entry;
-
- return err;
- }
-
-
- list_for_each_entry_safe_continue((*prev), n, head, list) {
- err = func(*prev, data);
- if (err)
- break;
- }
-
- return err;
-}
-EXPORT_SYMBOL_GPL(__efivar_entry_iter);
-
/**
* efivar_entry_iter - iterate over variable list
* @func: callback function
@@ -1104,12 +1051,18 @@ EXPORT_SYMBOL_GPL(__efivar_entry_iter);
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_entry_iter_begin();
if (err)
return err;
- err = __efivar_entry_iter(func, head, data, NULL);
+
+ list_for_each_entry_safe(entry, n, head, list) {
+ err = func(entry, data);
+ if (err)
+ break;
+ }
efivar_entry_iter_end();
return err;
diff --git a/include/linux/efi.h b/include/linux/efi.h
index 54ca2d6b6c78..93ce85a14a46 100644
--- a/include/linux/efi.h
+++ b/include/linux/efi.h
@@ -1083,9 +1083,6 @@ int efivar_entry_set_safe(efi_char16_t *name, efi_guid_t vendor, u32 attributes,
int efivar_entry_iter_begin(void);
void efivar_entry_iter_end(void);
-int __efivar_entry_iter(int (*func)(struct efivar_entry *, void *),
- struct list_head *head, void *data,
- struct efivar_entry **prev);
int efivar_entry_iter(int (*func)(struct efivar_entry *, void *),
struct list_head *head, void *data);
--
2.35.1
If a pstore record has its ecc_notice_size field set to >0, it means the
record's buffer has that many additional bytes appended to the end that
carry backend specific metadata, typically used for error correction.
Given that this is backend specific, and that user space cannot really
make sense of this metadata anyway, let's not expose it via the pstore
filesystem.
Signed-off-by: Ard Biesheuvel <[email protected]>
---
fs/pstore/inode.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/fs/pstore/inode.c b/fs/pstore/inode.c
index 14658b009f1b..b62eead3f801 100644
--- a/fs/pstore/inode.c
+++ b/fs/pstore/inode.c
@@ -349,7 +349,7 @@ int pstore_mkfile(struct dentry *root, struct pstore_record *record)
int rc = 0;
char name[PSTORE_NAMELEN];
struct pstore_private *private, *pos;
- size_t size = record->size + record->ecc_notice_size;
+ size_t size = record->size;
if (WARN_ON(!inode_is_locked(d_inode(root))))
return -EINVAL;
--
2.35.1
Commit 5d9db883761a ("efi: Add support for a UEFI variable filesystem")
dated Oct 5, 2012, introduced a new efivarfs pseudo-filesystem to
replace the efivars sysfs interface that was used up to that point to
expose EFI variables to user space.
The main problem with the sysfs interface was that it only supported up
to 1024 bytes of payload per file, whereas the underlying variables
themselves are only bounded by a platform specific per-variable and
global limit that is typically much higher than 1024 bytes.
The deprecated sysfs interface is only enabled on x86 and Itanium, other
EFI enabled architectures only support the efivarfs pseudo-filesystem.
So let's finally rip off the band aid, and drop the old interface
entirely. This will make it easier to refactor and clean up the
underlying infrastructure that is shared between efivars, efivarfs and
efi-pstore, and is long overdue for a makeover.
Signed-off-by: Ard Biesheuvel <[email protected]>
---
Documentation/x86/x86_64/uefi.rst | 2 +-
arch/arm/configs/milbeaut_m10v_defconfig | 1 -
arch/ia64/configs/bigsur_defconfig | 1 -
arch/ia64/configs/generic_defconfig | 1 -
arch/ia64/configs/gensparse_defconfig | 1 -
arch/ia64/configs/tiger_defconfig | 1 -
arch/ia64/configs/zx1_defconfig | 1 -
arch/x86/configs/i386_defconfig | 1 -
arch/x86/configs/x86_64_defconfig | 1 -
drivers/firmware/efi/Kconfig | 12 -
drivers/firmware/efi/Makefile | 1 -
drivers/firmware/efi/efivars.c | 660 --------------------
drivers/firmware/efi/vars.c | 265 +-------
include/linux/efi.h | 18 -
14 files changed, 3 insertions(+), 963 deletions(-)
diff --git a/Documentation/x86/x86_64/uefi.rst b/Documentation/x86/x86_64/uefi.rst
index 3b894103a734..fbc30c9a071d 100644
--- a/Documentation/x86/x86_64/uefi.rst
+++ b/Documentation/x86/x86_64/uefi.rst
@@ -29,7 +29,7 @@ Mechanics
be selected::
CONFIG_EFI=y
- CONFIG_EFI_VARS=y or m # optional
+ CONFIG_EFIVAR_FS=y or m # optional
- Create a VFAT partition on the disk
- Copy the following to the VFAT partition:
diff --git a/arch/arm/configs/milbeaut_m10v_defconfig b/arch/arm/configs/milbeaut_m10v_defconfig
index 7c07f9893a0f..9b4789af0201 100644
--- a/arch/arm/configs/milbeaut_m10v_defconfig
+++ b/arch/arm/configs/milbeaut_m10v_defconfig
@@ -44,7 +44,6 @@ CONFIG_ARM_CPUIDLE=y
CONFIG_VFP=y
CONFIG_NEON=y
CONFIG_KERNEL_MODE_NEON=y
-CONFIG_EFI_VARS=m
CONFIG_EFI_CAPSULE_LOADER=m
CONFIG_ARM_CRYPTO=y
CONFIG_CRYPTO_SHA1_ARM_NEON=m
diff --git a/arch/ia64/configs/bigsur_defconfig b/arch/ia64/configs/bigsur_defconfig
index 0341a67cc1bf..a3724882295c 100644
--- a/arch/ia64/configs/bigsur_defconfig
+++ b/arch/ia64/configs/bigsur_defconfig
@@ -10,7 +10,6 @@ CONFIG_SMP=y
CONFIG_NR_CPUS=2
CONFIG_PREEMPT=y
CONFIG_IA64_PALINFO=y
-CONFIG_EFI_VARS=y
CONFIG_BINFMT_MISC=m
CONFIG_ACPI_BUTTON=m
CONFIG_ACPI_FAN=m
diff --git a/arch/ia64/configs/generic_defconfig b/arch/ia64/configs/generic_defconfig
index 8916a2850c48..a3dff482a3d7 100644
--- a/arch/ia64/configs/generic_defconfig
+++ b/arch/ia64/configs/generic_defconfig
@@ -21,7 +21,6 @@ CONFIG_IA64_MCA_RECOVERY=y
CONFIG_IA64_PALINFO=y
CONFIG_KEXEC=y
CONFIG_CRASH_DUMP=y
-CONFIG_EFI_VARS=y
CONFIG_BINFMT_MISC=m
CONFIG_ACPI_BUTTON=m
CONFIG_ACPI_FAN=m
diff --git a/arch/ia64/configs/gensparse_defconfig b/arch/ia64/configs/gensparse_defconfig
index 281eb9c544f9..4cd46105b020 100644
--- a/arch/ia64/configs/gensparse_defconfig
+++ b/arch/ia64/configs/gensparse_defconfig
@@ -18,7 +18,6 @@ CONFIG_HOTPLUG_CPU=y
CONFIG_SPARSEMEM_MANUAL=y
CONFIG_IA64_MCA_RECOVERY=y
CONFIG_IA64_PALINFO=y
-CONFIG_EFI_VARS=y
CONFIG_BINFMT_MISC=m
CONFIG_ACPI_BUTTON=m
CONFIG_ACPI_FAN=m
diff --git a/arch/ia64/configs/tiger_defconfig b/arch/ia64/configs/tiger_defconfig
index b4f9819a1a45..a2045d73adfa 100644
--- a/arch/ia64/configs/tiger_defconfig
+++ b/arch/ia64/configs/tiger_defconfig
@@ -23,7 +23,6 @@ CONFIG_FORCE_CPEI_RETARGET=y
CONFIG_IA64_MCA_RECOVERY=y
CONFIG_IA64_PALINFO=y
CONFIG_KEXEC=y
-CONFIG_EFI_VARS=y
CONFIG_BINFMT_MISC=m
CONFIG_ACPI_BUTTON=m
CONFIG_ACPI_FAN=m
diff --git a/arch/ia64/configs/zx1_defconfig b/arch/ia64/configs/zx1_defconfig
index 851d8594cdb8..99f8b2a0332b 100644
--- a/arch/ia64/configs/zx1_defconfig
+++ b/arch/ia64/configs/zx1_defconfig
@@ -12,7 +12,6 @@ CONFIG_FLATMEM_MANUAL=y
CONFIG_IA64_MCA_RECOVERY=y
CONFIG_IA64_PALINFO=y
CONFIG_CRASH_DUMP=y
-CONFIG_EFI_VARS=y
CONFIG_BINFMT_MISC=y
CONFIG_HOTPLUG_PCI=y
CONFIG_HOTPLUG_PCI_ACPI=y
diff --git a/arch/x86/configs/i386_defconfig b/arch/x86/configs/i386_defconfig
index 98a4852ed6a0..7207219509f6 100644
--- a/arch/x86/configs/i386_defconfig
+++ b/arch/x86/configs/i386_defconfig
@@ -135,7 +135,6 @@ CONFIG_DEVTMPFS=y
CONFIG_DEVTMPFS_MOUNT=y
CONFIG_DEBUG_DEVRES=y
CONFIG_CONNECTOR=y
-CONFIG_EFI_VARS=y
CONFIG_EFI_CAPSULE_LOADER=y
CONFIG_BLK_DEV_LOOP=y
CONFIG_VIRTIO_BLK=y
diff --git a/arch/x86/configs/x86_64_defconfig b/arch/x86/configs/x86_64_defconfig
index 69784505a7a8..5ce67b73e218 100644
--- a/arch/x86/configs/x86_64_defconfig
+++ b/arch/x86/configs/x86_64_defconfig
@@ -134,7 +134,6 @@ CONFIG_DEVTMPFS=y
CONFIG_DEVTMPFS_MOUNT=y
CONFIG_DEBUG_DEVRES=y
CONFIG_CONNECTOR=y
-CONFIG_EFI_VARS=y
CONFIG_BLK_DEV_LOOP=y
CONFIG_VIRTIO_BLK=y
CONFIG_BLK_DEV_SD=y
diff --git a/drivers/firmware/efi/Kconfig b/drivers/firmware/efi/Kconfig
index 6fd4414c4836..c2f64e405336 100644
--- a/drivers/firmware/efi/Kconfig
+++ b/drivers/firmware/efi/Kconfig
@@ -2,18 +2,6 @@
menu "EFI (Extensible Firmware Interface) Support"
depends on EFI
-config EFI_VARS
- tristate "EFI Variable Support via sysfs"
- depends on EFI && (X86 || IA64)
- default n
- help
- If you say Y here, you are able to get EFI (Extensible Firmware
- Interface) variable information via sysfs. You may read,
- write, create, and destroy EFI variables through this interface.
- Note that this driver is only retained for compatibility with
- legacy users: new users should use the efivarfs filesystem
- instead.
-
config EFI_ESRT
bool
depends on EFI && !IA64
diff --git a/drivers/firmware/efi/Makefile b/drivers/firmware/efi/Makefile
index c02ff25dd477..8d151e332584 100644
--- a/drivers/firmware/efi/Makefile
+++ b/drivers/firmware/efi/Makefile
@@ -17,7 +17,6 @@ ifneq ($(CONFIG_EFI_CAPSULE_LOADER),)
obj-$(CONFIG_EFI) += capsule.o
endif
obj-$(CONFIG_EFI_PARAMS_FROM_FDT) += fdtparams.o
-obj-$(CONFIG_EFI_VARS) += efivars.o
obj-$(CONFIG_EFI_ESRT) += esrt.o
obj-$(CONFIG_EFI_VARS_PSTORE) += efi-pstore.o
obj-$(CONFIG_UEFI_CPER) += cper.o
diff --git a/drivers/firmware/efi/efivars.c b/drivers/firmware/efi/efivars.c
deleted file mode 100644
index 801a65582172..000000000000
--- a/drivers/firmware/efi/efivars.c
+++ /dev/null
@@ -1,660 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0+
-/*
- * Originally from efivars.c,
- *
- * Copyright (C) 2001,2003,2004 Dell <[email protected]>
- * Copyright (C) 2004 Intel Corporation <[email protected]>
- *
- * This code takes all variables accessible from EFI runtime and
- * exports them via sysfs
- */
-
-#include <linux/efi.h>
-#include <linux/module.h>
-#include <linux/slab.h>
-#include <linux/ucs2_string.h>
-#include <linux/compat.h>
-
-#define EFIVARS_VERSION "0.08"
-#define EFIVARS_DATE "2004-May-17"
-
-MODULE_AUTHOR("Matt Domsch <[email protected]>");
-MODULE_DESCRIPTION("sysfs interface to EFI Variables");
-MODULE_LICENSE("GPL");
-MODULE_VERSION(EFIVARS_VERSION);
-
-static LIST_HEAD(efivar_sysfs_list);
-
-static struct kset *efivars_kset;
-
-static struct bin_attribute *efivars_new_var;
-static struct bin_attribute *efivars_del_var;
-
-struct compat_efi_variable {
- efi_char16_t VariableName[EFI_VAR_NAME_LEN/sizeof(efi_char16_t)];
- efi_guid_t VendorGuid;
- __u32 DataSize;
- __u8 Data[1024];
- __u32 Status;
- __u32 Attributes;
-} __packed;
-
-struct efivar_attribute {
- struct attribute attr;
- ssize_t (*show) (struct efivar_entry *entry, char *buf);
- ssize_t (*store)(struct efivar_entry *entry, const char *buf, size_t count);
-};
-
-#define EFIVAR_ATTR(_name, _mode, _show, _store) \
-struct efivar_attribute efivar_attr_##_name = { \
- .attr = {.name = __stringify(_name), .mode = _mode}, \
- .show = _show, \
- .store = _store, \
-};
-
-#define to_efivar_attr(_attr) container_of(_attr, struct efivar_attribute, attr)
-#define to_efivar_entry(obj) container_of(obj, struct efivar_entry, kobj)
-
-/*
- * Prototype for sysfs creation function
- */
-static int
-efivar_create_sysfs_entry(struct efivar_entry *new_var);
-
-static ssize_t
-efivar_guid_read(struct efivar_entry *entry, char *buf)
-{
- struct efi_variable *var = &entry->var;
- char *str = buf;
-
- if (!entry || !buf)
- return 0;
-
- efi_guid_to_str(&var->VendorGuid, str);
- str += strlen(str);
- str += sprintf(str, "\n");
-
- return str - buf;
-}
-
-static ssize_t
-efivar_attr_read(struct efivar_entry *entry, char *buf)
-{
- struct efi_variable *var = &entry->var;
- unsigned long size = sizeof(var->Data);
- char *str = buf;
- int ret;
-
- if (!entry || !buf)
- return -EINVAL;
-
- ret = efivar_entry_get(entry, &var->Attributes, &size, var->Data);
- var->DataSize = size;
- if (ret)
- return -EIO;
-
- if (var->Attributes & EFI_VARIABLE_NON_VOLATILE)
- str += sprintf(str, "EFI_VARIABLE_NON_VOLATILE\n");
- if (var->Attributes & EFI_VARIABLE_BOOTSERVICE_ACCESS)
- str += sprintf(str, "EFI_VARIABLE_BOOTSERVICE_ACCESS\n");
- if (var->Attributes & EFI_VARIABLE_RUNTIME_ACCESS)
- str += sprintf(str, "EFI_VARIABLE_RUNTIME_ACCESS\n");
- if (var->Attributes & EFI_VARIABLE_HARDWARE_ERROR_RECORD)
- str += sprintf(str, "EFI_VARIABLE_HARDWARE_ERROR_RECORD\n");
- if (var->Attributes & EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS)
- str += sprintf(str,
- "EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS\n");
- if (var->Attributes &
- EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS)
- str += sprintf(str,
- "EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS\n");
- if (var->Attributes & EFI_VARIABLE_APPEND_WRITE)
- str += sprintf(str, "EFI_VARIABLE_APPEND_WRITE\n");
- return str - buf;
-}
-
-static ssize_t
-efivar_size_read(struct efivar_entry *entry, char *buf)
-{
- struct efi_variable *var = &entry->var;
- unsigned long size = sizeof(var->Data);
- char *str = buf;
- int ret;
-
- if (!entry || !buf)
- return -EINVAL;
-
- ret = efivar_entry_get(entry, &var->Attributes, &size, var->Data);
- var->DataSize = size;
- if (ret)
- return -EIO;
-
- str += sprintf(str, "0x%lx\n", var->DataSize);
- return str - buf;
-}
-
-static ssize_t
-efivar_data_read(struct efivar_entry *entry, char *buf)
-{
- struct efi_variable *var = &entry->var;
- unsigned long size = sizeof(var->Data);
- int ret;
-
- if (!entry || !buf)
- return -EINVAL;
-
- ret = efivar_entry_get(entry, &var->Attributes, &size, var->Data);
- var->DataSize = size;
- if (ret)
- return -EIO;
-
- memcpy(buf, var->Data, var->DataSize);
- return var->DataSize;
-}
-
-static inline int
-sanity_check(struct efi_variable *var, efi_char16_t *name, efi_guid_t vendor,
- unsigned long size, u32 attributes, u8 *data)
-{
- /*
- * If only updating the variable data, then the name
- * and guid should remain the same
- */
- if (memcmp(name, var->VariableName, sizeof(var->VariableName)) ||
- efi_guidcmp(vendor, var->VendorGuid)) {
- printk(KERN_ERR "efivars: Cannot edit the wrong variable!\n");
- return -EINVAL;
- }
-
- if ((size <= 0) || (attributes == 0)){
- printk(KERN_ERR "efivars: DataSize & Attributes must be valid!\n");
- return -EINVAL;
- }
-
- if ((attributes & ~EFI_VARIABLE_MASK) != 0 ||
- efivar_validate(vendor, name, data, size) == false) {
- printk(KERN_ERR "efivars: Malformed variable content\n");
- return -EINVAL;
- }
-
- return 0;
-}
-
-static void
-copy_out_compat(struct efi_variable *dst, struct compat_efi_variable *src)
-{
- memcpy(dst->VariableName, src->VariableName, EFI_VAR_NAME_LEN);
- memcpy(dst->Data, src->Data, sizeof(src->Data));
-
- dst->VendorGuid = src->VendorGuid;
- dst->DataSize = src->DataSize;
- dst->Attributes = src->Attributes;
-}
-
-/*
- * We allow each variable to be edited via rewriting the
- * entire efi variable structure.
- */
-static ssize_t
-efivar_store_raw(struct efivar_entry *entry, const char *buf, size_t count)
-{
- struct efi_variable *new_var, *var = &entry->var;
- efi_char16_t *name;
- unsigned long size;
- efi_guid_t vendor;
- u32 attributes;
- u8 *data;
- int err;
-
- if (!entry || !buf)
- return -EINVAL;
-
- if (in_compat_syscall()) {
- struct compat_efi_variable *compat;
-
- if (count != sizeof(*compat))
- return -EINVAL;
-
- compat = (struct compat_efi_variable *)buf;
- attributes = compat->Attributes;
- vendor = compat->VendorGuid;
- name = compat->VariableName;
- size = compat->DataSize;
- data = compat->Data;
-
- err = sanity_check(var, name, vendor, size, attributes, data);
- if (err)
- return err;
-
- copy_out_compat(&entry->var, compat);
- } else {
- if (count != sizeof(struct efi_variable))
- return -EINVAL;
-
- new_var = (struct efi_variable *)buf;
-
- attributes = new_var->Attributes;
- vendor = new_var->VendorGuid;
- name = new_var->VariableName;
- size = new_var->DataSize;
- data = new_var->Data;
-
- err = sanity_check(var, name, vendor, size, attributes, data);
- if (err)
- return err;
-
- memcpy(&entry->var, new_var, count);
- }
-
- err = efivar_entry_set(entry, attributes, size, data, NULL);
- if (err) {
- printk(KERN_WARNING "efivars: set_variable() failed: status=%d\n", err);
- return -EIO;
- }
-
- return count;
-}
-
-static ssize_t
-efivar_show_raw(struct efivar_entry *entry, char *buf)
-{
- struct efi_variable *var = &entry->var;
- struct compat_efi_variable *compat;
- unsigned long datasize = sizeof(var->Data);
- size_t size;
- int ret;
-
- if (!entry || !buf)
- return 0;
-
- ret = efivar_entry_get(entry, &var->Attributes, &datasize, var->Data);
- var->DataSize = datasize;
- if (ret)
- return -EIO;
-
- if (in_compat_syscall()) {
- compat = (struct compat_efi_variable *)buf;
-
- size = sizeof(*compat);
- memcpy(compat->VariableName, var->VariableName,
- EFI_VAR_NAME_LEN);
- memcpy(compat->Data, var->Data, sizeof(compat->Data));
-
- compat->VendorGuid = var->VendorGuid;
- compat->DataSize = var->DataSize;
- compat->Attributes = var->Attributes;
- } else {
- size = sizeof(*var);
- memcpy(buf, var, size);
- }
-
- return size;
-}
-
-/*
- * Generic read/write functions that call the specific functions of
- * the attributes...
- */
-static ssize_t efivar_attr_show(struct kobject *kobj, struct attribute *attr,
- char *buf)
-{
- struct efivar_entry *var = to_efivar_entry(kobj);
- struct efivar_attribute *efivar_attr = to_efivar_attr(attr);
- ssize_t ret = -EIO;
-
- if (!capable(CAP_SYS_ADMIN))
- return -EACCES;
-
- if (efivar_attr->show) {
- ret = efivar_attr->show(var, buf);
- }
- return ret;
-}
-
-static ssize_t efivar_attr_store(struct kobject *kobj, struct attribute *attr,
- const char *buf, size_t count)
-{
- struct efivar_entry *var = to_efivar_entry(kobj);
- struct efivar_attribute *efivar_attr = to_efivar_attr(attr);
- ssize_t ret = -EIO;
-
- if (!capable(CAP_SYS_ADMIN))
- return -EACCES;
-
- if (efivar_attr->store)
- ret = efivar_attr->store(var, buf, count);
-
- return ret;
-}
-
-static const struct sysfs_ops efivar_attr_ops = {
- .show = efivar_attr_show,
- .store = efivar_attr_store,
-};
-
-static void efivar_release(struct kobject *kobj)
-{
- struct efivar_entry *var = to_efivar_entry(kobj);
- kfree(var);
-}
-
-static EFIVAR_ATTR(guid, 0400, efivar_guid_read, NULL);
-static EFIVAR_ATTR(attributes, 0400, efivar_attr_read, NULL);
-static EFIVAR_ATTR(size, 0400, efivar_size_read, NULL);
-static EFIVAR_ATTR(data, 0400, efivar_data_read, NULL);
-static EFIVAR_ATTR(raw_var, 0600, efivar_show_raw, efivar_store_raw);
-
-static struct attribute *def_attrs[] = {
- &efivar_attr_guid.attr,
- &efivar_attr_size.attr,
- &efivar_attr_attributes.attr,
- &efivar_attr_data.attr,
- &efivar_attr_raw_var.attr,
- NULL,
-};
-ATTRIBUTE_GROUPS(def);
-
-static struct kobj_type efivar_ktype = {
- .release = efivar_release,
- .sysfs_ops = &efivar_attr_ops,
- .default_groups = def_groups,
-};
-
-static ssize_t efivar_create(struct file *filp, struct kobject *kobj,
- struct bin_attribute *bin_attr,
- char *buf, loff_t pos, size_t count)
-{
- struct compat_efi_variable *compat = (struct compat_efi_variable *)buf;
- struct efi_variable *new_var = (struct efi_variable *)buf;
- struct efivar_entry *new_entry;
- bool need_compat = in_compat_syscall();
- efi_char16_t *name;
- unsigned long size;
- u32 attributes;
- u8 *data;
- int err;
-
- if (!capable(CAP_SYS_ADMIN))
- return -EACCES;
-
- if (need_compat) {
- if (count != sizeof(*compat))
- return -EINVAL;
-
- attributes = compat->Attributes;
- name = compat->VariableName;
- size = compat->DataSize;
- data = compat->Data;
- } else {
- if (count != sizeof(*new_var))
- return -EINVAL;
-
- attributes = new_var->Attributes;
- name = new_var->VariableName;
- size = new_var->DataSize;
- data = new_var->Data;
- }
-
- if ((attributes & ~EFI_VARIABLE_MASK) != 0 ||
- efivar_validate(new_var->VendorGuid, name, data,
- size) == false) {
- printk(KERN_ERR "efivars: Malformed variable content\n");
- return -EINVAL;
- }
-
- new_entry = kzalloc(sizeof(*new_entry), GFP_KERNEL);
- if (!new_entry)
- return -ENOMEM;
-
- if (need_compat)
- copy_out_compat(&new_entry->var, compat);
- else
- memcpy(&new_entry->var, new_var, sizeof(*new_var));
-
- err = efivar_entry_set(new_entry, attributes, size,
- data, &efivar_sysfs_list);
- if (err) {
- if (err == -EEXIST)
- err = -EINVAL;
- goto out;
- }
-
- if (efivar_create_sysfs_entry(new_entry)) {
- printk(KERN_WARNING "efivars: failed to create sysfs entry.\n");
- kfree(new_entry);
- }
- return count;
-
-out:
- kfree(new_entry);
- return err;
-}
-
-static ssize_t efivar_delete(struct file *filp, struct kobject *kobj,
- struct bin_attribute *bin_attr,
- char *buf, loff_t pos, size_t count)
-{
- struct efi_variable *del_var = (struct efi_variable *)buf;
- struct compat_efi_variable *compat;
- struct efivar_entry *entry;
- efi_char16_t *name;
- efi_guid_t vendor;
- int err = 0;
-
- if (!capable(CAP_SYS_ADMIN))
- return -EACCES;
-
- if (in_compat_syscall()) {
- if (count != sizeof(*compat))
- return -EINVAL;
-
- compat = (struct compat_efi_variable *)buf;
- name = compat->VariableName;
- vendor = compat->VendorGuid;
- } else {
- if (count != sizeof(*del_var))
- return -EINVAL;
-
- name = del_var->VariableName;
- vendor = del_var->VendorGuid;
- }
-
- if (efivar_entry_iter_begin())
- return -EINTR;
- entry = efivar_entry_find(name, vendor, &efivar_sysfs_list, true);
- if (!entry)
- err = -EINVAL;
- else if (__efivar_entry_delete(entry))
- err = -EIO;
-
- efivar_entry_iter_end();
-
- if (err)
- return err;
-
- efivar_unregister(entry);
-
- /* It's dead Jim.... */
- return count;
-}
-
-/**
- * efivar_create_sysfs_entry - create a new entry in sysfs
- * @new_var: efivar entry to create
- *
- * Returns 0 on success, negative error code on failure
- */
-static int
-efivar_create_sysfs_entry(struct efivar_entry *new_var)
-{
- int short_name_size;
- char *short_name;
- unsigned long utf8_name_size;
- efi_char16_t *variable_name = new_var->var.VariableName;
- int ret;
-
- /*
- * Length of the variable bytes in UTF8, plus the '-' separator,
- * plus the GUID, plus trailing NUL
- */
- utf8_name_size = ucs2_utf8size(variable_name);
- short_name_size = utf8_name_size + 1 + EFI_VARIABLE_GUID_LEN + 1;
-
- short_name = kmalloc(short_name_size, GFP_KERNEL);
- if (!short_name)
- return -ENOMEM;
-
- ucs2_as_utf8(short_name, variable_name, short_name_size);
-
- /* This is ugly, but necessary to separate one vendor's
- private variables from another's. */
- short_name[utf8_name_size] = '-';
- efi_guid_to_str(&new_var->var.VendorGuid,
- short_name + utf8_name_size + 1);
-
- new_var->kobj.kset = efivars_kset;
-
- ret = kobject_init_and_add(&new_var->kobj, &efivar_ktype,
- NULL, "%s", short_name);
- kfree(short_name);
- if (ret) {
- kobject_put(&new_var->kobj);
- return ret;
- }
-
- kobject_uevent(&new_var->kobj, KOBJ_ADD);
- __efivar_entry_add(new_var, &efivar_sysfs_list);
-
- return 0;
-}
-
-static int
-create_efivars_bin_attributes(void)
-{
- struct bin_attribute *attr;
- int error;
-
- /* new_var */
- attr = kzalloc(sizeof(*attr), GFP_KERNEL);
- if (!attr)
- return -ENOMEM;
-
- attr->attr.name = "new_var";
- attr->attr.mode = 0200;
- attr->write = efivar_create;
- efivars_new_var = attr;
-
- /* del_var */
- attr = kzalloc(sizeof(*attr), GFP_KERNEL);
- if (!attr) {
- error = -ENOMEM;
- goto out_free;
- }
- attr->attr.name = "del_var";
- attr->attr.mode = 0200;
- attr->write = efivar_delete;
- efivars_del_var = attr;
-
- sysfs_bin_attr_init(efivars_new_var);
- sysfs_bin_attr_init(efivars_del_var);
-
- /* Register */
- error = sysfs_create_bin_file(&efivars_kset->kobj, efivars_new_var);
- if (error) {
- printk(KERN_ERR "efivars: unable to create new_var sysfs file"
- " due to error %d\n", error);
- goto out_free;
- }
-
- error = sysfs_create_bin_file(&efivars_kset->kobj, efivars_del_var);
- if (error) {
- printk(KERN_ERR "efivars: unable to create del_var sysfs file"
- " due to error %d\n", error);
- sysfs_remove_bin_file(&efivars_kset->kobj, efivars_new_var);
- goto out_free;
- }
-
- return 0;
-out_free:
- kfree(efivars_del_var);
- efivars_del_var = NULL;
- kfree(efivars_new_var);
- efivars_new_var = NULL;
- return error;
-}
-
-static int efivars_sysfs_callback(efi_char16_t *name, efi_guid_t vendor,
- unsigned long name_size, void *data)
-{
- struct efivar_entry *entry;
-
- entry = kzalloc(sizeof(*entry), GFP_KERNEL);
- if (!entry)
- return -ENOMEM;
-
- memcpy(entry->var.VariableName, name, name_size);
- memcpy(&(entry->var.VendorGuid), &vendor, sizeof(efi_guid_t));
-
- efivar_create_sysfs_entry(entry);
-
- return 0;
-}
-
-static int efivar_sysfs_destroy(struct efivar_entry *entry, void *data)
-{
- efivar_entry_remove(entry);
- efivar_unregister(entry);
- return 0;
-}
-
-static void efivars_sysfs_exit(void)
-{
- /* Remove all entries and destroy */
- int err;
-
- err = efivar_entry_iter(efivar_sysfs_destroy, &efivar_sysfs_list, NULL);
- if (err) {
- pr_err("efivars: Failed to destroy sysfs entries\n");
- return;
- }
-
- if (efivars_new_var)
- sysfs_remove_bin_file(&efivars_kset->kobj, efivars_new_var);
- if (efivars_del_var)
- sysfs_remove_bin_file(&efivars_kset->kobj, efivars_del_var);
- kfree(efivars_new_var);
- kfree(efivars_del_var);
- kset_unregister(efivars_kset);
-}
-
-static int efivars_sysfs_init(void)
-{
- struct kobject *parent_kobj = efivars_kobject();
- int error = 0;
-
- /* No efivars has been registered yet */
- if (!parent_kobj || !efivar_supports_writes())
- return 0;
-
- printk(KERN_INFO "EFI Variables Facility v%s %s\n", EFIVARS_VERSION,
- EFIVARS_DATE);
-
- efivars_kset = kset_create_and_add("vars", NULL, parent_kobj);
- if (!efivars_kset) {
- printk(KERN_ERR "efivars: Subsystem registration failed.\n");
- return -ENOMEM;
- }
-
- efivar_init(efivars_sysfs_callback, NULL, true, &efivar_sysfs_list);
-
- error = create_efivars_bin_attributes();
- if (error) {
- efivars_sysfs_exit();
- return error;
- }
-
- return 0;
-}
-
-module_init(efivars_sysfs_init);
-module_exit(efivars_sysfs_exit);
diff --git a/drivers/firmware/efi/vars.c b/drivers/firmware/efi/vars.c
index 932435945c85..94de1d0cb4e7 100644
--- a/drivers/firmware/efi/vars.c
+++ b/drivers/firmware/efi/vars.c
@@ -547,36 +547,6 @@ static void efivar_entry_list_del_unlock(struct efivar_entry *entry)
up(&efivars_lock);
}
-/**
- * __efivar_entry_delete - delete an EFI variable
- * @entry: entry containing EFI variable to delete
- *
- * Delete the variable from the firmware but leave @entry on the
- * variable list.
- *
- * This function differs from efivar_entry_delete() because it does
- * not remove @entry from the variable list. Also, it is safe to be
- * called from within a efivar_entry_iter_begin() and
- * efivar_entry_iter_end() region, unlike efivar_entry_delete().
- *
- * Returns 0 on success, or a converted EFI status code if
- * set_variable() fails.
- */
-int __efivar_entry_delete(struct efivar_entry *entry)
-{
- efi_status_t status;
-
- if (!__efivars)
- return -EINVAL;
-
- status = __efivars->ops->set_variable(entry->var.VariableName,
- &entry->var.VendorGuid,
- 0, 0, NULL);
-
- return efi_status_to_err(status);
-}
-EXPORT_SYMBOL_GPL(__efivar_entry_delete);
-
/**
* efivar_entry_delete - delete variable and remove entry from list
* @entry: entry containing variable to delete
@@ -614,213 +584,6 @@ int efivar_entry_delete(struct efivar_entry *entry)
}
EXPORT_SYMBOL_GPL(efivar_entry_delete);
-/**
- * efivar_entry_set - call set_variable()
- * @entry: entry containing the EFI variable to write
- * @attributes: variable attributes
- * @size: size of @data buffer
- * @data: buffer containing variable data
- * @head: head of variable list
- *
- * Calls set_variable() for an EFI variable. If creating a new EFI
- * variable, this function is usually followed by efivar_entry_add().
- *
- * Before writing the variable, the remaining EFI variable storage
- * space is checked to ensure there is enough room available.
- *
- * If @head is not NULL a lookup is performed to determine whether
- * the entry is already on the list.
- *
- * Returns 0 on success, -EINTR if we can't grab the semaphore,
- * -EEXIST if a lookup is performed and the entry already exists on
- * the list, or a converted EFI status code if set_variable() fails.
- */
-int efivar_entry_set(struct efivar_entry *entry, u32 attributes,
- unsigned long size, void *data, struct list_head *head)
-{
- const struct efivar_operations *ops;
- efi_status_t status;
- efi_char16_t *name = entry->var.VariableName;
- efi_guid_t vendor = entry->var.VendorGuid;
-
- if (down_interruptible(&efivars_lock))
- return -EINTR;
-
- if (!__efivars) {
- up(&efivars_lock);
- return -EINVAL;
- }
- ops = __efivars->ops;
- if (head && efivar_entry_find(name, vendor, head, false)) {
- up(&efivars_lock);
- return -EEXIST;
- }
-
- status = check_var_size(attributes, size + ucs2_strsize(name, 1024));
- if (status == EFI_SUCCESS || status == EFI_UNSUPPORTED)
- status = ops->set_variable(name, &vendor,
- attributes, size, data);
-
- up(&efivars_lock);
-
- return efi_status_to_err(status);
-
-}
-EXPORT_SYMBOL_GPL(efivar_entry_set);
-
-/*
- * efivar_entry_set_nonblocking - call set_variable_nonblocking()
- *
- * This function is guaranteed to not block and is suitable for calling
- * from crash/panic handlers.
- *
- * Crucially, this function will not block if it cannot acquire
- * efivars_lock. Instead, it returns -EBUSY.
- */
-static int
-efivar_entry_set_nonblocking(efi_char16_t *name, efi_guid_t vendor,
- u32 attributes, unsigned long size, void *data)
-{
- const struct efivar_operations *ops;
- efi_status_t status;
-
- if (down_trylock(&efivars_lock))
- return -EBUSY;
-
- if (!__efivars) {
- up(&efivars_lock);
- return -EINVAL;
- }
-
- status = check_var_size_nonblocking(attributes,
- size + ucs2_strsize(name, 1024));
- if (status != EFI_SUCCESS) {
- up(&efivars_lock);
- return -ENOSPC;
- }
-
- ops = __efivars->ops;
- status = ops->set_variable_nonblocking(name, &vendor, attributes,
- size, data);
-
- up(&efivars_lock);
- return efi_status_to_err(status);
-}
-
-/**
- * efivar_entry_set_safe - call set_variable() if enough space in firmware
- * @name: buffer containing the variable name
- * @vendor: variable vendor guid
- * @attributes: variable attributes
- * @block: can we block in this context?
- * @size: size of @data buffer
- * @data: buffer containing variable data
- *
- * Ensures there is enough free storage in the firmware for this variable, and
- * if so, calls set_variable(). If creating a new EFI variable, this function
- * is usually followed by efivar_entry_add().
- *
- * Returns 0 on success, -ENOSPC if the firmware does not have enough
- * space for set_variable() to succeed, or a converted EFI status code
- * if set_variable() fails.
- */
-int efivar_entry_set_safe(efi_char16_t *name, efi_guid_t vendor, u32 attributes,
- bool block, unsigned long size, void *data)
-{
- const struct efivar_operations *ops;
- efi_status_t status;
- unsigned long varsize;
-
- if (!__efivars)
- return -EINVAL;
-
- ops = __efivars->ops;
- if (!ops->query_variable_store)
- return -ENOSYS;
-
- /*
- * If the EFI variable backend provides a non-blocking
- * ->set_variable() operation and we're in a context where we
- * cannot block, then we need to use it to avoid live-locks,
- * since the implication is that the regular ->set_variable()
- * will block.
- *
- * If no ->set_variable_nonblocking() is provided then
- * ->set_variable() is assumed to be non-blocking.
- */
- if (!block && ops->set_variable_nonblocking)
- return efivar_entry_set_nonblocking(name, vendor, attributes,
- size, data);
-
- varsize = size + ucs2_strsize(name, 1024);
- if (!block) {
- if (down_trylock(&efivars_lock))
- return -EBUSY;
- status = check_var_size_nonblocking(attributes, varsize);
- } else {
- if (down_interruptible(&efivars_lock))
- return -EINTR;
- status = check_var_size(attributes, varsize);
- }
-
- if (status != EFI_SUCCESS) {
- up(&efivars_lock);
- return -ENOSPC;
- }
-
- status = ops->set_variable(name, &vendor, attributes, size, data);
-
- up(&efivars_lock);
-
- return efi_status_to_err(status);
-}
-EXPORT_SYMBOL_GPL(efivar_entry_set_safe);
-
-/**
- * efivar_entry_find - search for an entry
- * @name: the EFI variable name
- * @guid: the EFI variable vendor's guid
- * @head: head of the variable list
- * @remove: should we remove the entry from the list?
- *
- * Search for an entry on the variable list that has the EFI variable
- * name @name and vendor guid @guid. If an entry is found on the list
- * and @remove is true, the entry is removed from the list.
- *
- * The caller MUST call efivar_entry_iter_begin() and
- * efivar_entry_iter_end() before and after the invocation of this
- * function, respectively.
- *
- * Returns the entry if found on the list, %NULL otherwise.
- */
-struct efivar_entry *efivar_entry_find(efi_char16_t *name, efi_guid_t guid,
- struct list_head *head, bool remove)
-{
- struct efivar_entry *entry, *n;
- int strsize1, strsize2;
- bool found = false;
-
- list_for_each_entry_safe(entry, n, head, list) {
- strsize1 = ucs2_strsize(name, 1024);
- strsize2 = ucs2_strsize(entry->var.VariableName, 1024);
- if (strsize1 == strsize2 &&
- !memcmp(name, &(entry->var.VariableName), strsize1) &&
- !efi_guidcmp(guid, entry->var.VendorGuid)) {
- found = true;
- break;
- }
- }
-
- if (!found)
- return NULL;
-
- if (remove)
- list_del(&entry->list);
-
- return entry;
-}
-EXPORT_SYMBOL_GPL(efivar_entry_find);
-
/**
* efivar_entry_size - obtain the size of a variable
* @entry: entry for this variable
@@ -1010,30 +773,6 @@ int efivar_entry_set_get_size(struct efivar_entry *entry, u32 attributes,
}
EXPORT_SYMBOL_GPL(efivar_entry_set_get_size);
-/**
- * efivar_entry_iter_begin - begin iterating the variable list
- *
- * Lock the variable list to prevent entry insertion and removal until
- * efivar_entry_iter_end() is called. This function is usually used in
- * conjunction with __efivar_entry_iter() or efivar_entry_iter().
- */
-int efivar_entry_iter_begin(void)
-{
- return down_interruptible(&efivars_lock);
-}
-EXPORT_SYMBOL_GPL(efivar_entry_iter_begin);
-
-/**
- * efivar_entry_iter_end - finish iterating the variable list
- *
- * Unlock the variable list and allow modifications to the list again.
- */
-void efivar_entry_iter_end(void)
-{
- up(&efivars_lock);
-}
-EXPORT_SYMBOL_GPL(efivar_entry_iter_end);
-
/**
* efivar_entry_iter - iterate over variable list
* @func: callback function
@@ -1054,7 +793,7 @@ int efivar_entry_iter(int (*func)(struct efivar_entry *, void *),
struct efivar_entry *entry, *n;
int err = 0;
- err = efivar_entry_iter_begin();
+ err = down_interruptible(&efivars_lock);
if (err)
return err;
@@ -1063,7 +802,7 @@ int efivar_entry_iter(int (*func)(struct efivar_entry *, void *),
if (err)
break;
}
- efivar_entry_iter_end();
+ up(&efivars_lock);
return err;
}
diff --git a/include/linux/efi.h b/include/linux/efi.h
index 93ce85a14a46..10ef0a0d5e9a 100644
--- a/include/linux/efi.h
+++ b/include/linux/efi.h
@@ -1045,12 +1045,6 @@ struct efivar_entry {
struct kobject kobj;
};
-static inline void
-efivar_unregister(struct efivar_entry *var)
-{
- kobject_put(&var->kobj);
-}
-
int efivars_register(struct efivars *efivars,
const struct efivar_operations *ops,
struct kobject *kobject);
@@ -1064,8 +1058,6 @@ int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *),
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_delete(struct efivar_entry *entry);
int efivar_entry_size(struct efivar_entry *entry, unsigned long *size);
@@ -1073,22 +1065,12 @@ int __efivar_entry_get(struct efivar_entry *entry, u32 *attributes,
unsigned long *size, void *data);
int efivar_entry_get(struct efivar_entry *entry, u32 *attributes,
unsigned long *size, void *data);
-int efivar_entry_set(struct efivar_entry *entry, u32 attributes,
- unsigned long size, void *data, struct list_head *head);
int efivar_entry_set_get_size(struct efivar_entry *entry, u32 attributes,
unsigned long *size, void *data, bool *set);
-int efivar_entry_set_safe(efi_char16_t *name, efi_guid_t vendor, u32 attributes,
- bool block, unsigned long size, void *data);
-
-int efivar_entry_iter_begin(void);
-void efivar_entry_iter_end(void);
int efivar_entry_iter(int (*func)(struct efivar_entry *, void *),
struct list_head *head, void *data);
-struct efivar_entry *efivar_entry_find(efi_char16_t *name, efi_guid_t guid,
- struct list_head *head, bool remove);
-
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,
--
2.35.1
The current efivars layer is a jumble of list iterators, shadow data
structures and safe variable manipulation helpers that really belong in
the efivarfs pseudo file system once the obsolete sysfs access method to
EFI variables is removed.
So split off a minimal efivar get/set variable API that reuses the
existing efivars_lock semaphore to mediate access to the various runtime
services, primarily to ensure that performing a SetVariable() on one CPU
while another is calling GetNextVariable() in a loop to enumerate the
contents of the EFI variable store does not result in surprises.
Signed-off-by: Ard Biesheuvel <[email protected]>
---
drivers/firmware/efi/vars.c | 154 ++++++++++++++++++--
include/linux/efi.h | 20 +++
2 files changed, 164 insertions(+), 10 deletions(-)
diff --git a/drivers/firmware/efi/vars.c b/drivers/firmware/efi/vars.c
index 146360e2f1cb..41c82614a4b2 100644
--- a/drivers/firmware/efi/vars.c
+++ b/drivers/firmware/efi/vars.c
@@ -298,14 +298,10 @@ efivar_variable_is_removable(efi_guid_t vendor, const char *var_name,
}
EXPORT_SYMBOL_GPL(efivar_variable_is_removable);
-static efi_status_t
-check_var_size(u32 attributes, unsigned long size)
+efi_status_t check_var_size(u32 attributes, unsigned long size)
{
const struct efivar_operations *fops;
- if (!__efivars)
- return EFI_UNSUPPORTED;
-
fops = __efivars->ops;
if (!fops->query_variable_store)
@@ -313,15 +309,12 @@ check_var_size(u32 attributes, unsigned long size)
return fops->query_variable_store(attributes, size, false);
}
+EXPORT_SYMBOL_NS_GPL(check_var_size, EFIVAR);
-static efi_status_t
-check_var_size_nonblocking(u32 attributes, unsigned long size)
+efi_status_t check_var_size_nonblocking(u32 attributes, unsigned long size)
{
const struct efivar_operations *fops;
- if (!__efivars)
- return EFI_UNSUPPORTED;
-
fops = __efivars->ops;
if (!fops->query_variable_store)
@@ -329,6 +322,7 @@ check_var_size_nonblocking(u32 attributes, unsigned long size)
return fops->query_variable_store(attributes, size, true);
}
+EXPORT_SYMBOL_NS_GPL(check_var_size_nonblocking, EFIVAR);
static bool variable_is_present(efi_char16_t *variable_name, efi_guid_t *vendor,
struct list_head *head)
@@ -1220,3 +1214,143 @@ int efivar_supports_writes(void)
return __efivars && __efivars->ops->set_variable;
}
EXPORT_SYMBOL_GPL(efivar_supports_writes);
+
+/*
+ * efivar_lock() - obtain the efivar lock, wait for it if needed
+ * @return 0 on success, error code on failure
+ */
+int efivar_lock(void)
+{
+ if (down_interruptible(&efivars_lock))
+ return -EINTR;
+ if (!__efivars->ops) {
+ up(&efivars_lock);
+ return -ENODEV;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(efivar_lock, EFIVAR);
+
+/*
+ * efivar_lock() - obtain the efivar lock if it is free
+ * @return 0 on success, error code on failure
+ */
+int efivar_trylock(void)
+{
+ if (down_trylock(&efivars_lock))
+ return -EBUSY;
+ if (!__efivars->ops) {
+ up(&efivars_lock);
+ return -ENODEV;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(efivar_trylock, EFIVAR);
+
+/*
+ * efivar_unlock() - release the efivar lock
+ */
+void efivar_unlock(void)
+{
+ up(&efivars_lock);
+}
+EXPORT_SYMBOL_NS_GPL(efivar_unlock, EFIVAR);
+
+/*
+ * efivar_get_variable() - retrieve a variable identified by name/vendor
+ *
+ * Must be called with efivars_lock held.
+ */
+efi_status_t efivar_get_variable(efi_char16_t *name, efi_guid_t *vendor,
+ u32 *attr, unsigned long *size, void *data)
+{
+ return __efivars->ops->get_variable(name, vendor, attr, size, data);
+}
+EXPORT_SYMBOL_NS_GPL(efivar_get_variable, EFIVAR);
+
+/*
+ * efivar_get_next_variable() - enumerate the next name/vendor pair
+ *
+ * Must be called with efivars_lock held.
+ */
+efi_status_t efivar_get_next_variable(unsigned long *name_size,
+ efi_char16_t *name, efi_guid_t *vendor)
+{
+ return __efivars->ops->get_next_variable(name_size, name, vendor);
+}
+EXPORT_SYMBOL_NS_GPL(efivar_get_next_variable, EFIVAR);
+
+/*
+ * efivar_set_variable_blocking() - local helper function for set_variable
+ *
+ * Must be called with efivars_lock held.
+ */
+static efi_status_t
+efivar_set_variable_blocking(efi_char16_t *name, efi_guid_t *vendor,
+ u32 attr, unsigned long data_size, void *data)
+{
+ efi_status_t status;
+
+ if (data_size > 0) {
+ status = check_var_size(attr, data_size +
+ ucs2_strsize(name, 1024));
+ if (status != EFI_SUCCESS)
+ return status;
+ }
+ return __efivars->ops->set_variable(name, vendor, attr, data_size, data);
+}
+
+/*
+ * efivar_set_variable_locked() - set a variable identified by name/vendor
+ *
+ * Must be called with efivars_lock held. If @nonblocking is set, it will use
+ * non-blocking primitives so it is guaranteed not to sleep.
+ */
+efi_status_t efivar_set_variable_locked(efi_char16_t *name, efi_guid_t *vendor,
+ u32 attr, unsigned long data_size,
+ void *data, bool nonblocking)
+{
+ efi_set_variable_t *setvar;
+ efi_status_t status;
+
+ if (!nonblocking)
+ return efivar_set_variable_blocking(name, vendor, attr,
+ data_size, data);
+
+ /*
+ * If no _nonblocking variant exists, the ordinary one
+ * is assumed to be non-blocking.
+ */
+ setvar = __efivars->ops->set_variable_nonblocking ?:
+ __efivars->ops->set_variable;
+
+ if (data_size > 0) {
+ status = check_var_size_nonblocking(attr, data_size +
+ ucs2_strsize(name, 1024));
+ if (status != EFI_SUCCESS)
+ return status;
+ }
+ return setvar(name, vendor, attr, data_size, data);
+}
+EXPORT_SYMBOL_NS_GPL(efivar_set_variable_locked, EFIVAR);
+
+/*
+ * efivar_set_variable() - set a variable identified by name/vendor
+ *
+ * Can be called without holding the efivars_lock. Will sleep on obtaining the
+ * lock, or on obtaining other locks that are needed in order to complete the
+ * call.
+ */
+efi_status_t efivar_set_variable(efi_char16_t *name, efi_guid_t *vendor,
+ u32 attr, unsigned long data_size, void *data)
+{
+ efi_status_t status;
+
+ if (efivar_lock())
+ return EFI_ABORTED;
+
+ status = efivar_set_variable_blocking(name, vendor, attr, data_size, data);
+ efivar_unlock();
+ return status;
+}
+EXPORT_SYMBOL_NS_GPL(efivar_set_variable, EFIVAR);
diff --git a/include/linux/efi.h b/include/linux/efi.h
index 56f04b6daeb0..c828ab6f0e2a 100644
--- a/include/linux/efi.h
+++ b/include/linux/efi.h
@@ -1099,6 +1099,26 @@ bool efivar_validate(efi_guid_t vendor, efi_char16_t *var_name, u8 *data,
bool efivar_variable_is_removable(efi_guid_t vendor, const char *name,
size_t len);
+int efivar_lock(void);
+int efivar_trylock(void);
+void efivar_unlock(void);
+
+efi_status_t efivar_get_variable(efi_char16_t *name, efi_guid_t *vendor,
+ u32 *attr, unsigned long *size, void *data);
+
+efi_status_t efivar_get_next_variable(unsigned long *name_size,
+ efi_char16_t *name, efi_guid_t *vendor);
+
+efi_status_t efivar_set_variable_locked(efi_char16_t *name, efi_guid_t *vendor,
+ u32 attr, unsigned long data_size,
+ void *data, bool nonblocking);
+
+efi_status_t efivar_set_variable(efi_char16_t *name, efi_guid_t *vendor,
+ u32 attr, unsigned long data_size, void *data);
+
+efi_status_t check_var_size(u32 attributes, unsigned long size);
+efi_status_t check_var_size_nonblocking(u32 attributes, unsigned long size);
+
#if IS_ENABLED(CONFIG_EFI_CAPSULE_LOADER)
extern bool efi_capsule_pending(int *reset_type);
--
2.35.1
Avoid the efivars layer and simply call the newly introduced EFI
varstore helpers instead. This simplifies the code substantially, and
also allows us to remove some hacks in the shared efivars layer that
were added for efi-pstore specifically.
Since we don't store the name of the associated EFI variable into each
pstore record when enumerating them, we have to guess the variable name
it was constructed from at deletion time, since we no longer keep a
shadow copy of the variable store. To make this a bit more exact, store
the CRC-32 of the ASCII name into the pstore record's ECC region so we
can use it later to make an educated guess regarding the name of the EFI
variable.
Signed-off-by: Ard Biesheuvel <[email protected]>
---
drivers/firmware/efi/efi-pstore.c | 386 ++++++--------------
drivers/firmware/efi/efivars.c | 12 +-
drivers/firmware/efi/vars.c | 12 +-
include/linux/efi.h | 2 -
4 files changed, 119 insertions(+), 293 deletions(-)
diff --git a/drivers/firmware/efi/efi-pstore.c b/drivers/firmware/efi/efi-pstore.c
index 0d80cc7ff6ca..917d3935b58c 100644
--- a/drivers/firmware/efi/efi-pstore.c
+++ b/drivers/firmware/efi/efi-pstore.c
@@ -1,11 +1,14 @@
// SPDX-License-Identifier: GPL-2.0+
+#include <linux/crc32.h>
#include <linux/efi.h>
#include <linux/module.h>
#include <linux/pstore.h>
#include <linux/slab.h>
#include <linux/ucs2_string.h>
+MODULE_IMPORT_NS(EFIVAR);
+
#define DUMP_NAME_LEN 66
#define EFIVARS_DATA_SIZE_MAX 1024
@@ -20,18 +23,25 @@ module_param_named(pstore_disable, efivars_pstore_disable, bool, 0644);
EFI_VARIABLE_BOOTSERVICE_ACCESS | \
EFI_VARIABLE_RUNTIME_ACCESS)
-static LIST_HEAD(efi_pstore_list);
-static DECLARE_WORK(efivar_work, NULL);
-
static int efi_pstore_open(struct pstore_info *psi)
{
- psi->data = NULL;
+ int err;
+
+ err = efivar_lock();
+ if (err)
+ return err;
+
+ psi->data = kzalloc(EFIVARS_DATA_SIZE_MAX, GFP_KERNEL);
+ if (!psi->data)
+ return -ENOMEM;
+
return 0;
}
static int efi_pstore_close(struct pstore_info *psi)
{
- psi->data = NULL;
+ efivar_unlock();
+ kfree(psi->data);
return 0;
}
@@ -40,22 +50,20 @@ static inline u64 generic_id(u64 timestamp, unsigned int part, int count)
return (timestamp * 100 + part) * 1000 + count;
}
-static int efi_pstore_read_func(struct efivar_entry *entry,
- struct pstore_record *record)
+static int efi_pstore_read_func(struct pstore_record *record,
+ efi_char16_t *varname)
{
- efi_guid_t vendor = LINUX_EFI_CRASH_GUID;
+ unsigned long size = EFIVARS_DATA_SIZE_MAX;
char name[DUMP_NAME_LEN], data_type;
+ efi_status_t status;
int i;
int cnt;
unsigned int part;
- unsigned long size;
u64 time;
-
- if (efi_guidcmp(entry->var.VendorGuid, vendor))
- return 0;
+ u32 *crc;
for (i = 0; i < DUMP_NAME_LEN; i++)
- name[i] = entry->var.VariableName[i];
+ name[i] = varname[i];
if (sscanf(name, "dump-type%u-%u-%d-%llu-%c",
&record->type, &part, &cnt, &time, &data_type) == 5) {
@@ -68,7 +76,6 @@ static int efi_pstore_read_func(struct efivar_entry *entry,
record->compressed = true;
else
record->compressed = false;
- record->ecc_notice_size = 0;
} else if (sscanf(name, "dump-type%u-%u-%d-%llu",
&record->type, &part, &cnt, &time) == 4) {
record->id = generic_id(time, part, cnt);
@@ -77,7 +84,6 @@ static int efi_pstore_read_func(struct efivar_entry *entry,
record->time.tv_sec = time;
record->time.tv_nsec = 0;
record->compressed = false;
- record->ecc_notice_size = 0;
} else if (sscanf(name, "dump-type%u-%u-%llu",
&record->type, &part, &time) == 3) {
/*
@@ -91,165 +97,74 @@ static int efi_pstore_read_func(struct efivar_entry *entry,
record->time.tv_sec = time;
record->time.tv_nsec = 0;
record->compressed = false;
- record->ecc_notice_size = 0;
} else
return 0;
- entry->var.DataSize = 1024;
- __efivar_entry_get(entry, &entry->var.Attributes,
- &entry->var.DataSize, entry->var.Data);
- size = entry->var.DataSize;
- memcpy(record->buf, entry->var.Data,
- (size_t)min_t(unsigned long, EFIVARS_DATA_SIZE_MAX, size));
-
- return size;
-}
-
-/**
- * efi_pstore_scan_sysfs_enter
- * @pos: scanning entry
- * @next: next entry
- * @head: list head
- */
-static void efi_pstore_scan_sysfs_enter(struct efivar_entry *pos,
- struct efivar_entry *next,
- struct list_head *head)
-{
- pos->scanning = true;
- if (&next->list != head)
- next->scanning = true;
-}
-
-/**
- * __efi_pstore_scan_sysfs_exit
- * @entry: deleting entry
- * @turn_off_scanning: Check if a scanning flag should be turned off
- */
-static inline int __efi_pstore_scan_sysfs_exit(struct efivar_entry *entry,
- bool turn_off_scanning)
-{
- if (entry->deleting) {
- list_del(&entry->list);
- efivar_entry_iter_end();
- kfree(entry);
- if (efivar_entry_iter_begin())
- return -EINTR;
- } else if (turn_off_scanning)
- entry->scanning = false;
-
- return 0;
-}
-
-/**
- * efi_pstore_scan_sysfs_exit
- * @pos: scanning entry
- * @next: next entry
- * @head: list head
- * @stop: a flag checking if scanning will stop
- */
-static int efi_pstore_scan_sysfs_exit(struct efivar_entry *pos,
- struct efivar_entry *next,
- struct list_head *head, bool stop)
-{
- int ret = __efi_pstore_scan_sysfs_exit(pos, true);
-
- if (ret)
- return ret;
-
- if (stop)
- ret = __efi_pstore_scan_sysfs_exit(next, &next->list != head);
- return ret;
-}
+ record->ecc_notice_size = sizeof(*crc);
+ record->buf = kmalloc(size + record->ecc_notice_size, GFP_KERNEL);
+ if (!record->buf)
+ return -ENOMEM;
-/**
- * efi_pstore_sysfs_entry_iter
- *
- * @record: pstore record to pass to callback
- *
- * You MUST call efivar_entry_iter_begin() before this function, and
- * efivar_entry_iter_end() afterwards.
- *
- */
-static int efi_pstore_sysfs_entry_iter(struct pstore_record *record)
-{
- struct efivar_entry **pos = (struct efivar_entry **)&record->psi->data;
- struct efivar_entry *entry, *n;
- struct list_head *head = &efi_pstore_list;
- int size = 0;
- int ret;
-
- if (!*pos) {
- list_for_each_entry_safe(entry, n, head, list) {
- efi_pstore_scan_sysfs_enter(entry, n, head);
-
- size = efi_pstore_read_func(entry, record);
- ret = efi_pstore_scan_sysfs_exit(entry, n, head,
- size < 0);
- if (ret)
- return ret;
- if (size)
- break;
- }
- *pos = n;
- return size;
+ status = efivar_get_variable(varname, &LINUX_EFI_CRASH_GUID, NULL,
+ &size, record->buf);
+ if (status != EFI_SUCCESS) {
+ kfree(record->buf);
+ return -EIO;
}
- list_for_each_entry_safe_from((*pos), n, head, list) {
- efi_pstore_scan_sysfs_enter((*pos), n, head);
+ /*
+ * Store the CRC-32 of the ASCII name in the record so we can use it to
+ * reconstruct the name when we need to delete the EFI variable later.
+ */
+ crc = (u32 *)&record->buf[size];
+ *crc = crc32_le(U32_MAX, name, strlen(name));
- size = efi_pstore_read_func((*pos), record);
- ret = efi_pstore_scan_sysfs_exit((*pos), n, head, size < 0);
- if (ret)
- return ret;
- if (size)
- break;
- }
- *pos = n;
return size;
}
-/**
- * efi_pstore_read
- *
- * This function returns a size of NVRAM entry logged via efi_pstore_write().
- * The meaning and behavior of efi_pstore/pstore are as below.
- *
- * size > 0: Got data of an entry logged via efi_pstore_write() successfully,
- * and pstore filesystem will continue reading subsequent entries.
- * size == 0: Entry was not logged via efi_pstore_write(),
- * and efi_pstore driver will continue reading subsequent entries.
- * size < 0: Failed to get data of entry logging via efi_pstore_write(),
- * and pstore will stop reading entry.
- */
static ssize_t efi_pstore_read(struct pstore_record *record)
{
- ssize_t size;
+ efi_char16_t *varname = record->psi->data;
+ efi_guid_t guid = LINUX_EFI_CRASH_GUID;
+ unsigned long varname_size;
+ efi_status_t status;
- record->buf = kzalloc(EFIVARS_DATA_SIZE_MAX, GFP_KERNEL);
- if (!record->buf)
- return -ENOMEM;
+ for (;;) {
+ varname_size = EFIVARS_DATA_SIZE_MAX;
- if (efivar_entry_iter_begin()) {
- size = -EINTR;
- goto out;
- }
- size = efi_pstore_sysfs_entry_iter(record);
- efivar_entry_iter_end();
+ /*
+ * If this is the first read() call in the pstore enumeration,
+ * varname will be the empty string, and the GetNextVariable()
+ * runtime service call will return the first EFI variable in
+ * its own enumeration order, ignoring the guid argument.
+ *
+ * Subsequent calls to GetNextVariable() must pass the name and
+ * guid values returned by the previous call, which is why we
+ * store varname in record->psi->data. Given that we only
+ * enumerate variables with the efi-pstore GUID, there is no
+ * need to record the guid return value.
+ */
+ status = efivar_get_next_variable(&varname_size, varname, &guid);
+ if (status == EFI_NOT_FOUND)
+ return 0;
-out:
- if (size <= 0) {
- kfree(record->buf);
- record->buf = NULL;
+ if (status != EFI_SUCCESS)
+ return -EIO;
+
+ /* skip variables that don't concern us */
+ if (efi_guidcmp(guid, LINUX_EFI_CRASH_GUID))
+ continue;
+
+ return efi_pstore_read_func(record, varname);
}
- return size;
}
static int efi_pstore_write(struct pstore_record *record)
{
char name[DUMP_NAME_LEN];
efi_char16_t efi_name[DUMP_NAME_LEN];
- efi_guid_t vendor = LINUX_EFI_CRASH_GUID;
- int i, ret = 0;
+ efi_status_t status;
+ int i;
record->id = generic_id(record->time.tv_sec, record->part,
record->count);
@@ -265,51 +180,62 @@ static int efi_pstore_write(struct pstore_record *record)
for (i = 0; i < DUMP_NAME_LEN; i++)
efi_name[i] = name[i];
- ret = efivar_entry_set_safe(efi_name, vendor, PSTORE_EFI_ATTRIBUTES,
- false, record->size, record->psi->buf);
-
- if (record->reason == KMSG_DUMP_OOPS && try_module_get(THIS_MODULE))
- if (!schedule_work(&efivar_work))
- module_put(THIS_MODULE);
-
- return ret;
+ if (efivar_trylock())
+ return -EBUSY;
+ status = efivar_set_variable_locked(efi_name, &LINUX_EFI_CRASH_GUID,
+ PSTORE_EFI_ATTRIBUTES,
+ record->size, record->psi->buf,
+ true);
+ efivar_unlock();
+ return status == EFI_SUCCESS ? 0 : -EIO;
};
/*
- * Clean up an entry with the same name
+ * Reconstruct the EFI variable name from which we loaded this record based on
+ * the CRC-32 recorded in the entry.
*/
-static int efi_pstore_erase_func(struct efivar_entry *entry, void *data)
+static int efi_pstore_erase_name(struct pstore_record *record, char *name)
{
- efi_char16_t *efi_name = data;
- efi_guid_t vendor = LINUX_EFI_CRASH_GUID;
- unsigned long ucs2_len = ucs2_strlen(efi_name);
+ const u32 *crc = (const u32 *)&record->buf[record->size];
+ unsigned long l;
- if (efi_guidcmp(entry->var.VendorGuid, vendor))
+ l = snprintf(name, DUMP_NAME_LEN, "dump-type%u-%u-%d-%lld-C",
+ record->type, record->part, record->count,
+ (long long)record->time.tv_sec);
+ if (*crc == crc32_le(U32_MAX, name, l))
return 0;
- if (ucs2_strncmp(entry->var.VariableName, efi_name, (size_t)ucs2_len))
+ l = snprintf(name, DUMP_NAME_LEN, "dump-type%u-%u-%d-%lld-D",
+ record->type, record->part, record->count,
+ (long long)record->time.tv_sec);
+ if (*crc == crc32_le(U32_MAX, name, l))
return 0;
- if (entry->scanning) {
- /*
- * Skip deletion because this entry will be deleted
- * after scanning is completed.
- */
- entry->deleting = true;
- } else
- list_del(&entry->list);
+ l = snprintf(name, DUMP_NAME_LEN, "dump-type%u-%u-%d-%lld",
+ record->type, record->part, record->count,
+ (long long)record->time.tv_sec);
+ if (*crc == crc32_le(U32_MAX, name, l))
+ return 0;
- /* found */
- __efivar_entry_delete(entry);
+ l = snprintf(name, DUMP_NAME_LEN, "dump-type%u-%u-%lld",
+ record->type, record->part,
+ (long long)record->time.tv_sec);
+ if (*crc == crc32_le(U32_MAX, name, l))
+ return 0;
- return 1;
+ return -ENODEV;
}
-static int efi_pstore_erase_name(const char *name)
+static int efi_pstore_erase(struct pstore_record *record)
{
- struct efivar_entry *entry = NULL;
efi_char16_t efi_name[DUMP_NAME_LEN];
- int found, i;
+ char name[DUMP_NAME_LEN];
+ efi_status_t status;
+ int i, err;
+
+ err = efi_pstore_erase_name(record, name);
+ if (err)
+ return err;
for (i = 0; i < DUMP_NAME_LEN; i++) {
efi_name[i] = name[i];
@@ -317,36 +243,12 @@ static int efi_pstore_erase_name(const char *name)
break;
}
- if (efivar_entry_iter_begin())
- return -EINTR;
-
- found = __efivar_entry_iter(efi_pstore_erase_func, &efi_pstore_list,
- efi_name, &entry);
- efivar_entry_iter_end();
-
- if (found && !entry->scanning)
- kfree(entry);
-
- return found ? 0 : -ENOENT;
-}
-
-static int efi_pstore_erase(struct pstore_record *record)
-{
- char name[DUMP_NAME_LEN];
- int ret;
-
- snprintf(name, sizeof(name), "dump-type%u-%u-%d-%lld",
- record->type, record->part, record->count,
- (long long)record->time.tv_sec);
- ret = efi_pstore_erase_name(name);
- if (ret != -ENOENT)
- return ret;
-
- snprintf(name, sizeof(name), "dump-type%u-%u-%lld",
- record->type, record->part, (long long)record->time.tv_sec);
- ret = efi_pstore_erase_name(name);
+ status = efivar_set_variable(efi_name, &LINUX_EFI_CRASH_GUID,
+ PSTORE_EFI_ATTRIBUTES, 0, NULL);
- return ret;
+ if (status != EFI_SUCCESS && status != EFI_NOT_FOUND)
+ return -EIO;
+ return 0;
}
static struct pstore_info efi_pstore_info = {
@@ -360,74 +262,14 @@ static struct pstore_info efi_pstore_info = {
.erase = efi_pstore_erase,
};
-static int efi_pstore_callback(efi_char16_t *name, efi_guid_t vendor,
- unsigned long name_size, void *data)
-{
- struct efivar_entry *entry;
-
- entry = kzalloc(sizeof(*entry), GFP_KERNEL);
- if (!entry)
- return -ENOMEM;
-
- memcpy(entry->var.VariableName, name, name_size);
- entry->var.VendorGuid = vendor;
-
- __efivar_entry_add(entry, &efi_pstore_list);
-
- return 0;
-}
-
-static int efi_pstore_update_entry(efi_char16_t *name, efi_guid_t vendor,
- unsigned long name_size, void *data)
-{
- struct efivar_entry *entry = data;
-
- if (efivar_entry_find(name, vendor, &efi_pstore_list, false))
- return 0;
-
- memcpy(entry->var.VariableName, name, name_size);
- memcpy(&(entry->var.VendorGuid), &vendor, sizeof(efi_guid_t));
-
- return 1;
-}
-
-static void efi_pstore_update_entries(struct work_struct *work)
-{
- struct efivar_entry *entry;
- int err;
-
- /* Add new sysfs entries */
- while (1) {
- entry = kzalloc(sizeof(*entry), GFP_KERNEL);
- if (!entry)
- return;
-
- err = efivar_init(efi_pstore_update_entry, entry,
- false, &efi_pstore_list);
- if (!err)
- break;
-
- efivar_entry_add(entry, &efi_pstore_list);
- }
-
- kfree(entry);
- module_put(THIS_MODULE);
-}
-
static __init int efivars_pstore_init(void)
{
- int ret;
-
- if (!efivars_kobject() || !efivar_supports_writes())
+ if (!efivar_supports_writes())
return 0;
if (efivars_pstore_disable)
return 0;
- ret = efivar_init(efi_pstore_callback, NULL, true, &efi_pstore_list);
- if (ret)
- return ret;
-
efi_pstore_info.buf = kmalloc(4096, GFP_KERNEL);
if (!efi_pstore_info.buf)
return -ENOMEM;
@@ -440,8 +282,6 @@ static __init int efivars_pstore_init(void)
efi_pstore_info.bufsize = 0;
}
- INIT_WORK(&efivar_work, efi_pstore_update_entries);
-
return 0;
}
diff --git a/drivers/firmware/efi/efivars.c b/drivers/firmware/efi/efivars.c
index c19db0b35c0d..8341fb15f62e 100644
--- a/drivers/firmware/efi/efivars.c
+++ b/drivers/firmware/efi/efivars.c
@@ -467,16 +467,12 @@ static ssize_t efivar_delete(struct file *filp, struct kobject *kobj,
else if (__efivar_entry_delete(entry))
err = -EIO;
- if (err) {
- efivar_entry_iter_end();
+ efivar_entry_iter_end();
+
+ if (err)
return err;
- }
- if (!entry->scanning) {
- efivar_entry_iter_end();
- efivar_unregister(entry);
- } else
- efivar_entry_iter_end();
+ efivar_unregister(entry);
/* It's dead Jim.... */
return count;
diff --git a/drivers/firmware/efi/vars.c b/drivers/firmware/efi/vars.c
index 41c82614a4b2..5640ffa81544 100644
--- a/drivers/firmware/efi/vars.c
+++ b/drivers/firmware/efi/vars.c
@@ -821,16 +821,8 @@ struct efivar_entry *efivar_entry_find(efi_char16_t *name, efi_guid_t guid,
if (!found)
return NULL;
- if (remove) {
- if (entry->scanning) {
- /*
- * The entry will be deleted
- * after scanning is completed.
- */
- entry->deleting = true;
- } else
- list_del(&entry->list);
- }
+ if (remove)
+ list_del(&entry->list);
return entry;
}
diff --git a/include/linux/efi.h b/include/linux/efi.h
index c828ab6f0e2a..08bc6215e3b4 100644
--- a/include/linux/efi.h
+++ b/include/linux/efi.h
@@ -1043,8 +1043,6 @@ struct efivar_entry {
struct efi_variable var;
struct list_head list;
struct kobject kobj;
- bool scanning;
- bool deleting;
};
static inline void
--
2.35.1
Switch the caching linked-list efivars layer implementation to the newly
introduced efivar get/set variable wrappers, instead of accessing the
lock and the ops pointer directly. This will permit us to move this code
out of the public efivars API, and into efivarfs once the obsolete sysfs
access method is finally removed.
Signed-off-by: Ard Biesheuvel <[email protected]>
---
drivers/firmware/efi/vars.c | 133 ++++++++------------
1 file changed, 52 insertions(+), 81 deletions(-)
diff --git a/drivers/firmware/efi/vars.c b/drivers/firmware/efi/vars.c
index 94de1d0cb4e7..cafc128a5774 100644
--- a/drivers/firmware/efi/vars.c
+++ b/drivers/firmware/efi/vars.c
@@ -408,28 +408,21 @@ static void dup_variable_bug(efi_char16_t *str16, efi_guid_t *vendor_guid,
int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *),
void *data, bool duplicates, struct list_head *head)
{
- const struct efivar_operations *ops;
unsigned long variable_name_size = 1024;
efi_char16_t *variable_name;
efi_status_t status;
efi_guid_t vendor_guid;
int err = 0;
- if (!__efivars)
- return -EFAULT;
-
- ops = __efivars->ops;
-
variable_name = kzalloc(variable_name_size, GFP_KERNEL);
if (!variable_name) {
printk(KERN_ERR "efivars: Memory allocation failed.\n");
return -ENOMEM;
}
- if (down_interruptible(&efivars_lock)) {
- err = -EINTR;
+ err = efivar_lock();
+ if (err)
goto free;
- }
/*
* Per EFI spec, the maximum storage allocated for both
@@ -439,9 +432,9 @@ int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *),
do {
variable_name_size = 1024;
- status = ops->get_next_variable(&variable_name_size,
- variable_name,
- &vendor_guid);
+ status = efivar_get_next_variable(&variable_name_size,
+ variable_name,
+ &vendor_guid);
switch (status) {
case EFI_SUCCESS:
variable_name_size = var_name_strnsize(variable_name,
@@ -483,7 +476,7 @@ int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *),
} while (status != EFI_NOT_FOUND);
- up(&efivars_lock);
+ efivar_unlock();
free:
kfree(variable_name);
@@ -500,10 +493,13 @@ EXPORT_SYMBOL_GPL(efivar_init);
*/
int efivar_entry_add(struct efivar_entry *entry, struct list_head *head)
{
- if (down_interruptible(&efivars_lock))
- return -EINTR;
+ int err;
+
+ err = efivar_lock();
+ if (err)
+ return err;
list_add(&entry->list, head);
- up(&efivars_lock);
+ efivar_unlock();
return 0;
}
@@ -544,7 +540,7 @@ EXPORT_SYMBOL_GPL(efivar_entry_remove);
static void efivar_entry_list_del_unlock(struct efivar_entry *entry)
{
list_del(&entry->list);
- up(&efivars_lock);
+ efivar_unlock();
}
/**
@@ -560,22 +556,18 @@ static void efivar_entry_list_del_unlock(struct efivar_entry *entry)
*/
int efivar_entry_delete(struct efivar_entry *entry)
{
- const struct efivar_operations *ops;
efi_status_t status;
+ int err;
- if (down_interruptible(&efivars_lock))
- return -EINTR;
+ err = efivar_lock();
+ if (err)
+ return err;
- if (!__efivars) {
- up(&efivars_lock);
- return -EINVAL;
- }
- ops = __efivars->ops;
- status = ops->set_variable(entry->var.VariableName,
- &entry->var.VendorGuid,
- 0, 0, NULL);
+ status = efivar_set_variable_locked(entry->var.VariableName,
+ &entry->var.VendorGuid,
+ 0, 0, NULL, false);
if (!(status == EFI_SUCCESS || status == EFI_NOT_FOUND)) {
- up(&efivars_lock);
+ efivar_unlock();
return efi_status_to_err(status);
}
@@ -591,21 +583,18 @@ EXPORT_SYMBOL_GPL(efivar_entry_delete);
*/
int efivar_entry_size(struct efivar_entry *entry, unsigned long *size)
{
- const struct efivar_operations *ops;
efi_status_t status;
+ int err;
*size = 0;
- if (down_interruptible(&efivars_lock))
- return -EINTR;
- if (!__efivars) {
- up(&efivars_lock);
- return -EINVAL;
- }
- ops = __efivars->ops;
- status = ops->get_variable(entry->var.VariableName,
- &entry->var.VendorGuid, NULL, size, NULL);
- up(&efivars_lock);
+ err = efivar_lock();
+ if (err)
+ return err;
+
+ status = efivar_get_variable(entry->var.VariableName,
+ &entry->var.VendorGuid, NULL, size, NULL);
+ efivar_unlock();
if (status != EFI_BUFFER_TOO_SMALL)
return efi_status_to_err(status);
@@ -621,21 +610,16 @@ EXPORT_SYMBOL_GPL(efivar_entry_size);
* @size: size of @data buffer
* @data: buffer to store variable data
*
- * The caller MUST call efivar_entry_iter_begin() and
- * efivar_entry_iter_end() before and after the invocation of this
- * function, respectively.
+ * The caller MUST hold the efivar lock when calling this function.
*/
int __efivar_entry_get(struct efivar_entry *entry, u32 *attributes,
unsigned long *size, void *data)
{
efi_status_t status;
- if (!__efivars)
- return -EINVAL;
-
- status = __efivars->ops->get_variable(entry->var.VariableName,
- &entry->var.VendorGuid,
- attributes, size, data);
+ status = efivar_get_variable(entry->var.VariableName,
+ &entry->var.VendorGuid,
+ attributes, size, data);
return efi_status_to_err(status);
}
@@ -651,22 +635,15 @@ EXPORT_SYMBOL_GPL(__efivar_entry_get);
int efivar_entry_get(struct efivar_entry *entry, u32 *attributes,
unsigned long *size, void *data)
{
- efi_status_t status;
-
- if (down_interruptible(&efivars_lock))
- return -EINTR;
-
- if (!__efivars) {
- up(&efivars_lock);
- return -EINVAL;
- }
+ int err;
- status = __efivars->ops->get_variable(entry->var.VariableName,
- &entry->var.VendorGuid,
- attributes, size, data);
- up(&efivars_lock);
+ err = efivar_lock();
+ if (err)
+ return err;
+ err = __efivar_entry_get(entry, attributes, size, data);
+ efivar_unlock();
- return efi_status_to_err(status);
+ return err;
}
EXPORT_SYMBOL_GPL(efivar_entry_get);
@@ -695,7 +672,6 @@ EXPORT_SYMBOL_GPL(efivar_entry_get);
int efivar_entry_set_get_size(struct efivar_entry *entry, u32 attributes,
unsigned long *size, void *data, bool *set)
{
- const struct efivar_operations *ops;
efi_char16_t *name = entry->var.VariableName;
efi_guid_t *vendor = &entry->var.VendorGuid;
efi_status_t status;
@@ -711,13 +687,9 @@ int efivar_entry_set_get_size(struct efivar_entry *entry, u32 attributes,
* set_variable call, and removal of the variable from the efivars
* list (in the case of an authenticated delete).
*/
- if (down_interruptible(&efivars_lock))
- return -EINTR;
-
- if (!__efivars) {
- err = -EINVAL;
- goto out;
- }
+ err = efivar_lock();
+ if (err)
+ return err;
/*
* Ensure that the available space hasn't shrunk below the safe level
@@ -735,9 +707,8 @@ int efivar_entry_set_get_size(struct efivar_entry *entry, u32 attributes,
}
}
- ops = __efivars->ops;
-
- status = ops->set_variable(name, vendor, attributes, *size, data);
+ status = efivar_set_variable_locked(name, vendor, attributes, *size,
+ data, false);
if (status != EFI_SUCCESS) {
err = efi_status_to_err(status);
goto out;
@@ -752,14 +723,14 @@ int efivar_entry_set_get_size(struct efivar_entry *entry, u32 attributes,
* happened.
*/
*size = 0;
- status = ops->get_variable(entry->var.VariableName,
- &entry->var.VendorGuid,
- NULL, size, NULL);
+ status = efivar_get_variable(entry->var.VariableName,
+ &entry->var.VendorGuid,
+ NULL, size, NULL);
if (status == EFI_NOT_FOUND)
efivar_entry_list_del_unlock(entry);
else
- up(&efivars_lock);
+ efivar_unlock();
if (status && status != EFI_BUFFER_TOO_SMALL)
return efi_status_to_err(status);
@@ -767,7 +738,7 @@ int efivar_entry_set_get_size(struct efivar_entry *entry, u32 attributes,
return 0;
out:
- up(&efivars_lock);
+ efivar_unlock();
return err;
}
@@ -793,7 +764,7 @@ int efivar_entry_iter(int (*func)(struct efivar_entry *, void *),
struct efivar_entry *entry, *n;
int err = 0;
- err = down_interruptible(&efivars_lock);
+ err = efivar_lock();
if (err)
return err;
@@ -802,7 +773,7 @@ int efivar_entry_iter(int (*func)(struct efivar_entry *, void *),
if (err)
break;
}
- up(&efivars_lock);
+ efivar_unlock();
return err;
}
--
2.35.1
Move the fiddly bits of the efivar layer into its only remaining user,
efivarfs, and confine its use to that particular module. All other uses
of the EFI variable store have no need for this additional layer of
complexity, given that they either only read variables, or read and
write variables into a separate GUIDed namespace, and cannot be used to
manipulate EFI variables that are covered by the EFI spec and/or affect
the boot flow.
Signed-off-by: Ard Biesheuvel <[email protected]>
---
drivers/firmware/efi/efi.c | 1 +
drivers/firmware/efi/vars.c | 733 -------------------
fs/efivarfs/Makefile | 2 +-
fs/efivarfs/internal.h | 40 ++
fs/efivarfs/vars.c | 742 ++++++++++++++++++++
include/linux/efi.h | 38 -
6 files changed, 784 insertions(+), 772 deletions(-)
diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c
index 7f06065d3eb0..e4080ad96089 100644
--- a/drivers/firmware/efi/efi.c
+++ b/drivers/firmware/efi/efi.c
@@ -899,6 +899,7 @@ int efi_status_to_err(efi_status_t status)
return err;
}
+EXPORT_SYMBOL_GPL(efi_status_to_err);
static DEFINE_SPINLOCK(efi_mem_reserve_persistent_lock);
static struct linux_efi_memreserve *efi_memreserve_root __ro_after_init;
diff --git a/drivers/firmware/efi/vars.c b/drivers/firmware/efi/vars.c
index cafc128a5774..dd74d2ad3184 100644
--- a/drivers/firmware/efi/vars.c
+++ b/drivers/firmware/efi/vars.c
@@ -6,298 +6,20 @@
* Copyright (C) 2004 Intel Corporation <[email protected]>
*/
-#include <linux/capability.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/init.h>
-#include <linux/mm.h>
#include <linux/module.h>
#include <linux/string.h>
#include <linux/smp.h>
#include <linux/efi.h>
-#include <linux/sysfs.h>
-#include <linux/device.h>
-#include <linux/slab.h>
-#include <linux/ctype.h>
#include <linux/ucs2_string.h>
/* Private pointer to registered efivars */
static struct efivars *__efivars;
-/*
- * efivars_lock protects three things:
- * 1) efivarfs_list and efivars_sysfs_list
- * 2) ->ops calls
- * 3) (un)registration of __efivars
- */
static DEFINE_SEMAPHORE(efivars_lock);
-static bool
-validate_device_path(efi_char16_t *var_name, int match, u8 *buffer,
- unsigned long len)
-{
- struct efi_generic_dev_path *node;
- int offset = 0;
-
- node = (struct efi_generic_dev_path *)buffer;
-
- if (len < sizeof(*node))
- return false;
-
- while (offset <= len - sizeof(*node) &&
- node->length >= sizeof(*node) &&
- node->length <= len - offset) {
- offset += node->length;
-
- if ((node->type == EFI_DEV_END_PATH ||
- node->type == EFI_DEV_END_PATH2) &&
- node->sub_type == EFI_DEV_END_ENTIRE)
- return true;
-
- node = (struct efi_generic_dev_path *)(buffer + offset);
- }
-
- /*
- * If we're here then either node->length pointed past the end
- * of the buffer or we reached the end of the buffer without
- * finding a device path end node.
- */
- return false;
-}
-
-static bool
-validate_boot_order(efi_char16_t *var_name, int match, u8 *buffer,
- unsigned long len)
-{
- /* An array of 16-bit integers */
- if ((len % 2) != 0)
- return false;
-
- return true;
-}
-
-static bool
-validate_load_option(efi_char16_t *var_name, int match, u8 *buffer,
- unsigned long len)
-{
- u16 filepathlength;
- int i, desclength = 0, namelen;
-
- namelen = ucs2_strnlen(var_name, EFI_VAR_NAME_LEN);
-
- /* Either "Boot" or "Driver" followed by four digits of hex */
- for (i = match; i < match+4; i++) {
- if (var_name[i] > 127 ||
- hex_to_bin(var_name[i] & 0xff) < 0)
- return true;
- }
-
- /* Reject it if there's 4 digits of hex and then further content */
- if (namelen > match + 4)
- return false;
-
- /* A valid entry must be at least 8 bytes */
- if (len < 8)
- return false;
-
- filepathlength = buffer[4] | buffer[5] << 8;
-
- /*
- * There's no stored length for the description, so it has to be
- * found by hand
- */
- desclength = ucs2_strsize((efi_char16_t *)(buffer + 6), len - 6) + 2;
-
- /* Each boot entry must have a descriptor */
- if (!desclength)
- return false;
-
- /*
- * If the sum of the length of the description, the claimed filepath
- * length and the original header are greater than the length of the
- * variable, it's malformed
- */
- if ((desclength + filepathlength + 6) > len)
- return false;
-
- /*
- * And, finally, check the filepath
- */
- return validate_device_path(var_name, match, buffer + desclength + 6,
- filepathlength);
-}
-
-static bool
-validate_uint16(efi_char16_t *var_name, int match, u8 *buffer,
- unsigned long len)
-{
- /* A single 16-bit integer */
- if (len != 2)
- return false;
-
- return true;
-}
-
-static bool
-validate_ascii_string(efi_char16_t *var_name, int match, u8 *buffer,
- unsigned long len)
-{
- int i;
-
- for (i = 0; i < len; i++) {
- if (buffer[i] > 127)
- return false;
-
- if (buffer[i] == 0)
- return true;
- }
-
- return false;
-}
-
-struct variable_validate {
- efi_guid_t vendor;
- char *name;
- bool (*validate)(efi_char16_t *var_name, int match, u8 *data,
- unsigned long len);
-};
-
-/*
- * This is the list of variables we need to validate, as well as the
- * whitelist for what we think is safe not to default to immutable.
- *
- * If it has a validate() method that's not NULL, it'll go into the
- * validation routine. If not, it is assumed valid, but still used for
- * whitelisting.
- *
- * Note that it's sorted by {vendor,name}, but globbed names must come after
- * any other name with the same prefix.
- */
-static const struct variable_validate variable_validate[] = {
- { EFI_GLOBAL_VARIABLE_GUID, "BootNext", validate_uint16 },
- { EFI_GLOBAL_VARIABLE_GUID, "BootOrder", validate_boot_order },
- { EFI_GLOBAL_VARIABLE_GUID, "Boot*", validate_load_option },
- { EFI_GLOBAL_VARIABLE_GUID, "DriverOrder", validate_boot_order },
- { EFI_GLOBAL_VARIABLE_GUID, "Driver*", validate_load_option },
- { EFI_GLOBAL_VARIABLE_GUID, "ConIn", validate_device_path },
- { EFI_GLOBAL_VARIABLE_GUID, "ConInDev", validate_device_path },
- { EFI_GLOBAL_VARIABLE_GUID, "ConOut", validate_device_path },
- { EFI_GLOBAL_VARIABLE_GUID, "ConOutDev", validate_device_path },
- { EFI_GLOBAL_VARIABLE_GUID, "ErrOut", validate_device_path },
- { EFI_GLOBAL_VARIABLE_GUID, "ErrOutDev", validate_device_path },
- { EFI_GLOBAL_VARIABLE_GUID, "Lang", validate_ascii_string },
- { EFI_GLOBAL_VARIABLE_GUID, "OsIndications", NULL },
- { EFI_GLOBAL_VARIABLE_GUID, "PlatformLang", validate_ascii_string },
- { EFI_GLOBAL_VARIABLE_GUID, "Timeout", validate_uint16 },
- { LINUX_EFI_CRASH_GUID, "*", NULL },
- { NULL_GUID, "", NULL },
-};
-
-/*
- * Check if @var_name matches the pattern given in @match_name.
- *
- * @var_name: an array of @len non-NUL characters.
- * @match_name: a NUL-terminated pattern string, optionally ending in "*". A
- * final "*" character matches any trailing characters @var_name,
- * including the case when there are none left in @var_name.
- * @match: on output, the number of non-wildcard characters in @match_name
- * that @var_name matches, regardless of the return value.
- * @return: whether @var_name fully matches @match_name.
- */
-static bool
-variable_matches(const char *var_name, size_t len, const char *match_name,
- int *match)
-{
- for (*match = 0; ; (*match)++) {
- char c = match_name[*match];
-
- switch (c) {
- case '*':
- /* Wildcard in @match_name means we've matched. */
- return true;
-
- case '\0':
- /* @match_name has ended. Has @var_name too? */
- return (*match == len);
-
- default:
- /*
- * We've reached a non-wildcard char in @match_name.
- * Continue only if there's an identical character in
- * @var_name.
- */
- if (*match < len && c == var_name[*match])
- continue;
- return false;
- }
- }
-}
-
-bool
-efivar_validate(efi_guid_t vendor, efi_char16_t *var_name, u8 *data,
- unsigned long data_size)
-{
- int i;
- unsigned long utf8_size;
- u8 *utf8_name;
-
- utf8_size = ucs2_utf8size(var_name);
- utf8_name = kmalloc(utf8_size + 1, GFP_KERNEL);
- if (!utf8_name)
- return false;
-
- ucs2_as_utf8(utf8_name, var_name, utf8_size);
- utf8_name[utf8_size] = '\0';
-
- for (i = 0; variable_validate[i].name[0] != '\0'; i++) {
- const char *name = variable_validate[i].name;
- int match = 0;
-
- if (efi_guidcmp(vendor, variable_validate[i].vendor))
- continue;
-
- if (variable_matches(utf8_name, utf8_size+1, name, &match)) {
- if (variable_validate[i].validate == NULL)
- break;
- kfree(utf8_name);
- return variable_validate[i].validate(var_name, match,
- data, data_size);
- }
- }
- kfree(utf8_name);
- return true;
-}
-EXPORT_SYMBOL_GPL(efivar_validate);
-
-bool
-efivar_variable_is_removable(efi_guid_t vendor, const char *var_name,
- size_t len)
-{
- int i;
- bool found = false;
- int match = 0;
-
- /*
- * Check if our variable is in the validated variables list
- */
- for (i = 0; variable_validate[i].name[0] != '\0'; i++) {
- if (efi_guidcmp(variable_validate[i].vendor, vendor))
- continue;
-
- if (variable_matches(var_name, len,
- variable_validate[i].name, &match)) {
- found = true;
- break;
- }
- }
-
- /*
- * If it's in our list, it is removable.
- */
- return found;
-}
-EXPORT_SYMBOL_GPL(efivar_variable_is_removable);
-
efi_status_t check_var_size(u32 attributes, unsigned long size)
{
const struct efivar_operations *fops;
@@ -324,461 +46,6 @@ efi_status_t check_var_size_nonblocking(u32 attributes, unsigned long size)
}
EXPORT_SYMBOL_NS_GPL(check_var_size_nonblocking, EFIVAR);
-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, 1024);
- list_for_each_entry_safe(entry, n, head, list) {
- strsize2 = ucs2_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;
-}
-
-/*
- * Returns the size of variable_name, in bytes, including the
- * terminating NULL character, or variable_name_size if no NULL
- * character is found among the first variable_name_size bytes.
- */
-static unsigned long var_name_strnsize(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;
- }
-
- return min(len, variable_name_size);
-}
-
-/*
- * Print a warning when duplicate EFI variables are encountered and
- * disable the sysfs workqueue since the firmware is buggy.
- */
-static void dup_variable_bug(efi_char16_t *str16, efi_guid_t *vendor_guid,
- unsigned long len16)
-{
- size_t i, len8 = len16 / sizeof(efi_char16_t);
- char *str8;
-
- str8 = kzalloc(len8, GFP_KERNEL);
- if (!str8)
- return;
-
- for (i = 0; i < len8; i++)
- str8[i] = str16[i];
-
- printk(KERN_WARNING "efivars: duplicate variable: %s-%pUl\n",
- str8, vendor_guid);
- kfree(str8);
-}
-
-/**
- * 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
- * @duplicates: error if we encounter duplicates on @head?
- * @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.
- *
- * 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 *),
- void *data, bool duplicates, struct list_head *head)
-{
- unsigned long variable_name_size = 1024;
- efi_char16_t *variable_name;
- efi_status_t status;
- efi_guid_t vendor_guid;
- int err = 0;
-
- variable_name = kzalloc(variable_name_size, GFP_KERNEL);
- if (!variable_name) {
- printk(KERN_ERR "efivars: Memory allocation failed.\n");
- return -ENOMEM;
- }
-
- err = efivar_lock();
- if (err)
- goto free;
-
- /*
- * Per EFI spec, the maximum storage allocated for both
- * the variable name and variable data is 1024 bytes.
- */
-
- do {
- variable_name_size = 1024;
-
- status = efivar_get_next_variable(&variable_name_size,
- variable_name,
- &vendor_guid);
- switch (status) {
- case EFI_SUCCESS:
- variable_name_size = var_name_strnsize(variable_name,
- variable_name_size);
-
- /*
- * Some firmware implementations return the
- * same variable name on multiple calls to
- * get_next_variable(). Terminate the loop
- * immediately as there is no guarantee that
- * we'll ever see a different variable name,
- * and may end up looping here forever.
- */
- if (duplicates &&
- variable_is_present(variable_name, &vendor_guid,
- head)) {
- 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);
- if (err)
- status = EFI_NOT_FOUND;
- }
- break;
- case EFI_UNSUPPORTED:
- err = -EOPNOTSUPP;
- status = EFI_NOT_FOUND;
- break;
- case EFI_NOT_FOUND:
- break;
- default:
- printk(KERN_WARNING "efivars: get_next_variable: status=%lx\n",
- status);
- status = EFI_NOT_FOUND;
- break;
- }
-
- } while (status != EFI_NOT_FOUND);
-
- efivar_unlock();
-free:
- kfree(variable_name);
-
- return err;
-}
-EXPORT_SYMBOL_GPL(efivar_init);
-
-/**
- * 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;
-}
-EXPORT_SYMBOL_GPL(efivar_entry_add);
-
-/**
- * __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);
-}
-EXPORT_SYMBOL_GPL(__efivar_entry_add);
-
-/**
- * efivar_entry_remove - remove entry from variable list
- * @entry: entry to remove from list
- */
-void efivar_entry_remove(struct efivar_entry *entry)
-{
- list_del(&entry->list);
-}
-EXPORT_SYMBOL_GPL(efivar_entry_remove);
-
-/*
- * 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
- * @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.
- *
- * Returns 0 on success, -EINTR if we can't grab the semaphore,
- * converted EFI status code if set_variable() fails.
- */
-int efivar_entry_delete(struct efivar_entry *entry)
-{
- efi_status_t status;
- int err;
-
- err = efivar_lock();
- if (err)
- return err;
-
- 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();
- return efi_status_to_err(status);
- }
-
- efivar_entry_list_del_unlock(entry);
- return 0;
-}
-EXPORT_SYMBOL_GPL(efivar_entry_delete);
-
-/**
- * efivar_entry_size - obtain the size of a variable
- * @entry: entry for this variable
- * @size: location to store the variable's size
- */
-int efivar_entry_size(struct efivar_entry *entry, unsigned long *size)
-{
- efi_status_t status;
- int err;
-
- *size = 0;
-
- err = efivar_lock();
- if (err)
- return err;
-
- status = efivar_get_variable(entry->var.VariableName,
- &entry->var.VendorGuid, NULL, size, NULL);
- efivar_unlock();
-
- if (status != EFI_BUFFER_TOO_SMALL)
- return efi_status_to_err(status);
-
- return 0;
-}
-EXPORT_SYMBOL_GPL(efivar_entry_size);
-
-/**
- * __efivar_entry_get - call get_variable()
- * @entry: read data for this variable
- * @attributes: variable attributes
- * @size: size of @data buffer
- * @data: buffer to store variable data
- *
- * The caller MUST hold the efivar lock when calling this function.
- */
-int __efivar_entry_get(struct efivar_entry *entry, u32 *attributes,
- unsigned long *size, void *data)
-{
- efi_status_t status;
-
- status = efivar_get_variable(entry->var.VariableName,
- &entry->var.VendorGuid,
- attributes, size, data);
-
- return efi_status_to_err(status);
-}
-EXPORT_SYMBOL_GPL(__efivar_entry_get);
-
-/**
- * efivar_entry_get - call get_variable()
- * @entry: read data for this variable
- * @attributes: variable attributes
- * @size: size of @data buffer
- * @data: buffer to store variable data
- */
-int efivar_entry_get(struct efivar_entry *entry, u32 *attributes,
- unsigned long *size, void *data)
-{
- int err;
-
- err = efivar_lock();
- if (err)
- return err;
- err = __efivar_entry_get(entry, attributes, size, data);
- efivar_unlock();
-
- return err;
-}
-EXPORT_SYMBOL_GPL(efivar_entry_get);
-
-/**
- * efivar_entry_set_get_size - call set_variable() and get new size (atomic)
- * @entry: entry containing variable to set and get
- * @attributes: attributes of variable to be written
- * @size: size of data buffer
- * @data: buffer containing data to write
- * @set: did the set_variable() call succeed?
- *
- * This is a pretty special (complex) function. See efivarfs_file_write().
- *
- * Atomically call set_variable() for @entry and if the call is
- * successful, return the new size of the variable from get_variable()
- * in @size. The success of set_variable() is indicated by @set.
- *
- * Returns 0 on success, -EINVAL if the variable data is invalid,
- * -ENOSPC if the firmware does not have enough available space, or a
- * converted EFI status code if either of set_variable() or
- * get_variable() fail.
- *
- * If the EFI variable does not exist when calling set_variable()
- * (EFI_NOT_FOUND), @entry is removed from the variable list.
- */
-int efivar_entry_set_get_size(struct efivar_entry *entry, u32 attributes,
- unsigned long *size, void *data, bool *set)
-{
- efi_char16_t *name = entry->var.VariableName;
- efi_guid_t *vendor = &entry->var.VendorGuid;
- efi_status_t status;
- int err;
-
- *set = false;
-
- if (efivar_validate(*vendor, name, data, *size) == false)
- 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).
- */
- err = efivar_lock();
- if (err)
- return err;
-
- /*
- * Ensure that the available space hasn't shrunk below the safe level
- */
- status = check_var_size(attributes, *size + ucs2_strsize(name, 1024));
- if (status != EFI_SUCCESS) {
- if (status != EFI_UNSUPPORTED) {
- err = efi_status_to_err(status);
- goto out;
- }
-
- if (*size > 65536) {
- err = -ENOSPC;
- goto out;
- }
- }
-
- status = efivar_set_variable_locked(name, vendor, attributes, *size,
- data, false);
- if (status != EFI_SUCCESS) {
- err = efi_status_to_err(status);
- goto out;
- }
-
- *set = true;
-
- /*
- * 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.
- */
- *size = 0;
- status = efivar_get_variable(entry->var.VariableName,
- &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)
- return efi_status_to_err(status);
-
- return 0;
-
-out:
- efivar_unlock();
- return err;
-
-}
-EXPORT_SYMBOL_GPL(efivar_entry_set_get_size);
-
-/**
- * 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;
-}
-EXPORT_SYMBOL_GPL(efivar_entry_iter);
-
/**
* efivars_kobject - get the kobject for the registered efivars
*
diff --git a/fs/efivarfs/Makefile b/fs/efivarfs/Makefile
index 0b1c5e63eb71..7bfc2f9754a8 100644
--- a/fs/efivarfs/Makefile
+++ b/fs/efivarfs/Makefile
@@ -5,4 +5,4 @@
obj-$(CONFIG_EFIVAR_FS) += efivarfs.o
-efivarfs-objs := inode.o file.o super.o
+efivarfs-objs := inode.o file.o super.o vars.o
diff --git a/fs/efivarfs/internal.h b/fs/efivarfs/internal.h
index 30ae44cb7453..8ebf3a6a8aa2 100644
--- a/fs/efivarfs/internal.h
+++ b/fs/efivarfs/internal.h
@@ -7,6 +7,46 @@
#define EFIVAR_FS_INTERNAL_H
#include <linux/list.h>
+#include <linux/efi.h>
+
+struct efi_variable {
+ efi_char16_t VariableName[EFI_VAR_NAME_LEN/sizeof(efi_char16_t)];
+ efi_guid_t VendorGuid;
+ unsigned long DataSize;
+ __u8 Data[1024];
+ efi_status_t Status;
+ __u32 Attributes;
+} __attribute__((packed));
+
+struct efivar_entry {
+ struct efi_variable var;
+ struct list_head list;
+ struct kobject kobj;
+};
+
+int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *),
+ void *data, bool duplicates, struct list_head *head);
+
+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);
+int __efivar_entry_get(struct efivar_entry *entry, u32 *attributes,
+ unsigned long *size, void *data);
+int efivar_entry_get(struct efivar_entry *entry, u32 *attributes,
+ unsigned long *size, void *data);
+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);
extern const struct file_operations efivarfs_file_operations;
extern const struct inode_operations efivarfs_dir_inode_operations;
diff --git a/fs/efivarfs/vars.c b/fs/efivarfs/vars.c
new file mode 100644
index 000000000000..b22141dcc4d3
--- /dev/null
+++ b/fs/efivarfs/vars.c
@@ -0,0 +1,742 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Originally from efivars.c
+ *
+ * Copyright (C) 2001,2003,2004 Dell <[email protected]>
+ * Copyright (C) 2004 Intel Corporation <[email protected]>
+ */
+
+#include <linux/capability.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/smp.h>
+#include <linux/efi.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/ctype.h>
+#include <linux/ucs2_string.h>
+
+#include "internal.h"
+
+MODULE_IMPORT_NS(EFIVAR);
+
+static bool
+validate_device_path(efi_char16_t *var_name, int match, u8 *buffer,
+ unsigned long len)
+{
+ struct efi_generic_dev_path *node;
+ int offset = 0;
+
+ node = (struct efi_generic_dev_path *)buffer;
+
+ if (len < sizeof(*node))
+ return false;
+
+ while (offset <= len - sizeof(*node) &&
+ node->length >= sizeof(*node) &&
+ node->length <= len - offset) {
+ offset += node->length;
+
+ if ((node->type == EFI_DEV_END_PATH ||
+ node->type == EFI_DEV_END_PATH2) &&
+ node->sub_type == EFI_DEV_END_ENTIRE)
+ return true;
+
+ node = (struct efi_generic_dev_path *)(buffer + offset);
+ }
+
+ /*
+ * If we're here then either node->length pointed past the end
+ * of the buffer or we reached the end of the buffer without
+ * finding a device path end node.
+ */
+ return false;
+}
+
+static bool
+validate_boot_order(efi_char16_t *var_name, int match, u8 *buffer,
+ unsigned long len)
+{
+ /* An array of 16-bit integers */
+ if ((len % 2) != 0)
+ return false;
+
+ return true;
+}
+
+static bool
+validate_load_option(efi_char16_t *var_name, int match, u8 *buffer,
+ unsigned long len)
+{
+ u16 filepathlength;
+ int i, desclength = 0, namelen;
+
+ namelen = ucs2_strnlen(var_name, EFI_VAR_NAME_LEN);
+
+ /* Either "Boot" or "Driver" followed by four digits of hex */
+ for (i = match; i < match+4; i++) {
+ if (var_name[i] > 127 ||
+ hex_to_bin(var_name[i] & 0xff) < 0)
+ return true;
+ }
+
+ /* Reject it if there's 4 digits of hex and then further content */
+ if (namelen > match + 4)
+ return false;
+
+ /* A valid entry must be at least 8 bytes */
+ if (len < 8)
+ return false;
+
+ filepathlength = buffer[4] | buffer[5] << 8;
+
+ /*
+ * There's no stored length for the description, so it has to be
+ * found by hand
+ */
+ desclength = ucs2_strsize((efi_char16_t *)(buffer + 6), len - 6) + 2;
+
+ /* Each boot entry must have a descriptor */
+ if (!desclength)
+ return false;
+
+ /*
+ * If the sum of the length of the description, the claimed filepath
+ * length and the original header are greater than the length of the
+ * variable, it's malformed
+ */
+ if ((desclength + filepathlength + 6) > len)
+ return false;
+
+ /*
+ * And, finally, check the filepath
+ */
+ return validate_device_path(var_name, match, buffer + desclength + 6,
+ filepathlength);
+}
+
+static bool
+validate_uint16(efi_char16_t *var_name, int match, u8 *buffer,
+ unsigned long len)
+{
+ /* A single 16-bit integer */
+ if (len != 2)
+ return false;
+
+ return true;
+}
+
+static bool
+validate_ascii_string(efi_char16_t *var_name, int match, u8 *buffer,
+ unsigned long len)
+{
+ int i;
+
+ for (i = 0; i < len; i++) {
+ if (buffer[i] > 127)
+ return false;
+
+ if (buffer[i] == 0)
+ return true;
+ }
+
+ return false;
+}
+
+struct variable_validate {
+ efi_guid_t vendor;
+ char *name;
+ bool (*validate)(efi_char16_t *var_name, int match, u8 *data,
+ unsigned long len);
+};
+
+/*
+ * This is the list of variables we need to validate, as well as the
+ * whitelist for what we think is safe not to default to immutable.
+ *
+ * If it has a validate() method that's not NULL, it'll go into the
+ * validation routine. If not, it is assumed valid, but still used for
+ * whitelisting.
+ *
+ * Note that it's sorted by {vendor,name}, but globbed names must come after
+ * any other name with the same prefix.
+ */
+static const struct variable_validate variable_validate[] = {
+ { EFI_GLOBAL_VARIABLE_GUID, "BootNext", validate_uint16 },
+ { EFI_GLOBAL_VARIABLE_GUID, "BootOrder", validate_boot_order },
+ { EFI_GLOBAL_VARIABLE_GUID, "Boot*", validate_load_option },
+ { EFI_GLOBAL_VARIABLE_GUID, "DriverOrder", validate_boot_order },
+ { EFI_GLOBAL_VARIABLE_GUID, "Driver*", validate_load_option },
+ { EFI_GLOBAL_VARIABLE_GUID, "ConIn", validate_device_path },
+ { EFI_GLOBAL_VARIABLE_GUID, "ConInDev", validate_device_path },
+ { EFI_GLOBAL_VARIABLE_GUID, "ConOut", validate_device_path },
+ { EFI_GLOBAL_VARIABLE_GUID, "ConOutDev", validate_device_path },
+ { EFI_GLOBAL_VARIABLE_GUID, "ErrOut", validate_device_path },
+ { EFI_GLOBAL_VARIABLE_GUID, "ErrOutDev", validate_device_path },
+ { EFI_GLOBAL_VARIABLE_GUID, "Lang", validate_ascii_string },
+ { EFI_GLOBAL_VARIABLE_GUID, "OsIndications", NULL },
+ { EFI_GLOBAL_VARIABLE_GUID, "PlatformLang", validate_ascii_string },
+ { EFI_GLOBAL_VARIABLE_GUID, "Timeout", validate_uint16 },
+ { LINUX_EFI_CRASH_GUID, "*", NULL },
+ { NULL_GUID, "", NULL },
+};
+
+/*
+ * Check if @var_name matches the pattern given in @match_name.
+ *
+ * @var_name: an array of @len non-NUL characters.
+ * @match_name: a NUL-terminated pattern string, optionally ending in "*". A
+ * final "*" character matches any trailing characters @var_name,
+ * including the case when there are none left in @var_name.
+ * @match: on output, the number of non-wildcard characters in @match_name
+ * that @var_name matches, regardless of the return value.
+ * @return: whether @var_name fully matches @match_name.
+ */
+static bool
+variable_matches(const char *var_name, size_t len, const char *match_name,
+ int *match)
+{
+ for (*match = 0; ; (*match)++) {
+ char c = match_name[*match];
+
+ switch (c) {
+ case '*':
+ /* Wildcard in @match_name means we've matched. */
+ return true;
+
+ case '\0':
+ /* @match_name has ended. Has @var_name too? */
+ return (*match == len);
+
+ default:
+ /*
+ * We've reached a non-wildcard char in @match_name.
+ * Continue only if there's an identical character in
+ * @var_name.
+ */
+ if (*match < len && c == var_name[*match])
+ continue;
+ return false;
+ }
+ }
+}
+
+bool
+efivar_validate(efi_guid_t vendor, efi_char16_t *var_name, u8 *data,
+ unsigned long data_size)
+{
+ int i;
+ unsigned long utf8_size;
+ u8 *utf8_name;
+
+ utf8_size = ucs2_utf8size(var_name);
+ utf8_name = kmalloc(utf8_size + 1, GFP_KERNEL);
+ if (!utf8_name)
+ return false;
+
+ ucs2_as_utf8(utf8_name, var_name, utf8_size);
+ utf8_name[utf8_size] = '\0';
+
+ for (i = 0; variable_validate[i].name[0] != '\0'; i++) {
+ const char *name = variable_validate[i].name;
+ int match = 0;
+
+ if (efi_guidcmp(vendor, variable_validate[i].vendor))
+ continue;
+
+ if (variable_matches(utf8_name, utf8_size+1, name, &match)) {
+ if (variable_validate[i].validate == NULL)
+ break;
+ kfree(utf8_name);
+ return variable_validate[i].validate(var_name, match,
+ data, data_size);
+ }
+ }
+ kfree(utf8_name);
+ return true;
+}
+
+bool
+efivar_variable_is_removable(efi_guid_t vendor, const char *var_name,
+ size_t len)
+{
+ int i;
+ bool found = false;
+ int match = 0;
+
+ /*
+ * Check if our variable is in the validated variables list
+ */
+ for (i = 0; variable_validate[i].name[0] != '\0'; i++) {
+ if (efi_guidcmp(variable_validate[i].vendor, vendor))
+ continue;
+
+ if (variable_matches(var_name, len,
+ variable_validate[i].name, &match)) {
+ found = true;
+ break;
+ }
+ }
+
+ /*
+ * If it's in our list, it is removable.
+ */
+ 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, 1024);
+ list_for_each_entry_safe(entry, n, head, list) {
+ strsize2 = ucs2_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;
+}
+
+/*
+ * Returns the size of variable_name, in bytes, including the
+ * terminating NULL character, or variable_name_size if no NULL
+ * character is found among the first variable_name_size bytes.
+ */
+static unsigned long var_name_strnsize(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;
+ }
+
+ return min(len, variable_name_size);
+}
+
+/*
+ * Print a warning when duplicate EFI variables are encountered and
+ * disable the sysfs workqueue since the firmware is buggy.
+ */
+static void dup_variable_bug(efi_char16_t *str16, efi_guid_t *vendor_guid,
+ unsigned long len16)
+{
+ size_t i, len8 = len16 / sizeof(efi_char16_t);
+ char *str8;
+
+ str8 = kzalloc(len8, GFP_KERNEL);
+ if (!str8)
+ return;
+
+ for (i = 0; i < len8; i++)
+ str8[i] = str16[i];
+
+ printk(KERN_WARNING "efivars: duplicate variable: %s-%pUl\n",
+ str8, vendor_guid);
+ kfree(str8);
+}
+
+/**
+ * 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
+ * @duplicates: error if we encounter duplicates on @head?
+ * @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.
+ *
+ * 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 *),
+ void *data, bool duplicates, struct list_head *head)
+{
+ unsigned long variable_name_size = 1024;
+ efi_char16_t *variable_name;
+ efi_status_t status;
+ efi_guid_t vendor_guid;
+ int err = 0;
+
+ variable_name = kzalloc(variable_name_size, GFP_KERNEL);
+ if (!variable_name) {
+ printk(KERN_ERR "efivars: Memory allocation failed.\n");
+ return -ENOMEM;
+ }
+
+ err = efivar_lock();
+ if (err)
+ goto free;
+
+ /*
+ * Per EFI spec, the maximum storage allocated for both
+ * the variable name and variable data is 1024 bytes.
+ */
+
+ do {
+ variable_name_size = 1024;
+
+ status = efivar_get_next_variable(&variable_name_size,
+ variable_name,
+ &vendor_guid);
+ switch (status) {
+ case EFI_SUCCESS:
+ variable_name_size = var_name_strnsize(variable_name,
+ variable_name_size);
+
+ /*
+ * Some firmware implementations return the
+ * same variable name on multiple calls to
+ * get_next_variable(). Terminate the loop
+ * immediately as there is no guarantee that
+ * we'll ever see a different variable name,
+ * and may end up looping here forever.
+ */
+ if (duplicates &&
+ variable_is_present(variable_name, &vendor_guid,
+ head)) {
+ 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);
+ if (err)
+ status = EFI_NOT_FOUND;
+ }
+ break;
+ case EFI_UNSUPPORTED:
+ err = -EOPNOTSUPP;
+ status = EFI_NOT_FOUND;
+ break;
+ case EFI_NOT_FOUND:
+ break;
+ default:
+ printk(KERN_WARNING "efivars: get_next_variable: status=%lx\n",
+ status);
+ status = EFI_NOT_FOUND;
+ break;
+ }
+
+ } while (status != EFI_NOT_FOUND);
+
+ efivar_unlock();
+free:
+ kfree(variable_name);
+
+ return err;
+}
+
+/**
+ * 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;
+}
+EXPORT_SYMBOL_GPL(efivar_entry_add);
+
+/**
+ * __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);
+}
+EXPORT_SYMBOL_GPL(__efivar_entry_add);
+
+/**
+ * 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);
+}
+EXPORT_SYMBOL_GPL(efivar_entry_remove);
+
+/*
+ * 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
+ * @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.
+ *
+ * Returns 0 on success, -EINTR if we can't grab the semaphore,
+ * converted EFI status code if set_variable() fails.
+ */
+int efivar_entry_delete(struct efivar_entry *entry)
+{
+ efi_status_t status;
+ int err;
+
+ err = efivar_lock();
+ if (err)
+ return err;
+
+ 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();
+ return efi_status_to_err(status);
+ }
+
+ efivar_entry_list_del_unlock(entry);
+ return 0;
+}
+
+/**
+ * efivar_entry_size - obtain the size of a variable
+ * @entry: entry for this variable
+ * @size: location to store the variable's size
+ */
+int efivar_entry_size(struct efivar_entry *entry, unsigned long *size)
+{
+ efi_status_t status;
+ int err;
+
+ *size = 0;
+
+ err = efivar_lock();
+ if (err)
+ return err;
+
+ status = efivar_get_variable(entry->var.VariableName,
+ &entry->var.VendorGuid, NULL, size, NULL);
+ efivar_unlock();
+
+ if (status != EFI_BUFFER_TOO_SMALL)
+ return efi_status_to_err(status);
+
+ return 0;
+}
+
+/**
+ * __efivar_entry_get - call get_variable()
+ * @entry: read data for this variable
+ * @attributes: variable attributes
+ * @size: size of @data buffer
+ * @data: buffer to store variable data
+ *
+ * The caller MUST call efivar_entry_iter_begin() and
+ * efivar_entry_iter_end() before and after the invocation of this
+ * function, respectively.
+ */
+int __efivar_entry_get(struct efivar_entry *entry, u32 *attributes,
+ unsigned long *size, void *data)
+{
+ efi_status_t status;
+
+ status = efivar_get_variable(entry->var.VariableName,
+ &entry->var.VendorGuid,
+ attributes, size, data);
+
+ return efi_status_to_err(status);
+}
+
+/**
+ * efivar_entry_get - call get_variable()
+ * @entry: read data for this variable
+ * @attributes: variable attributes
+ * @size: size of @data buffer
+ * @data: buffer to store variable data
+ */
+int efivar_entry_get(struct efivar_entry *entry, u32 *attributes,
+ unsigned long *size, void *data)
+{
+ int err;
+
+ err = efivar_lock();
+ if (err)
+ return err;
+ err = __efivar_entry_get(entry, attributes, size, data);
+ efivar_unlock();
+
+ return 0;
+}
+
+/**
+ * efivar_entry_set_get_size - call set_variable() and get new size (atomic)
+ * @entry: entry containing variable to set and get
+ * @attributes: attributes of variable to be written
+ * @size: size of data buffer
+ * @data: buffer containing data to write
+ * @set: did the set_variable() call succeed?
+ *
+ * This is a pretty special (complex) function. See efivarfs_file_write().
+ *
+ * Atomically call set_variable() for @entry and if the call is
+ * successful, return the new size of the variable from get_variable()
+ * in @size. The success of set_variable() is indicated by @set.
+ *
+ * Returns 0 on success, -EINVAL if the variable data is invalid,
+ * -ENOSPC if the firmware does not have enough available space, or a
+ * converted EFI status code if either of set_variable() or
+ * get_variable() fail.
+ *
+ * If the EFI variable does not exist when calling set_variable()
+ * (EFI_NOT_FOUND), @entry is removed from the variable list.
+ */
+int efivar_entry_set_get_size(struct efivar_entry *entry, u32 attributes,
+ unsigned long *size, void *data, bool *set)
+{
+ efi_char16_t *name = entry->var.VariableName;
+ efi_guid_t *vendor = &entry->var.VendorGuid;
+ efi_status_t status;
+ int err;
+
+ *set = false;
+
+ if (efivar_validate(*vendor, name, data, *size) == false)
+ 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).
+ */
+ err = efivar_lock();
+ if (err)
+ return err;
+
+ /*
+ * Ensure that the available space hasn't shrunk below the safe level
+ */
+ status = check_var_size(attributes, *size + ucs2_strsize(name, 1024));
+ if (status != EFI_SUCCESS) {
+ if (status != EFI_UNSUPPORTED) {
+ err = efi_status_to_err(status);
+ goto out;
+ }
+
+ if (*size > 65536) {
+ err = -ENOSPC;
+ goto out;
+ }
+ }
+
+ status = efivar_set_variable_locked(name, vendor, attributes, *size,
+ data, false);
+ if (status != EFI_SUCCESS) {
+ err = efi_status_to_err(status);
+ goto out;
+ }
+
+ *set = true;
+
+ /*
+ * 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.
+ */
+ *size = 0;
+ status = efivar_get_variable(entry->var.VariableName,
+ &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)
+ return efi_status_to_err(status);
+
+ return 0;
+
+out:
+ efivar_unlock();
+ 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;
+}
+EXPORT_SYMBOL_GPL(efivar_entry_iter);
diff --git a/include/linux/efi.h b/include/linux/efi.h
index 10ef0a0d5e9a..8122c2ed505c 100644
--- a/include/linux/efi.h
+++ b/include/linux/efi.h
@@ -1030,21 +1030,6 @@ struct efivars {
#define EFI_VAR_NAME_LEN 1024
-struct efi_variable {
- efi_char16_t VariableName[EFI_VAR_NAME_LEN/sizeof(efi_char16_t)];
- efi_guid_t VendorGuid;
- unsigned long DataSize;
- __u8 Data[1024];
- efi_status_t Status;
- __u32 Attributes;
-} __attribute__((packed));
-
-struct efivar_entry {
- struct efi_variable var;
- struct list_head list;
- struct kobject kobj;
-};
-
int efivars_register(struct efivars *efivars,
const struct efivar_operations *ops,
struct kobject *kobject);
@@ -1052,29 +1037,6 @@ int efivars_unregister(struct efivars *efivars);
struct kobject *efivars_kobject(void);
int efivar_supports_writes(void);
-int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *),
- void *data, bool duplicates, struct list_head *head);
-
-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);
-int __efivar_entry_get(struct efivar_entry *entry, u32 *attributes,
- unsigned long *size, void *data);
-int efivar_entry_get(struct efivar_entry *entry, u32 *attributes,
- unsigned long *size, void *data);
-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);
int efivar_lock(void);
int efivar_trylock(void);
--
2.35.1
Both efivars and efivarfs uses __efivar_entry_iter() to go over the
linked list that shadows the list of EFI variables held by the firmware,
but fail to call the begin/end helpers that are documented as a
prerequisite.
So switch to the proper version, which is efivar_entry_iter(). Given
that in both cases, efivar_entry_remove() is invoked with the lock held
already, don't take the lock there anymore.
Signed-off-by: Ard Biesheuvel <[email protected]>
---
drivers/firmware/efi/efivars.c | 8 ++------
drivers/firmware/efi/vars.c | 9 +--------
fs/efivarfs/super.c | 9 +++------
include/linux/efi.h | 2 +-
4 files changed, 7 insertions(+), 21 deletions(-)
diff --git a/drivers/firmware/efi/efivars.c b/drivers/firmware/efi/efivars.c
index 8341fb15f62e..801a65582172 100644
--- a/drivers/firmware/efi/efivars.c
+++ b/drivers/firmware/efi/efivars.c
@@ -602,10 +602,7 @@ static int efivars_sysfs_callback(efi_char16_t *name, efi_guid_t vendor,
static int efivar_sysfs_destroy(struct efivar_entry *entry, void *data)
{
- int err = efivar_entry_remove(entry);
-
- if (err)
- return err;
+ efivar_entry_remove(entry);
efivar_unregister(entry);
return 0;
}
@@ -615,8 +612,7 @@ static void efivars_sysfs_exit(void)
/* Remove all entries and destroy */
int err;
- err = __efivar_entry_iter(efivar_sysfs_destroy, &efivar_sysfs_list,
- NULL, NULL);
+ err = efivar_entry_iter(efivar_sysfs_destroy, &efivar_sysfs_list, NULL);
if (err) {
pr_err("efivars: Failed to destroy sysfs entries\n");
return;
diff --git a/drivers/firmware/efi/vars.c b/drivers/firmware/efi/vars.c
index 5640ffa81544..29540013b358 100644
--- a/drivers/firmware/efi/vars.c
+++ b/drivers/firmware/efi/vars.c
@@ -523,17 +523,10 @@ EXPORT_SYMBOL_GPL(__efivar_entry_add);
/**
* 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.
*/
-int efivar_entry_remove(struct efivar_entry *entry)
+void efivar_entry_remove(struct efivar_entry *entry)
{
- if (down_interruptible(&efivars_lock))
- return -EINTR;
list_del(&entry->list);
- up(&efivars_lock);
-
- return 0;
}
EXPORT_SYMBOL_GPL(efivar_entry_remove);
diff --git a/fs/efivarfs/super.c b/fs/efivarfs/super.c
index 09dfa8362f50..6780fc81cc11 100644
--- a/fs/efivarfs/super.c
+++ b/fs/efivarfs/super.c
@@ -180,10 +180,7 @@ static int efivarfs_callback(efi_char16_t *name16, efi_guid_t vendor,
static int efivarfs_destroy(struct efivar_entry *entry, void *data)
{
- int err = efivar_entry_remove(entry);
-
- if (err)
- return err;
+ efivar_entry_remove(entry);
kfree(entry);
return 0;
}
@@ -219,7 +216,7 @@ static int efivarfs_fill_super(struct super_block *sb, struct fs_context *fc)
err = efivar_init(efivarfs_callback, (void *)sb, true, &efivarfs_list);
if (err)
- __efivar_entry_iter(efivarfs_destroy, &efivarfs_list, NULL, NULL);
+ efivar_entry_iter(efivarfs_destroy, &efivarfs_list, NULL);
return err;
}
@@ -244,7 +241,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, NULL);
+ efivar_entry_iter(efivarfs_destroy, &efivarfs_list, NULL);
}
static struct file_system_type efivarfs_type = {
diff --git a/include/linux/efi.h b/include/linux/efi.h
index 08bc6215e3b4..54ca2d6b6c78 100644
--- a/include/linux/efi.h
+++ b/include/linux/efi.h
@@ -1063,7 +1063,7 @@ int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *),
int efivar_entry_add(struct efivar_entry *entry, struct list_head *head);
void __efivar_entry_add(struct efivar_entry *entry, struct list_head *head);
-int efivar_entry_remove(struct efivar_entry *entry);
+void efivar_entry_remove(struct efivar_entry *entry);
int __efivar_entry_delete(struct efivar_entry *entry);
int efivar_entry_delete(struct efivar_entry *entry);
--
2.35.1
On Tue, 21 Jun 2022 at 23:00, Kees Cook <[email protected]> wrote:
>
> On Tue, Jun 21, 2022 at 05:36:18PM +0200, Ard Biesheuvel wrote:
> > Avoid the efivars layer and simply call the newly introduced EFI
> > varstore helpers instead. This simplifies the code substantially, and
> > also allows us to remove some hacks in the shared efivars layer that
> > were added for efi-pstore specifically.
> >
> > Since we don't store the name of the associated EFI variable into each
> > pstore record when enumerating them, we have to guess the variable name
> > it was constructed from at deletion time, since we no longer keep a
> > shadow copy of the variable store. To make this a bit more exact, store
> > the CRC-32 of the ASCII name into the pstore record's ECC region so we
> > can use it later to make an educated guess regarding the name of the EFI
> > variable.
>
> I wonder if pstore_record should have a "private" field for backends to
> use? That seems like it solve the need for overloading the ecc field,
> and allow for arbitrarily more information to be stored (i.e. store full
> efi var name instead of an easily-colliding crc32?)
>
We could easily add that - we'd just have to decide how to free the
memory it points to.
On Tue, Jun 21, 2022 at 11:12:17PM +0200, Ard Biesheuvel wrote:
> On Tue, 21 Jun 2022 at 23:00, Kees Cook <[email protected]> wrote:
> >
> > On Tue, Jun 21, 2022 at 05:36:18PM +0200, Ard Biesheuvel wrote:
> > > Avoid the efivars layer and simply call the newly introduced EFI
> > > varstore helpers instead. This simplifies the code substantially, and
> > > also allows us to remove some hacks in the shared efivars layer that
> > > were added for efi-pstore specifically.
> > >
> > > Since we don't store the name of the associated EFI variable into each
> > > pstore record when enumerating them, we have to guess the variable name
> > > it was constructed from at deletion time, since we no longer keep a
> > > shadow copy of the variable store. To make this a bit more exact, store
> > > the CRC-32 of the ASCII name into the pstore record's ECC region so we
> > > can use it later to make an educated guess regarding the name of the EFI
> > > variable.
> >
> > I wonder if pstore_record should have a "private" field for backends to
> > use? That seems like it solve the need for overloading the ecc field,
> > and allow for arbitrarily more information to be stored (i.e. store full
> > efi var name instead of an easily-colliding crc32?)
> >
>
> We could easily add that - we'd just have to decide how to free the
> memory it points to.
I assume the pstore core could do that since it manages the record
lifetime already?
--
Kees Cook
On Tue, 21 Jun 2022 at 23:21, Kees Cook <[email protected]> wrote:
>
> On Tue, Jun 21, 2022 at 11:12:17PM +0200, Ard Biesheuvel wrote:
> > On Tue, 21 Jun 2022 at 23:00, Kees Cook <[email protected]> wrote:
> > >
> > > On Tue, Jun 21, 2022 at 05:36:18PM +0200, Ard Biesheuvel wrote:
> > > > Avoid the efivars layer and simply call the newly introduced EFI
> > > > varstore helpers instead. This simplifies the code substantially, and
> > > > also allows us to remove some hacks in the shared efivars layer that
> > > > were added for efi-pstore specifically.
> > > >
> > > > Since we don't store the name of the associated EFI variable into each
> > > > pstore record when enumerating them, we have to guess the variable name
> > > > it was constructed from at deletion time, since we no longer keep a
> > > > shadow copy of the variable store. To make this a bit more exact, store
> > > > the CRC-32 of the ASCII name into the pstore record's ECC region so we
> > > > can use it later to make an educated guess regarding the name of the EFI
> > > > variable.
> > >
> > > I wonder if pstore_record should have a "private" field for backends to
> > > use? That seems like it solve the need for overloading the ecc field,
> > > and allow for arbitrarily more information to be stored (i.e. store full
> > > efi var name instead of an easily-colliding crc32?)
> > >
> >
> > We could easily add that - we'd just have to decide how to free the
> > memory it points to.
>
> I assume the pstore core could do that since it manages the record
> lifetime already?
>
So if priv is non-NULL when it frees the record, it passes it to kfree() ?
On Tue, Jun 21, 2022 at 05:36:15PM +0200, Ard Biesheuvel wrote:
> If a pstore record has its ecc_notice_size field set to >0, it means the
> record's buffer has that many additional bytes appended to the end that
> carry backend specific metadata, typically used for error correction.
>
> Given that this is backend specific, and that user space cannot really
> make sense of this metadata anyway, let's not expose it via the pstore
> filesystem.
>
> Signed-off-by: Ard Biesheuvel <[email protected]>
"ecc_notice_size" is actually describing the length of the string
generated and appended by persistent_ram_ecc_string().
I've been bothered by this string, though, as it confuses what was
actually stored with additional lines. "Why does every entry end with a
string about ECC?"
I think it's more sensible to show to userspace the record "as stored". We
already prepend some chunking details when a panic write may split the
dump across multiple records, so if anyone needs this IN the userspace
file contents again, it could move there.
I'd rather ECC status be reported at boot, really. Given that nothing I
can find[1] parses the ECC notice string, I think it'd be fine to just
remove it from the string buffer entirely.
-Kees
[1] https://codesearch.debian.net/search?q=Corrected+bytes
--
Kees Cook
On Tue, Jun 21, 2022 at 11:33:29PM +0200, Ard Biesheuvel wrote:
> On Tue, 21 Jun 2022 at 23:21, Kees Cook <[email protected]> wrote:
> >
> > On Tue, Jun 21, 2022 at 11:12:17PM +0200, Ard Biesheuvel wrote:
> > > On Tue, 21 Jun 2022 at 23:00, Kees Cook <[email protected]> wrote:
> > > >
> > > > On Tue, Jun 21, 2022 at 05:36:18PM +0200, Ard Biesheuvel wrote:
> > > > > Avoid the efivars layer and simply call the newly introduced EFI
> > > > > varstore helpers instead. This simplifies the code substantially, and
> > > > > also allows us to remove some hacks in the shared efivars layer that
> > > > > were added for efi-pstore specifically.
> > > > >
> > > > > Since we don't store the name of the associated EFI variable into each
> > > > > pstore record when enumerating them, we have to guess the variable name
> > > > > it was constructed from at deletion time, since we no longer keep a
> > > > > shadow copy of the variable store. To make this a bit more exact, store
> > > > > the CRC-32 of the ASCII name into the pstore record's ECC region so we
> > > > > can use it later to make an educated guess regarding the name of the EFI
> > > > > variable.
> > > >
> > > > I wonder if pstore_record should have a "private" field for backends to
> > > > use? That seems like it solve the need for overloading the ecc field,
> > > > and allow for arbitrarily more information to be stored (i.e. store full
> > > > efi var name instead of an easily-colliding crc32?)
> > > >
> > >
> > > We could easily add that - we'd just have to decide how to free the
> > > memory it points to.
> >
> > I assume the pstore core could do that since it manages the record
> > lifetime already?
> >
>
> So if priv is non-NULL when it frees the record, it passes it to kfree() ?
That's my idea, yeah. I *think* it'll work; I haven't taken a
super-close look, though.
--
Kees Cook
On Tue, Jun 21, 2022 at 05:36:18PM +0200, Ard Biesheuvel wrote:
> Avoid the efivars layer and simply call the newly introduced EFI
> varstore helpers instead. This simplifies the code substantially, and
> also allows us to remove some hacks in the shared efivars layer that
> were added for efi-pstore specifically.
>
> Since we don't store the name of the associated EFI variable into each
> pstore record when enumerating them, we have to guess the variable name
> it was constructed from at deletion time, since we no longer keep a
> shadow copy of the variable store. To make this a bit more exact, store
> the CRC-32 of the ASCII name into the pstore record's ECC region so we
> can use it later to make an educated guess regarding the name of the EFI
> variable.
I wonder if pstore_record should have a "private" field for backends to
use? That seems like it solve the need for overloading the ecc field,
and allow for arbitrarily more information to be stored (i.e. store full
efi var name instead of an easily-colliding crc32?)
--
Kees Cook