All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/4] efi: run UEFI services with interrupts enabled
@ 2015-11-19 13:36 Ard Biesheuvel
       [not found] ` <1447940191-30705-1-git-send-email-ard.biesheuvel-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
  0 siblings, 1 reply; 13+ messages in thread
From: Ard Biesheuvel @ 2015-11-19 13:36 UTC (permalink / raw)
  To: linux-efi-u79uwXL29TY76Z2rM5mHXA,
	matt-mF/unelCI9GS6iBeEJttW/XRex20P6io,
	leif.lindholm-QSEj5FYQhm4dnm+yROfE0A, mark.rutland-5wv7dgnIgG8
  Cc: Ard Biesheuvel

The UEFI spec does not require interrupts to be disabled when invoking
runtime services. The reason we have been doing so is because EFI pstore
may call SetVariable() from interrupt context, which may result in deadlock
if another runtime services call was in progress on the same cpu.

The EFI pstore has already been updated to use a non-blocking path and fail
gracefully rather than spin forever, so we can updated the ordinary blocking
wrappers to run with interrupts enabled instead.

The first 3 patches fix a couple of bugs and remove a stale comment. Patch #4
actually implements the above.

Ard Biesheuvel (4):
  efi: expose non-blocking set_variable() wrapper to efivars
  efi: efivars: don't rely on blocking operations in non-blocking
    set_var()
  efi: runtime-wrappers: remove out of date comment regarding in_nmi()
  efi: runtime-wrappers: run UEFI Runtime Services with interrupts
    enabled

 drivers/firmware/efi/efi.c              |   1 +
 drivers/firmware/efi/runtime-wrappers.c | 114 +++++++++-----------
 drivers/firmware/efi/vars.c             |  12 +--
 3 files changed, 53 insertions(+), 74 deletions(-)

-- 
1.9.1

^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH 1/4] efi: expose non-blocking set_variable() wrapper to efivars
       [not found] ` <1447940191-30705-1-git-send-email-ard.biesheuvel-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
@ 2015-11-19 13:36   ` Ard Biesheuvel
       [not found]     ` <1447940191-30705-2-git-send-email-ard.biesheuvel-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
  2015-11-19 13:36   ` [PATCH 2/4] efi: efivars: don't rely on blocking operations in non-blocking set_var() Ard Biesheuvel
                     ` (2 subsequent siblings)
  3 siblings, 1 reply; 13+ messages in thread
From: Ard Biesheuvel @ 2015-11-19 13:36 UTC (permalink / raw)
  To: linux-efi-u79uwXL29TY76Z2rM5mHXA,
	matt-mF/unelCI9GS6iBeEJttW/XRex20P6io,
	leif.lindholm-QSEj5FYQhm4dnm+yROfE0A, mark.rutland-5wv7dgnIgG8
  Cc: Ard Biesheuvel

