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

Matt Fleming matt at console-pimps.org
Mon Aug 4 06:00:11 PDT 2014


On Fri, 11 Jul, at 09:09:16AM, Ard Biesheuvel wrote:
> 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. Use a spinlock to serialize all Runtime Services with respect
> to all others, even if this is more than strictly needed.
 
It'd be good to include a point about why we're only using a spinlock,
namely that anything else introduces complication to code that is
unlikely to be performance critical.

[...]

> + *
> + * In order to prevent deadlocks under NMI, the wrappers for these functions
> + * only grab the efi_runtime_lock or rtc_lock spinlocks if !efi_in_nmi().
> + */
> +#ifndef efi_in_nmi
> +#define efi_in_nmi()	(0)
> +#endif

It shouldn't be necessary to do the NMI checking for *all* runtime
services, unless you use, for instance, the timer functions on ARM64.

> +/*
>   * 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
> @@ -30,10 +95,17 @@ static efi_status_t virt_efi_get_time(efi_time_t *tm, efi_time_cap_t *tc)
>  {
>  	unsigned long flags;
>  	efi_status_t status;
> +	bool __in_nmi = efi_in_nmi();
>  
> -	spin_lock_irqsave(&rtc_lock, flags);
> +	if (!__in_nmi) {
> +		spin_lock_irqsave(&rtc_lock, flags);
> +		spin_lock(&efi_runtime_lock);
> +	}
>  	status = efi_call_virt(get_time, tm, tc);
> -	spin_unlock_irqrestore(&rtc_lock, flags);
> +	if (!__in_nmi) {
> +		spin_unlock(&efi_runtime_lock);
> +		spin_unlock_irqrestore(&rtc_lock, flags);
> +	}
>  	return status;
>  }
>  
> @@ -42,8 +114,11 @@ static efi_status_t virt_efi_set_time(efi_time_t *tm)
>  	unsigned long flags;
>  	efi_status_t status;
>  
> +	BUG_ON(efi_in_nmi());

I think we can safely drop these BUG_ON()s. I would expect things to
explode pretty spectacularly anyway, even without them if we're in NMI
context. BUG_ON()s are usually reserved for "this is a fatal condition
and we cannot make any forward progress at all, so stop".

But also see my earlier point about how most of these functions aren't
called from NMI context.

>  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);
> +	unsigned long flags;
> +	efi_status_t status;
> +	bool __in_nmi = efi_in_nmi();
> +
> +	if (!__in_nmi)
> +		spin_lock_irqsave(&efi_runtime_lock, flags);
> +	status = efi_call_virt(get_next_variable, name_size, name, vendor);
> +	if (!__in_nmi)
> +		spin_unlock_irqrestore(&efi_runtime_lock, flags);
> +	return status;

We shouldn't ever be in NMI context when calling get_next_variable(),
right?

> @@ -119,17 +245,33 @@ static void virt_efi_reset_system(int reset_type,
>  				  unsigned long data_size,
>  				  efi_char16_t *data)
>  {
> +	unsigned long flags;
> +	bool __in_nmi = efi_in_nmi();
> +
> +	if (!__in_nmi)
> +		spin_lock_irqsave(&efi_runtime_lock, flags);
>  	__efi_call_virt(reset_system, reset_type, status, data_size, data);
> +	if (!__in_nmi)
> +		spin_unlock_irqrestore(&efi_runtime_lock, flags);
>  }
  
Is it even possible to perform a reset through the usual machinery in
NMI context? I don't think this is possible for x86. What about for
arm64?

>  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;
> +	bool __in_nmi = efi_in_nmi();
> +
>  	if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION)
>  		return EFI_UNSUPPORTED;
>  
> -	return efi_call_virt(update_capsule, capsules, count, sg_list);
> +	if (!__in_nmi)
> +		spin_lock_irqsave(&efi_runtime_lock, flags);
> +	status = efi_call_virt(update_capsule, capsules, count, sg_list);
> +	if (!__in_nmi)
> +		spin_unlock_irqrestore(&efi_runtime_lock, flags);
> +	return status;
>  }

I don't think we need to check for being in NMI context here.

>  static efi_status_t virt_efi_query_capsule_caps(efi_capsule_header_t **capsules,
> @@ -137,11 +279,20 @@ 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;
> +	bool __in_nmi = efi_in_nmi();
> +
>  	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);
> +	if (!__in_nmi)
> +		spin_lock_irqsave(&efi_runtime_lock, flags);
> +	status = efi_call_virt(query_capsule_caps, capsules, count, max_size,
> +			       reset_type);
> +	if (!__in_nmi)
> +		spin_unlock_irqrestore(&efi_runtime_lock, flags);
> +	return status;
>  }

Definitely should not be perfoming QueryCapsuleCapabilities() in NMI
context.

-- 
Matt Fleming, Intel Open Source Technology Center



More information about the linux-arm-kernel mailing list