[PATCH 2/2] efi: implement mandatory locking for UEFI Runtime Services

Ard Biesheuvel ard.biesheuvel at linaro.org
Wed Jul 2 03:10:02 PDT 2014


According to section 7.1 of the UEFI spec, Runtime Services are not fully
reentrant, and there are particular combinations of calls that need to be
serialized.

Signed-off-by: Ard Biesheuvel <ard.biesheuvel at linaro.org>
---
 drivers/firmware/efi/runtime-wrappers.c | 109 +++++++++++++++++++++++++++++---
 1 file changed, 99 insertions(+), 10 deletions(-)

diff --git a/drivers/firmware/efi/runtime-wrappers.c b/drivers/firmware/efi/runtime-wrappers.c
index 10daa4bbb258..6588d054af99 100644
--- a/drivers/firmware/efi/runtime-wrappers.c
+++ b/drivers/firmware/efi/runtime-wrappers.c
@@ -15,10 +15,50 @@
  */
 
 #include <linux/efi.h>
-#include <linux/spinlock.h>             /* spinlock_t */
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
 #include <asm/efi.h>
 
 /*
+ * According to section 7.1 of the UEFI spec, Runtime Services are not fully
+ * reentrant, and there are particular combinations of calls that need to be
+ * serialized.
+ *
+ * Table 31. Rules for Reentry Into Runtime Services
+ * +------------------------------------+-------------------------------+
+ * | If previous call is busy in	| Forbidden to call		|
+ * +------------------------------------+-------------------------------+
+ * | Any				| SetVirtualAddressMap()	|
+ * +------------------------------------+-------------------------------+
+ * | ConvertPointer()			| ConvertPointer()		|
+ * +------------------------------------+-------------------------------+
+ * | SetVariable()			| ResetSystem()			|
+ * | UpdateCapsule()			|				|
+ * | SetTime()				|				|
+ * | SetWakeupTime()			|				|
+ * | GetNextHighMonotonicCount()	|				|
+ * +------------------------------------+-------------------------------+
+ * | GetVariable()			| GetVariable()			|
+ * | GetNextVariableName()		| GetNextVariableName()		|
+ * | SetVariable()			| SetVariable()			|
+ * | QueryVariableInfo()		| QueryVariableInfo()		|
+ * | UpdateCapsule()			| UpdateCapsule()		|
+ * | QueryCapsuleCapabilities()		| QueryCapsuleCapabilities()	|
+ * | GetNextHighMonotonicCount()	| GetNextHighMonotonicCount()	|
+ * +------------------------------------+-------------------------------+
+ * | GetTime()				| GetTime()			|
+ * | SetTime()				| SetTime()			|
+ * | GetWakeupTime()			| GetWakeupTime()		|
+ * | SetWakeupTime()			| SetWakeupTime()		|
+ * +------------------------------------+-------------------------------+
+ *
+ * The first two are disregarded here, as SetVirtualAddressMap() is called
+ * only once, and very early, and ConvertPointer() is not exposed at all.
+ */
+static DEFINE_MUTEX(var_ro_mutex);
+static DEFINE_MUTEX(var_rw_mutex);
+
+/*
  * 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
@@ -78,14 +118,25 @@ static efi_status_t virt_efi_get_variable(efi_char16_t *name,
 					  unsigned long *data_size,
 					  void *data)
 {
-	return efi_call_virt(get_variable, name, vendor, attr, data_size, data);
+	efi_status_t status;
+
+	mutex_lock(&var_ro_mutex);
+	status = efi_call_virt(get_variable, name, vendor, attr, data_size,
+			       data);
+	mutex_unlock(&var_ro_mutex);
+	return status;
 }
 
 static efi_status_t virt_efi_get_next_variable(unsigned long *name_size,
 					       efi_char16_t *name,
 					       efi_guid_t *vendor)
 {
-	return efi_call_virt(get_next_variable, name_size, name, vendor);
+	efi_status_t status;
+
+	mutex_lock(&var_ro_mutex);
+	status = efi_call_virt(get_next_variable, name_size, name, vendor);
+	mutex_unlock(&var_ro_mutex);
+	return status;
 }
 
 static efi_status_t virt_efi_set_variable(efi_char16_t *name,
@@ -94,7 +145,15 @@ static efi_status_t virt_efi_set_variable(efi_char16_t *name,
 					  unsigned long data_size,
 					  void *data)
 {
-	return efi_call_virt(set_variable, name, vendor, attr, data_size, data);
+	efi_status_t status;
+
+	mutex_lock(&var_ro_mutex);
+	mutex_lock(&var_rw_mutex);
+	status = efi_call_virt(set_variable, name, vendor, attr, data_size,
+			       data);
+	mutex_unlock(&var_rw_mutex);
+	mutex_unlock(&var_ro_mutex);
+	return status;
 }
 
 static efi_status_t virt_efi_query_variable_info(u32 attr,
@@ -102,16 +161,28 @@ static efi_status_t virt_efi_query_variable_info(u32 attr,
 						 u64 *remaining_space,
 						 u64 *max_variable_size)
 {
+	efi_status_t status;
+
 	if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION)
 		return EFI_UNSUPPORTED;
 
-	return efi_call_virt(query_variable_info, attr, storage_space,
-			     remaining_space, max_variable_size);
+	mutex_lock(&var_ro_mutex);
+	status = efi_call_virt(query_variable_info, attr, storage_space,
+			       remaining_space, max_variable_size);
+	mutex_unlock(&var_ro_mutex);
+	return status;
 }
 
 static efi_status_t virt_efi_get_next_high_mono_count(u32 *count)
 {
-	return efi_call_virt(get_next_high_mono_count, count);
+	efi_status_t status;
+
+	mutex_lock(&var_ro_mutex);
+	mutex_lock(&var_rw_mutex);
+	status = efi_call_virt(get_next_high_mono_count, count);
+	mutex_unlock(&var_rw_mutex);
+	mutex_unlock(&var_ro_mutex);
+	return status;
 }
 
 static void virt_efi_reset_system(int reset_type,
@@ -119,17 +190,30 @@ static void virt_efi_reset_system(int reset_type,
 				  unsigned long data_size,
 				  efi_char16_t *data)
 {
+	unsigned long flags;
+
+	mutex_lock(&var_rw_mutex);
+	spin_lock_irqsave(&rtc_lock, flags);
 	__efi_call_virt(reset_system, reset_type, status, data_size, data);
+	spin_unlock_irqrestore(&rtc_lock, flags);
+	mutex_unlock(&var_rw_mutex);
 }
 
 static efi_status_t virt_efi_update_capsule(efi_capsule_header_t **capsules,
 					    unsigned long count,
 					    unsigned long sg_list)
 {
+	efi_status_t status;
+
 	if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION)
 		return EFI_UNSUPPORTED;
 
-	return efi_call_virt(update_capsule, capsules, count, sg_list);
+	mutex_lock(&var_ro_mutex);
+	mutex_lock(&var_rw_mutex);
+	status = efi_call_virt(update_capsule, capsules, count, sg_list);
+	mutex_unlock(&var_rw_mutex);
+	mutex_unlock(&var_ro_mutex);
+	return status;
 }
 
 static efi_status_t virt_efi_query_capsule_caps(efi_capsule_header_t **capsules,
@@ -137,11 +221,16 @@ static efi_status_t virt_efi_query_capsule_caps(efi_capsule_header_t **capsules,
 						u64 *max_size,
 						int *reset_type)
 {
+	efi_status_t status;
+
 	if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION)
 		return EFI_UNSUPPORTED;
 
-	return efi_call_virt(query_capsule_caps, capsules, count, max_size,
-			     reset_type);
+	mutex_lock(&var_ro_mutex);
+	status = efi_call_virt(query_capsule_caps, capsules, count, max_size,
+			       reset_type);
+	mutex_unlock(&var_ro_mutex);
+	return status;
 }
 
 void efi_native_runtime_setup(void)
-- 
1.8.3.2




More information about the linux-arm-kernel mailing list