Commit 6d80dba1c9fe ("efi: Provide a non-blocking SetVariable()
operation") implemented a non-blocking alternative for the UEFI
SetVariable() invocation performed by efivars, since it may occur
in atomic context. However, this version of the function was never
exposed via the efivars struct, so the non-blocking versions was
not actually callable. Fix that.

Fixes: 6d80dba1c9fe ("efi: Provide a non-blocking SetVariable() operation")
Signed-off-by: Ard Biesheuvel <ard.biesheuvel-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
---
 drivers/firmware/efi/efi.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c
index 027ca212179f..3b52677f459a 100644
--- a/drivers/firmware/efi/efi.c
+++ b/drivers/firmware/efi/efi.c
@@ -180,6 +180,7 @@ static int generic_ops_register(void)
 {
 	generic_ops.get_variable = efi.get_variable;
 	generic_ops.set_variable = efi.set_variable;
+	generic_ops.set_variable_nonblocking = efi.set_variable_nonblocking;
 	generic_ops.get_next_variable = efi.get_next_variable;
 	generic_ops.query_variable_store = efi_query_variable_store;
 
-- 
1.9.1

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 2/4] efi: efivars: don't rely on blocking operations in non-blocking set_var()
       [not found] ` <1447940191-30705-1-git-send-email-ard.biesheuvel-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
  2015-11-19 13:36   ` [PATCH 1/4] efi: expose non-blocking set_variable() wrapper to efivars Ard Biesheuvel
@ 2015-11-19 13:36   ` Ard Biesheuvel
       [not found]     ` <1447940191-30705-3-git-send-email-ard.biesheuvel-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
  2015-11-19 13:36   ` [PATCH 3/4] efi: runtime-wrappers: remove out of date comment regarding in_nmi() Ard Biesheuvel
  2015-11-19 13:36   ` [PATCH 4/4] efi: runtime-wrappers: run UEFI Runtime Services with interrupts enabled Ard Biesheuvel
  3 siblings, 1 reply; 13+ messages in thread
From: Ard Biesheuvel @ 2015-11-19 13:36 UTC (permalink / raw)
  To: linux-efi-u79uwXL29TY76Z2rM5mHXA,
	matt-mF/unelCI9GS6iBeEJttW/XRex20P6io,
	leif.lindholm-QSEj5FYQhm4dnm+yROfE0A, mark.rutland-5wv7dgnIgG8
  Cc: Ard Biesheuvel

The non-blocking version of efivar_entry_set() gives up directly on
failure to acquire __efivars->lock. However, it also calls check_var_size(),
whose implementation calls the ordinary query_variable_info() and
set_variable() runtime service wrappers, meaning we could still deadlock
if efivar_entry_set_nonblocking() is called from an atomic context that
interrupted a runtime service already in progress on the same CPU.

So drop the call to check_var_size(). This is potentially unsafe on some
UEFI implementations that fail to boot if the varstore fills up, but
those systems are unlikely to be using efi-pstore in the first place.

Signed-off-by: Ard Biesheuvel <ard.biesheuvel-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
---
 drivers/firmware/efi/vars.c | 12 +++---------
 1 file changed, 3 insertions(+), 9 deletions(-)

diff --git a/drivers/firmware/efi/vars.c b/drivers/firmware/efi/vars.c
index 70a0fb10517f..caccdbffc1d0 100644
--- a/drivers/firmware/efi/vars.c
+++ b/drivers/firmware/efi/vars.c
@@ -615,12 +615,6 @@ efivar_entry_set_nonblocking(efi_char16_t *name, efi_guid_t vendor,
 	if (!spin_trylock_irqsave(&__efivars->lock, flags))
 		return -EBUSY;
 
-	status = check_var_size(attributes, size + ucs2_strsize(name, 1024));
-	if (status != EFI_SUCCESS) {
-		spin_unlock_irqrestore(&__efivars->lock, flags);
-		return -ENOSPC;
-	}
-
 	status = ops->set_variable_nonblocking(name, &vendor, attributes,
 					       size, data);
 
@@ -652,9 +646,6 @@ int efivar_entry_set_safe(efi_char16_t *name, efi_guid_t vendor, u32 attributes,
 	unsigned long flags;
 	efi_status_t status;
 
-	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
@@ -669,6 +660,9 @@ int efivar_entry_set_safe(efi_char16_t *name, efi_guid_t vendor, u32 attributes,
 		return efivar_entry_set_nonblocking(name, vendor, attributes,
 						    size, data);
 
+	if (!ops->query_variable_store)
+		return -ENOSYS;
+
 	if (!block) {
 		if (!spin_trylock_irqsave(&__efivars->lock, flags))
 			return -EBUSY;
-- 
1.9.1

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 3/4] efi: runtime-wrappers: remove out of date comment regarding in_nmi()
       [not found] ` <1447940191-30705-1-git-send-email-ard.biesheuvel-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
  2015-11-19 13:36   ` [PATCH 1/4] efi: expose non-blocking set_variable() wrapper to efivars Ard Biesheuvel
  2015-11-19 13:36   ` [PATCH 2/4] efi: efivars: don't rely on blocking operations in non-blocking set_var() Ard Biesheuvel
@ 2015-11-19 13:36   ` Ard Biesheuvel
       [not found]     ` <1447940191-30705-4-git-send-email-ard.biesheuvel-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
  2015-11-19 13:36   ` [PATCH 4/4] efi: runtime-wrappers: run UEFI Runtime Services with interrupts enabled Ard Biesheuvel
  3 siblings, 1 reply; 13+ messages in thread
From: Ard Biesheuvel @ 2015-11-19 13:36 UTC (permalink / raw)
  To: linux-efi-u79uwXL29TY76Z2rM5mHXA,
	matt-mF/unelCI9GS6iBeEJttW/XRex20P6io,
	leif.lindholm-QSEj5FYQhm4dnm+yROfE0A, mark.rutland-5wv7dgnIgG8
  Cc: Ard Biesheuvel

This code is long gone, so remove the comment as well.

Signed-off-by: Ard Biesheuvel <ard.biesheuvel-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
---
 drivers/firmware/efi/runtime-wrappers.c | 26 --------------------
 1 file changed, 26 deletions(-)

diff --git a/drivers/firmware/efi/runtime-wrappers.c b/drivers/firmware/efi/runtime-wrappers.c
index 228bbf910461..a624f7445d11 100644
--- a/drivers/firmware/efi/runtime-wrappers.c
+++ b/drivers/firmware/efi/runtime-wrappers.c
@@ -62,32 +62,6 @@
 static DEFINE_SPINLOCK(efi_runtime_lock);
 
 /*
- * Some runtime services calls can be reentrant under NMI, even if the table
- * above says they are not. (source: UEFI Specification v2.4A)
- *
- * Table 32. Functions that may be called after Machine Check, INIT and NMI
- * +----------------------------+------------------------------------------+
- * | Function			| Called after Machine Check, INIT and NMI |
- * +----------------------------+------------------------------------------+
- * | GetTime()			| Yes, even if previously busy.		   |
- * | GetVariable()		| Yes, even if previously busy		   |
- * | GetNextVariableName()	| Yes, even if previously busy		   |
- * | QueryVariableInfo()	| Yes, even if previously busy		   |
- * | SetVariable()		| Yes, even if previously busy		   |
- * | UpdateCapsule()		| Yes, even if previously busy		   |
- * | QueryCapsuleCapabilities()	| Yes, even if previously busy		   |
- * | ResetSystem()		| Yes, even if previously busy		   |
- * +----------------------------+------------------------------------------+
- *
- * In order to prevent deadlocks under NMI, the wrappers for these functions
- * may only grab the efi_runtime_lock or rtc_lock spinlocks if !efi_in_nmi().
- * However, not all of the services listed are reachable through NMI code paths,
- * so the the special handling as suggested by the UEFI spec is only implemented
- * for QueryVariableInfo() and SetVariable(), as these can be reached in NMI
- * context through efi_pstore_write().
- */
-
-/*
  * As per commit ef68c8f87ed1 ("x86: Serialize EFI time accesses on rtc_lock"),
  * the EFI specification requires that callers of the time related runtime
  * functions serialize with other CMOS accesses in the kernel, as the EFI time
-- 
1.9.1

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 4/4] efi: runtime-wrappers: run UEFI Runtime Services with interrupts enabled
       [not found] ` <1447940191-30705-1-git-send-email-ard.biesheuvel-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
                     ` (2 preceding siblings ...)
  2015-11-19 13:36   ` [PATCH 3/4] efi: runtime-wrappers: remove out of date comment regarding in_nmi() Ard Biesheuvel
@ 2015-11-19 13:36   ` Ard Biesheuvel
       [not found]     ` <1447940191-30705-5-git-send-email-ard.biesheuvel-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
  3 siblings, 1 reply; 13+ messages in thread
From: Ard Biesheuvel @ 2015-11-19 13:36 UTC (permalink / raw)
  To: linux-efi-u79uwXL29TY76Z2rM5mHXA,
	matt-mF/unelCI9GS6iBeEJttW/XRex20P6io,
	leif.lindholm-QSEj5FYQhm4dnm+yROfE0A, mark.rutland-5wv7dgnIgG8
  Cc: Ard Biesheuvel

The UEFI spec allows Runtime Services to be invoked with interrupts
enabled. The only reason we were disabling interrupts was to prevent
recursive calls into the services on the same CPU, which will lead to
deadlock. However, the only context where such invocations may occur
legally is from efi-pstore via efivars, and that code has been updated
to call a non-blocking alternative when invoked from a non-interruptible
context.

So instead, update the ordinary, blocking UEFI Runtime Services wrappers
to execute with interrupts enabled.

Signed-off-by: Ard Biesheuvel <ard.biesheuvel-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
---
 drivers/firmware/efi/runtime-wrappers.c | 88 +++++++++++---------
 1 file changed, 49 insertions(+), 39 deletions(-)

diff --git a/drivers/firmware/efi/runtime-wrappers.c b/drivers/firmware/efi/runtime-wrappers.c
index a624f7445d11..31e131f09530 100644
--- a/drivers/firmware/efi/runtime-wrappers.c
+++ b/drivers/firmware/efi/runtime-wrappers.c
@@ -71,27 +71,29 @@ __weak DEFINE_SPINLOCK(rtc_lock);
 
 static efi_status_t virt_efi_get_time(efi_time_t *tm, efi_time_cap_t *tc)
 {
-	unsigned long flags;
 	efi_status_t status;
 
-	spin_lock_irqsave(&rtc_lock, flags);
+	BUG_ON(in_irq());
+
+	spin_lock(&rtc_lock);
 	spin_lock(&efi_runtime_lock);
 	status = efi_call_virt(get_time, tm, tc);
 	spin_unlock(&efi_runtime_lock);
-	spin_unlock_irqrestore(&rtc_lock, flags);
+	spin_unlock(&rtc_lock);
 	return status;
 }
 
 static efi_status_t virt_efi_set_time(efi_time_t *tm)
 {
-	unsigned long flags;
 	efi_status_t status;
 
-	spin_lock_irqsave(&rtc_lock, flags);
+	BUG_ON(in_irq());
+
+	spin_lock(&rtc_lock);
 	spin_lock(&efi_runtime_lock);
 	status = efi_call_virt(set_time, tm);
 	spin_unlock(&efi_runtime_lock);
-	spin_unlock_irqrestore(&rtc_lock, flags);
+	spin_unlock(&rtc_lock);
 	return status;
 }
 
@@ -99,27 +101,29 @@ static efi_status_t virt_efi_get_wakeup_time(efi_bool_t *enabled,
 					     efi_bool_t *pending,
 					     efi_time_t *tm)
 {
-	unsigned long flags;
 	efi_status_t status;
 
-	spin_lock_irqsave(&rtc_lock, flags);
+	BUG_ON(in_irq());
+
+	spin_lock(&rtc_lock);
 	spin_lock(&efi_runtime_lock);
 	status = efi_call_virt(get_wakeup_time, enabled, pending, tm);
 	spin_unlock(&efi_runtime_lock);
-	spin_unlock_irqrestore(&rtc_lock, flags);
+	spin_unlock(&rtc_lock);
 	return status;
 }
 
 static efi_status_t virt_efi_set_wakeup_time(efi_bool_t enabled, efi_time_t *tm)
 {
-	unsigned long flags;
 	efi_status_t status;
 
-	spin_lock_irqsave(&rtc_lock, flags);
+	BUG_ON(in_irq());
+
+	spin_lock(&rtc_lock);
 	spin_lock(&efi_runtime_lock);
 	status = efi_call_virt(set_wakeup_time, enabled, tm);
 	spin_unlock(&efi_runtime_lock);
-	spin_unlock_irqrestore(&rtc_lock, flags);
+	spin_unlock(&rtc_lock);
 	return status;
 }
 
@@ -129,13 +133,14 @@ static efi_status_t virt_efi_get_variable(efi_char16_t *name,
 					  unsigned long *data_size,
 					  void *data)
 {
-	unsigned long flags;
 	efi_status_t status;
 
-	spin_lock_irqsave(&efi_runtime_lock, flags);
+	BUG_ON(in_irq());
+
+	spin_lock(&efi_runtime_lock);
 	status = efi_call_virt(get_variable, name, vendor, attr, data_size,
 			       data);
-	spin_unlock_irqrestore(&efi_runtime_lock, flags);
+	spin_unlock(&efi_runtime_lock);
 	return status;
 }
 
@@ -143,12 +148,13 @@ static efi_status_t virt_efi_get_next_variable(unsigned long *name_size,
 					       efi_char16_t *name,
 					       efi_guid_t *vendor)
 {
-	unsigned long flags;
 	efi_status_t status;
 
-	spin_lock_irqsave(&efi_runtime_lock, flags);
+	BUG_ON(in_irq());
+
+	spin_lock(&efi_runtime_lock);
 	status = efi_call_virt(get_next_variable, name_size, name, vendor);
-	spin_unlock_irqrestore(&efi_runtime_lock, flags);
+	spin_unlock(&efi_runtime_lock);
 	return status;
 }
 
@@ -158,13 +164,14 @@ static efi_status_t virt_efi_set_variable(efi_char16_t *name,
 					  unsigned long data_size,
 					  void *data)
 {
-	unsigned long flags;
 	efi_status_t status;
 
-	spin_lock_irqsave(&efi_runtime_lock, flags);
+	BUG_ON(in_irq());
+
+	spin_lock(&efi_runtime_lock);
 	status = efi_call_virt(set_variable, name, vendor, attr, data_size,
 			       data);
-	spin_unlock_irqrestore(&efi_runtime_lock, flags);
+	spin_unlock(&efi_runtime_lock);
 	return status;
 }
 
@@ -173,15 +180,14 @@ virt_efi_set_variable_nonblocking(efi_char16_t *name, efi_guid_t *vendor,
 				  u32 attr, unsigned long data_size,
 				  void *data)
 {
-	unsigned long flags;
 	efi_status_t status;
 
-	if (!spin_trylock_irqsave(&efi_runtime_lock, flags))
+	if (!spin_trylock(&efi_runtime_lock))
 		return EFI_NOT_READY;
 
 	status = efi_call_virt(set_variable, name, vendor, attr, data_size,
 			       data);
-	spin_unlock_irqrestore(&efi_runtime_lock, flags);
+	spin_unlock(&efi_runtime_lock);
 	return status;
 }
 
@@ -191,27 +197,29 @@ static efi_status_t virt_efi_query_variable_info(u32 attr,
 						 u64 *remaining_space,
 						 u64 *max_variable_size)
 {
-	unsigned long flags;
 	efi_status_t status;
 
+	BUG_ON(in_irq());
+
 	if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION)
 		return EFI_UNSUPPORTED;
 
-	spin_lock_irqsave(&efi_runtime_lock, flags);
+	spin_lock(&efi_runtime_lock);
 	status = efi_call_virt(query_variable_info, attr, storage_space,
 			       remaining_space, max_variable_size);
-	spin_unlock_irqrestore(&efi_runtime_lock, flags);
+	spin_unlock(&efi_runtime_lock);
 	return status;
 }
 
 static efi_status_t virt_efi_get_next_high_mono_count(u32 *count)
 {
-	unsigned long flags;
 	efi_status_t status;
 
-	spin_lock_irqsave(&efi_runtime_lock, flags);
+	BUG_ON(in_irq());
+
+	spin_lock(&efi_runtime_lock);
 	status = efi_call_virt(get_next_high_mono_count, count);
-	spin_unlock_irqrestore(&efi_runtime_lock, flags);
+	spin_unlock(&efi_runtime_lock);
 	return status;
 }
 
@@ -220,26 +228,27 @@ static void virt_efi_reset_system(int reset_type,
 				  unsigned long data_size,
 				  efi_char16_t *data)
 {
-	unsigned long flags;
+	BUG_ON(in_irq());
 
-	spin_lock_irqsave(&efi_runtime_lock, flags);
+	spin_lock(&efi_runtime_lock);
 	__efi_call_virt(reset_system, reset_type, status, data_size, data);
-	spin_unlock_irqrestore(&efi_runtime_lock, flags);
+	spin_unlock(&efi_runtime_lock);
 }
 
 static efi_status_t virt_efi_update_capsule(efi_capsule_header_t **capsules,
 					    unsigned long count,
 					    unsigned long sg_list)
 {
-	unsigned long flags;
 	efi_status_t status;
 
+	BUG_ON(in_irq());
+
 	if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION)
 		return EFI_UNSUPPORTED;
 
-	spin_lock_irqsave(&efi_runtime_lock, flags);
+	spin_lock(&efi_runtime_lock);
 	status = efi_call_virt(update_capsule, capsules, count, sg_list);
-	spin_unlock_irqrestore(&efi_runtime_lock, flags);
+	spin_unlock(&efi_runtime_lock);
 	return status;
 }
 
@@ -248,16 +257,17 @@ static efi_status_t virt_efi_query_capsule_caps(efi_capsule_header_t **capsules,
 						u64 *max_size,
 						int *reset_type)
 {
-	unsigned long flags;
 	efi_status_t status;
 
+	BUG_ON(in_irq());
+
 	if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION)
 		return EFI_UNSUPPORTED;
 
-	spin_lock_irqsave(&efi_runtime_lock, flags);
+	spin_lock(&efi_runtime_lock);
 	status = efi_call_virt(query_capsule_caps, capsules, count, max_size,
 			       reset_type);
-	spin_unlock_irqrestore(&efi_runtime_lock, flags);
+	spin_unlock(&efi_runtime_lock);
 	return status;
 }
 
-- 
1.9.1

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* Re: [PATCH 2/4] efi: efivars: don't rely on blocking operations in non-blocking set_var()
       [not found]     ` <1447940191-30705-3-git-send-email-ard.biesheuvel-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
@ 2015-11-26  9:54       ` Matt Fleming
       [not found]         ` <20151126095441.GA2765-mF/unelCI9GS6iBeEJttW/XRex20P6io@public.gmane.org>
  0 siblings, 1 reply; 13+ messages in thread
From: Matt Fleming @ 2015-11-26  9:54 UTC (permalink / raw)
  To: Ard Biesheuvel
  Cc: linux-efi-u79uwXL29TY76Z2rM5mHXA,
	leif.lindholm-QSEj5FYQhm4dnm+yROfE0A, mark.rutland-5wv7dgnIgG8,
	Matthew Garrett, Tony Luck

On Thu, 19 Nov, at 02:36:29PM, Ard Biesheuvel wrote:
> The non-blocking version of efivar_entry_set() gives up directly on
> failure to acquire __efivars->lock. However, it also calls check_var_size(),
> whose implementation calls the ordinary query_variable_info() and
> set_variable() runtime service wrappers, meaning we could still deadlock
> if efivar_entry_set_nonblocking() is called from an atomic context that
> interrupted a runtime service already in progress on the same CPU.
> 
> So drop the call to check_var_size(). This is potentially unsafe on some
> UEFI implementations that fail to boot if the varstore fills up, but
> those systems are unlikely to be using efi-pstore in the first place.
 
There is simply no way you can make that assumption. The whole "my
NVRAM is full, now my machine is bricked" issue arose because
efi-pstore was filling the NVRAM with crash logs; that's how we
triggered the problem in the first place.

The locking here is non-trivial, so it's worth spelling out. There are
two spinlocks we need to concern ourselves with,

  1. __efivars->lock
  2. efi_runtime_lock

All EFI variable operations are performed with __efivars->lock held,
apart from the x86 efi_delete_dummy_variable() call during boot when
we're UP anyway. For all intents and purposes, when we're SMP,
__efivars->lock is held for all EFI variable operations.

So the only potential for deadlock if CPU A is doing variable calls is
if CPU A or CPU B is doing efi_reset() or efi_capsule_*(). That's not
an impossible scenario (particularly on arm64 where efi_reset() is
used more frequently), but it gives you some clue as to when deadlock
might be a problem.

> Signed-off-by: Ard Biesheuvel <ard.biesheuvel-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
> ---
>  drivers/firmware/efi/vars.c | 12 +++---------
>  1 file changed, 3 insertions(+), 9 deletions(-)
> 
> diff --git a/drivers/firmware/efi/vars.c b/drivers/firmware/efi/vars.c
> index 70a0fb10517f..caccdbffc1d0 100644
> --- a/drivers/firmware/efi/vars.c
> +++ b/drivers/firmware/efi/vars.c
> @@ -615,12 +615,6 @@ efivar_entry_set_nonblocking(efi_char16_t *name, efi_guid_t vendor,
>  	if (!spin_trylock_irqsave(&__efivars->lock, flags))
>  		return -EBUSY;
>  
> -	status = check_var_size(attributes, size + ucs2_strsize(name, 1024));
> -	if (status != EFI_SUCCESS) {
> -		spin_unlock_irqrestore(&__efivars->lock, flags);
> -		return -ENOSPC;
> -	}
> -
>  	status = ops->set_variable_nonblocking(name, &vendor, attributes,
>  					       size, data);

We still need some validation to occur if efi_no_storage_paranoia is
unset, i.e. "Be paranoid about how much NVRAM we use".

However, efi_query_variable_store() actually does two things,

  1. Check if the write pushes free NVRAM space below EFI_MIN_RESERVE
  2. If it does, attempt to trigger garbage collection

Now, if you're in the belly of a kdump handler, attempting to trigger
garbage collection to ensure the write succeeds should be the last
thing on your mind. Everything you do is a last ditch attempt to
record the reason your machine is going down.

I think the best solution would be to only perform step 1. above in
the efi_pstore_write() code path, and keep a cached copy of
query_variable_info() that we can perform lockless queries on. We can
initially grab it on boot and update it with every ->set_variable()
call.

Thoughts?

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 1/4] efi: expose non-blocking set_variable() wrapper to efivars
       [not found]     ` <1447940191-30705-2-git-send-email-ard.biesheuvel-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
@ 2015-11-26  9:55       ` Matt Fleming
  0 siblings, 0 replies; 13+ messages in thread
From: Matt Fleming @ 2015-11-26  9:55 UTC (permalink / raw)
  To: Ard Biesheuvel
  Cc: linux-efi-u79uwXL29TY76Z2rM5mHXA,
	leif.lindholm-QSEj5FYQhm4dnm+yROfE0A, mark.rutland-5wv7dgnIgG8

On Thu, 19 Nov, at 02:36:28PM, Ard Biesheuvel wrote:
> Commit 6d80dba1c9fe ("efi: Provide a non-blocking SetVariable()
> operation") implemented a non-blocking alternative for the UEFI
> SetVariable() invocation performed by efivars, since it may occur
> in atomic context. However, this version of the function was never
> exposed via the efivars struct, so the non-blocking versions was
> not actually callable. Fix that.
> 
> Fixes: 6d80dba1c9fe ("efi: Provide a non-blocking SetVariable() operation")
> Signed-off-by: Ard Biesheuvel <ard.biesheuvel-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
> ---
>  drivers/firmware/efi/efi.c | 1 +
>  1 file changed, 1 insertion(+)
> 
> diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c
> index 027ca212179f..3b52677f459a 100644
> --- a/drivers/firmware/efi/efi.c
> +++ b/drivers/firmware/efi/efi.c
> @@ -180,6 +180,7 @@ static int generic_ops_register(void)
>  {
>  	generic_ops.get_variable = efi.get_variable;
>  	generic_ops.set_variable = efi.set_variable;
> +	generic_ops.set_variable_nonblocking = efi.set_variable_nonblocking;
>  	generic_ops.get_next_variable = efi.get_next_variable;
>  	generic_ops.query_variable_store = efi_query_variable_store;
>  

What a mess. Thanks for the fix Ard.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 3/4] efi: runtime-wrappers: remove out of date comment regarding in_nmi()
       [not found]     ` <1447940191-30705-4-git-send-email-ard.biesheuvel-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
@ 2015-11-26  9:58       ` Matt Fleming
  0 siblings, 0 replies; 13+ messages in thread
From: Matt Fleming @ 2015-11-26  9:58 UTC (permalink / raw)
  To: Ard Biesheuvel
  Cc: linux-efi-u79uwXL29TY76Z2rM5mHXA,
	leif.lindholm-QSEj5FYQhm4dnm+yROfE0A, mark.rutland-5wv7dgnIgG8

On Thu, 19 Nov, at 02:36:30PM, Ard Biesheuvel wrote:
> This code is long gone, so remove the comment as well.
> 
> Signed-off-by: Ard Biesheuvel <ard.biesheuvel-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
> ---
>  drivers/firmware/efi/runtime-wrappers.c | 26 --------------------
>  1 file changed, 26 deletions(-)

Looks good, thanks.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 4/4] efi: runtime-wrappers: run UEFI Runtime Services with interrupts enabled
       [not found]     ` <1447940191-30705-5-git-send-email-ard.biesheuvel-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
@ 2015-11-26 10:23       ` Matt Fleming
       [not found]         ` <20151126102345.GD2765-mF/unelCI9GS6iBeEJttW/XRex20P6io@public.gmane.org>
  0 siblings, 1 reply; 13+ messages in thread
From: Matt Fleming @ 2015-11-26 10:23 UTC (permalink / raw)
  To: Ard Biesheuvel
  Cc: linux-efi-u79uwXL29TY76Z2rM5mHXA,
	leif.lindholm-QSEj5FYQhm4dnm+yROfE0A, mark.rutland-5wv7dgnIgG8

On Thu, 19 Nov, at 02:36:31PM, Ard Biesheuvel wrote:
> The UEFI spec allows Runtime Services to be invoked with interrupts
> enabled. The only reason we were disabling interrupts was to prevent
> recursive calls into the services on the same CPU, which will lead to
> deadlock. However, the only context where such invocations may occur
> legally is from efi-pstore via efivars, and that code has been updated
> to call a non-blocking alternative when invoked from a non-interruptible
> context.
> 
> So instead, update the ordinary, blocking UEFI Runtime Services wrappers
> to execute with interrupts enabled.
 
Please document the justification for this change; why do you *need*
interrupts enabled as opposed to doing it because the spec allows it.

> Signed-off-by: Ard Biesheuvel <ard.biesheuvel-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
> ---
>  drivers/firmware/efi/runtime-wrappers.c | 88 +++++++++++---------
>  1 file changed, 49 insertions(+), 39 deletions(-)
> 
> diff --git a/drivers/firmware/efi/runtime-wrappers.c b/drivers/firmware/efi/runtime-wrappers.c
> index a624f7445d11..31e131f09530 100644
> --- a/drivers/firmware/efi/runtime-wrappers.c
> +++ b/drivers/firmware/efi/runtime-wrappers.c
> @@ -71,27 +71,29 @@ __weak DEFINE_SPINLOCK(rtc_lock);
>  
>  static efi_status_t virt_efi_get_time(efi_time_t *tm, efi_time_cap_t *tc)
>  {
> -	unsigned long flags;
>  	efi_status_t status;
>  
> -	spin_lock_irqsave(&rtc_lock, flags);
> +	BUG_ON(in_irq());
> +
> +	spin_lock(&rtc_lock);
>  	spin_lock(&efi_runtime_lock);
>  	status = efi_call_virt(get_time, tm, tc);
>  	spin_unlock(&efi_runtime_lock);
> -	spin_unlock_irqrestore(&rtc_lock, flags);
> +	spin_unlock(&rtc_lock);
>  	return status;
>  }

This looks wrong. rtc_lock can be grabbed from IRQ context. Maybe now
is the time to rip out rtc_lock? It's __weak for arm64 anyway and x86
doesn't even use these functions. Though I notice that arm also has an
rtc_lock.

>  static efi_status_t virt_efi_set_time(efi_time_t *tm)
>  {
> -	unsigned long flags;
>  	efi_status_t status;
>  
> -	spin_lock_irqsave(&rtc_lock, flags);
> +	BUG_ON(in_irq());
> +
> +	spin_lock(&rtc_lock);

The thing about using BUG_ON() here is that you get no LOCKDEP info if
you trigger it, nor any stack traces from the softlockup detector.
It's much better to leave spin_lock() alone to do its thing.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 2/4] efi: efivars: don't rely on blocking operations in non-blocking set_var()
       [not found]         ` <20151126095441.GA2765-mF/unelCI9GS6iBeEJttW/XRex20P6io@public.gmane.org>
@ 2015-11-26 12:06           ` Ard Biesheuvel
       [not found]             ` <CAKv+Gu8XqP+MgT1vkC-CLkEZ=p+X2tA3fQuprUCRJ1UhrFz-rg-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
  0 siblings, 1 reply; 13+ messages in thread
From: Ard Biesheuvel @ 2015-11-26 12:06 UTC (permalink / raw)
  To: Matt Fleming
  Cc: linux-efi-u79uwXL29TY76Z2rM5mHXA, Leif Lindholm, Mark Rutland,
	Matthew Garrett, Tony Luck

On 26 November 2015 at 10:54, Matt Fleming <matt-mF/unelCI9GS6iBeEJttW/XRex20P6io@public.gmane.org> wrote:
> On Thu, 19 Nov, at 02:36:29PM, Ard Biesheuvel wrote:
>> The non-blocking version of efivar_entry_set() gives up directly on
>> failure to acquire __efivars->lock. However, it also calls check_var_size(),
>> whose implementation calls the ordinary query_variable_info() and
>> set_variable() runtime service wrappers, meaning we could still deadlock
>> if efivar_entry_set_nonblocking() is called from an atomic context that
>> interrupted a runtime service already in progress on the same CPU.
>>
>> So drop the call to check_var_size(). This is potentially unsafe on some
>> UEFI implementations that fail to boot if the varstore fills up, but
>> those systems are unlikely to be using efi-pstore in the first place.
>
> There is simply no way you can make that assumption. The whole "my
> NVRAM is full, now my machine is bricked" issue arose because
> efi-pstore was filling the NVRAM with crash logs; that's how we
> triggered the problem in the first place.
>
> The locking here is non-trivial, so it's worth spelling out. There are
> two spinlocks we need to concern ourselves with,
>
>   1. __efivars->lock
>   2. efi_runtime_lock
>
> All EFI variable operations are performed with __efivars->lock held,
> apart from the x86 efi_delete_dummy_variable() call during boot when
> we're UP anyway. For all intents and purposes, when we're SMP,
> __efivars->lock is held for all EFI variable operations.
>
> So the only potential for deadlock if CPU A is doing variable calls is
> if CPU A or CPU B is doing efi_reset() or efi_capsule_*(). That's not
> an impossible scenario (particularly on arm64 where efi_reset() is
> used more frequently), but it gives you some clue as to when deadlock
> might be a problem.
>

The likelihood for deadlock is low considering the existence of
__efivars->lock, even on arm64, but I would still like to get this
straightened out one way or the other.

>> Signed-off-by: Ard Biesheuvel <ard.biesheuvel-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
>> ---
>>  drivers/firmware/efi/vars.c | 12 +++---------
>>  1 file changed, 3 insertions(+), 9 deletions(-)
>>
>> diff --git a/drivers/firmware/efi/vars.c b/drivers/firmware/efi/vars.c
>> index 70a0fb10517f..caccdbffc1d0 100644
>> --- a/drivers/firmware/efi/vars.c
>> +++ b/drivers/firmware/efi/vars.c
>> @@ -615,12 +615,6 @@ efivar_entry_set_nonblocking(efi_char16_t *name, efi_guid_t vendor,
>>       if (!spin_trylock_irqsave(&__efivars->lock, flags))
>>               return -EBUSY;
>>
>> -     status = check_var_size(attributes, size + ucs2_strsize(name, 1024));
>> -     if (status != EFI_SUCCESS) {
>> -             spin_unlock_irqrestore(&__efivars->lock, flags);
>> -             return -ENOSPC;
>> -     }
>> -
>>       status = ops->set_variable_nonblocking(name, &vendor, attributes,
>>                                              size, data);
>
> We still need some validation to occur if efi_no_storage_paranoia is
> unset, i.e. "Be paranoid about how much NVRAM we use".
>
> However, efi_query_variable_store() actually does two things,
>
>   1. Check if the write pushes free NVRAM space below EFI_MIN_RESERVE
>   2. If it does, attempt to trigger garbage collection
>
> Now, if you're in the belly of a kdump handler, attempting to trigger
> garbage collection to ensure the write succeeds should be the last
> thing on your mind. Everything you do is a last ditch attempt to
> record the reason your machine is going down.
>
> I think the best solution would be to only perform step 1. above in
> the efi_pstore_write() code path, and keep a cached copy of
> query_variable_info() that we can perform lockless queries on. We can
> initially grab it on boot and update it with every ->set_variable()
> call.
>
> Thoughts?

How would this be affected by things like suspend/resume, hibernation
or variable accesses made in SMM context (i.e., which the kernel knows
nothing about)
If we need to take all of that into account as well, we're probably
better off adding a nonblocking query_variable_info() call after all,
and wiring it up to efi_query_variable_store(), i.e., make it take a
'bool nonblocking' argument and choose between the two versions of
each of the hooks it uses.

-- 
Ard.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 4/4] efi: runtime-wrappers: run UEFI Runtime Services with interrupts enabled
       [not found]         ` <20151126102345.GD2765-mF/unelCI9GS6iBeEJttW/XRex20P6io@public.gmane.org>
@ 2015-11-26 12:09           ` Ard Biesheuvel
  0 siblings, 0 replies; 13+ messages in thread
From: Ard Biesheuvel @ 2015-11-26 12:09 UTC (permalink / raw)
  To: Matt Fleming
  Cc: linux-efi-u79uwXL29TY76Z2rM5mHXA, Leif Lindholm, Mark Rutland

On 26 November 2015 at 11:23, Matt Fleming <matt-mF/unelCI9GS6iBeEJttW/XRex20P6io@public.gmane.org> wrote:
> On Thu, 19 Nov, at 02:36:31PM, Ard Biesheuvel wrote:
>> The UEFI spec allows Runtime Services to be invoked with interrupts
>> enabled. The only reason we were disabling interrupts was to prevent
>> recursive calls into the services on the same CPU, which will lead to
>> deadlock. However, the only context where such invocations may occur
>> legally is from efi-pstore via efivars, and that code has been updated
>> to call a non-blocking alternative when invoked from a non-interruptible
>> context.
>>
>> So instead, update the ordinary, blocking UEFI Runtime Services wrappers
>> to execute with interrupts enabled.
>
> Please document the justification for this change; why do you *need*
> interrupts enabled as opposed to doing it because the spec allows it.
>

The primary concern is EFI writes on a UP system to a variable store
that is backed by RPMB/MMC storage through some marshalling layer in
the secure OS. I will add that to the commit log for v2

>> Signed-off-by: Ard Biesheuvel <ard.biesheuvel-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
>> ---
>>  drivers/firmware/efi/runtime-wrappers.c | 88 +++++++++++---------
>>  1 file changed, 49 insertions(+), 39 deletions(-)
>>
>> diff --git a/drivers/firmware/efi/runtime-wrappers.c b/drivers/firmware/efi/runtime-wrappers.c
>> index a624f7445d11..31e131f09530 100644
>> --- a/drivers/firmware/efi/runtime-wrappers.c
>> +++ b/drivers/firmware/efi/runtime-wrappers.c
>> @@ -71,27 +71,29 @@ __weak DEFINE_SPINLOCK(rtc_lock);
>>
>>  static efi_status_t virt_efi_get_time(efi_time_t *tm, efi_time_cap_t *tc)
>>  {
>> -     unsigned long flags;
>>       efi_status_t status;
>>
>> -     spin_lock_irqsave(&rtc_lock, flags);
>> +     BUG_ON(in_irq());
>> +
>> +     spin_lock(&rtc_lock);
>>       spin_lock(&efi_runtime_lock);
>>       status = efi_call_virt(get_time, tm, tc);
>>       spin_unlock(&efi_runtime_lock);
>> -     spin_unlock_irqrestore(&rtc_lock, flags);
>> +     spin_unlock(&rtc_lock);
>>       return status;
>>  }
>
> This looks wrong. rtc_lock can be grabbed from IRQ context. Maybe now
> is the time to rip out rtc_lock? It's __weak for arm64 anyway and x86
> doesn't even use these functions. Though I notice that arm also has an
> rtc_lock.
>
>>  static efi_status_t virt_efi_set_time(efi_time_t *tm)
>>  {
>> -     unsigned long flags;
>>       efi_status_t status;
>>
>> -     spin_lock_irqsave(&rtc_lock, flags);
>> +     BUG_ON(in_irq());
>> +
>> +     spin_lock(&rtc_lock);
>
> The thing about using BUG_ON() here is that you get no LOCKDEP info if
> you trigger it, nor any stack traces from the softlockup detector.
> It's much better to leave spin_lock() alone to do its thing.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 2/4] efi: efivars: don't rely on blocking operations in non-blocking set_var()
       [not found]             ` <CAKv+Gu8XqP+MgT1vkC-CLkEZ=p+X2tA3fQuprUCRJ1UhrFz-rg-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
@ 2015-11-27 21:18               ` Matt Fleming
       [not found]                 ` <20151127211836.GB13918-mF/unelCI9GS6iBeEJttW/XRex20P6io@public.gmane.org>
  0 siblings, 1 reply; 13+ messages in thread
From: Matt Fleming @ 2015-11-27 21:18 UTC (permalink / raw)
  To: Ard Biesheuvel
  Cc: linux-efi-u79uwXL29TY76Z2rM5mHXA, Leif Lindholm, Mark Rutland,
	Matthew Garrett, Tony Luck

On Thu, 26 Nov, at 01:06:27PM, Ard Biesheuvel wrote:
> 
> How would this be affected by things like suspend/resume, hibernation
> or variable accesses made in SMM context (i.e., which the kernel knows
> nothing about)
> If we need to take all of that into account as well, we're probably
> better off adding a nonblocking query_variable_info() call after all,
> and wiring it up to efi_query_variable_store(), i.e., make it take a
> 'bool nonblocking' argument and choose between the two versions of
> each of the hooks it uses.

I *think* that'd be OK. The thing I wanted to avoid was a
proliferation of nonblocking versions of the standard EFI calls, but
limiting it to adding query_variable_info() doesn't seem too bad.

Let me think about it over the weekend. 

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 2/4] efi: efivars: don't rely on blocking operations in non-blocking set_var()
       [not found]                 ` <20151127211836.GB13918-mF/unelCI9GS6iBeEJttW/XRex20P6io@public.gmane.org>
@ 2015-11-30 11:12                   ` Matt Fleming
  0 siblings, 0 replies; 13+ messages in thread
From: Matt Fleming @ 2015-11-30 11:12 UTC (permalink / raw)
  To: Ard Biesheuvel
  Cc: linux-efi-u79uwXL29TY76Z2rM5mHXA, Leif Lindholm, Mark Rutland,
	Matthew Garrett, Tony Luck

On Fri, 27 Nov, at 09:18:36PM, Matt Fleming wrote:
> 
> I *think* that'd be OK. The thing I wanted to avoid was a
> proliferation of nonblocking versions of the standard EFI calls, but
> limiting it to adding query_variable_info() doesn't seem too bad.
> 
> Let me think about it over the weekend. 

I couldn't come up with anything better. Go for it!

^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2015-11-30 11:12 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-11-19 13:36 [PATCH 0/4] efi: run UEFI services with interrupts enabled Ard Biesheuvel
     [not found] ` <1447940191-30705-1-git-send-email-ard.biesheuvel-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
2015-11-19 13:36   ` [PATCH 1/4] efi: expose non-blocking set_variable() wrapper to efivars Ard Biesheuvel
     [not found]     ` <1447940191-30705-2-git-send-email-ard.biesheuvel-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
2015-11-26  9:55       ` Matt Fleming
2015-11-19 13:36   ` [PATCH 2/4] efi: efivars: don't rely on blocking operations in non-blocking set_var() Ard Biesheuvel
     [not found]     ` <1447940191-30705-3-git-send-email-ard.biesheuvel-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
2015-11-26  9:54       ` Matt Fleming
     [not found]         ` <20151126095441.GA2765-mF/unelCI9GS6iBeEJttW/XRex20P6io@public.gmane.org>
2015-11-26 12:06           ` Ard Biesheuvel
     [not found]             ` <CAKv+Gu8XqP+MgT1vkC-CLkEZ=p+X2tA3fQuprUCRJ1UhrFz-rg-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2015-11-27 21:18               ` Matt Fleming
     [not found]                 ` <20151127211836.GB13918-mF/unelCI9GS6iBeEJttW/XRex20P6io@public.gmane.org>
2015-11-30 11:12                   ` Matt Fleming
2015-11-19 13:36   ` [PATCH 3/4] efi: runtime-wrappers: remove out of date comment regarding in_nmi() Ard Biesheuvel
     [not found]     ` <1447940191-30705-4-git-send-email-ard.biesheuvel-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
2015-11-26  9:58       ` Matt Fleming
2015-11-19 13:36   ` [PATCH 4/4] efi: runtime-wrappers: run UEFI Runtime Services with interrupts enabled Ard Biesheuvel
     [not found]     ` <1447940191-30705-5-git-send-email-ard.biesheuvel-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
2015-11-26 10:23       ` Matt Fleming
     [not found]         ` <20151126102345.GD2765-mF/unelCI9GS6iBeEJttW/XRex20P6io@public.gmane.org>
2015-11-26 12:09           ` Ard Biesheuvel

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.