diff mbox series

Grab the EFI System Reference Table and check it

Message ID YSt7P0a63GCxygAa@itl-email (mailing list archive)
State New, archived
Headers show
Series Grab the EFI System Reference Table and check it | expand

Commit Message

Demi Marie Obenour Aug. 29, 2021, 12:18 p.m. UTC
The EFI System Reference Table (ESRT) is necessary for fwupd to identify
firmware updates to install.  According to the UEFI specification ยง23.4,
the table shall be stored in memory of type EfiBootServicesData.
Therefore, Xen must avoid reusing that memory for other purposes, so
that Linux can access the ESRT.

See https://lore.kernel.org/xen-devel/20200818184018.GN1679@mail-itl/T/
for details.

Signed-off-by: Demi Marie Obenour <demi@invisiblethingslab.com>
---
 xen/arch/arm/efi/efi-boot.h |  9 ++--
 xen/arch/x86/efi/efi-boot.h |  5 ++-
 xen/common/efi/boot.c       | 90 +++++++++++++++++++++++++++++++++++--
 xen/common/efi/runtime.c    |  1 +
 xen/include/efi/efiapi.h    |  3 ++
 xen/include/xen/efi.h       |  1 +
 6 files changed, 101 insertions(+), 8 deletions(-)

Comments

Jan Beulich Sept. 2, 2021, 9:56 a.m. UTC | #1
On 29.08.2021 14:18, Demi Marie Obenour wrote:
> The EFI System Reference Table (ESRT) is necessary for fwupd to identify

To properly match the UEFI spec, both here and in the subject I suppose
you mean "Resource" instead of "Reference"?

> @@ -171,7 +172,7 @@ static void __init efi_arch_process_memory_map(EFI_SYSTEM_TABLE *SystemTable,
>          {
>          case EfiBootServicesCode:
>          case EfiBootServicesData:
> -            if ( map_bs )
> +            if ( map_bs || desc == esrt_desc )
>              {
>          default:
>                  type = E820_RESERVED;

Elsewhere you also permit ESRT to live in EfiRuntimeServicesData. The
code addition here looks to contradict that.

Furthermore you're potentially making a large chunk of data unavailable
just because it contains a small piece of data which needs to be
preserved.

> --- a/xen/common/efi/boot.c
> +++ b/xen/common/efi/boot.c
> @@ -93,6 +93,23 @@ typedef struct _EFI_LOAD_OPTION {
>      CHAR16 Description[];
>  } EFI_LOAD_OPTION;
>  
> +struct esrt_entry {
> +    EFI_GUID fw_class;
> +    uint32_t fw_type;
> +    uint32_t fw_version;
> +    uint32_t fw_lowest_supported_version;
> +    uint32_t fw_capsule_flags;
> +    uint32_t fw_last_attempt_version;
> +    uint32_t fw_last_attempt_status;
> +};
> +
> +struct esrt {
> +    uint32_t esrt_count;
> +    uint32_t esrt_max;
> +    uint64_t esrt_version;
> +    struct esrt_entry esrt_entries[];
> +};

These don't match the gnu-efi style (where we're generally taking
pieces from), but since I can't spot that package supporting ESRT at
all, we have to roll our own, so the style inconsistency is perhaps
okay. But at least - alongside EFI_GUID - I think you want to use EFI
types (UINT<nn>).

> @@ -166,6 +183,41 @@ static void __init PrintErr(const CHAR16 *s)
>      StdErr->OutputString(StdErr, (CHAR16 *)s );
>  }
>  
> +enum esrt_status {
> +    ESRT_NOTFOUND = 0,
> +    ESRT_GOOD,
> +    ESRT_BAD,
> +};

I think we generally try to limit all-caps identifiers to macros.

> +static enum esrt_status __init is_esrt_valid(
> +    const EFI_MEMORY_DESCRIPTOR *const desc) {

Style nit: Brace goes on it own line.

> +    size_t available_len, esrt_len, len;
> +    const uintptr_t physical_start = desc->PhysicalStart;
> +    const uintptr_t esrt = efi.esrt;

Like said above and to match other code in this file, EFI types
(UINTN in particular) should be preferred.

> +    const struct esrt *esrt_ptr;
> +
> +    len = desc->NumberOfPages << EFI_PAGE_SHIFT;
> +    if ( esrt == EFI_INVALID_TABLE_ADDR )
> +        return ESRT_BAD;

I think this is rather ESRT_NOTFOUND, too? But see also below.

> +    if ( physical_start > esrt || esrt - physical_start >= len )
> +        return ESRT_NOTFOUND;
> +    if ( ! ( desc->Attribute & EFI_MEMORY_RUNTIME ) &&
> +           ( desc->Type != EfiRuntimeServicesData ) &&
> +           ( desc->Type != EfiBootServicesData ) )

As you say in the description, the spec mandates the latter. I can
accept you also wanting to cover other plausible types, but then
please add a brief comment mentioning the intentional diverging
from what the spec says. This would then hopefully also clarify
the curious redundancy between checking EFI_MEMORY_RUNTIME and
EfiRuntimeServicesData.

As to style - there are quite a few too many blanks here.

> +        return ESRT_BAD;
> +    available_len = len - (esrt - physical_start);
> +    if ( available_len < offsetof(struct esrt, esrt_entries) )

Iirc both gcc and clang allow sizeof(*estr_ptr) despite the
flexible array member - please prefer that form (also again further
down).

> +        return ESRT_BAD;
> +    esrt_ptr = (const struct esrt *) esrt;

For this and the sizeof() below to be applicable, you need to check
the version to be 1. Also, as to style, there's not supposed to be
any blank after the closing parenthesis.

> +    if ( esrt_ptr->esrt_count <= 0 ||
> +         __builtin_mul_overflow(esrt_ptr->esrt_count,
> +                                sizeof(struct esrt_entry),

sizeof(esrt_ptr->esrt_entries[0]) or alike please.

Also - is __builtin_mul_overflow() supported by all gcc versions we
support building this code with? And is this overflow check needed
in the first place? After all we build EFI code only for 64-bit
hypervisors, and a 32-bit quantity times the sizeof() can't overflow
in 64 bits.

> +                                &esrt_len) ||
> +         esrt_len > available_len - offsetof(struct esrt, esrt_entries) )
> +        return ESRT_BAD;
> +    return ESRT_GOOD;
> +}

Style nit: Please put a blank line ahead of the function's main
"return" statement.

> @@ -835,6 +887,10 @@ static void __init efi_tables(void)
>  {
>      unsigned int i;
>  
> +    BUILD_BUG_ON(sizeof(struct esrt_entry) != 40);
> +    BUILD_BUG_ON(_Alignof(struct esrt_entry) != 4);
> +    BUILD_BUG_ON(offsetof(struct esrt, esrt_entries) != 16);

Why? We're not doing this for other types declared locally in this
source file afaics. Plus - did you check that all gcc versions we
support for building this code can actually deal with the C99
_Alignof()? I would generally have expected you to use __alignof(),
as we do elsewhere.

> @@ -1042,9 +1101,7 @@ static void __init efi_exit_boot(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *Syste
>      EFI_STATUS status;
>      UINTN info_size = 0, map_key;
>      bool retry;
> -#ifdef CONFIG_EFI_SET_VIRTUAL_ADDRESS_MAP
>      unsigned int i;
> -#endif
>  
>      efi_bs->GetMemoryMap(&info_size, NULL, &map_key,
>                           &efi_mdesc_size, &mdesc_ver);
> @@ -1055,6 +1112,10 @@ static void __init efi_exit_boot(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *Syste
>  
>      for ( retry = false; ; retry = true )
>      {
> +        enum esrt_status esrt_status = ESRT_NOTFOUND;
> +        const EFI_MEMORY_DESCRIPTOR *esrt_desc =
> +            (const EFI_MEMORY_DESCRIPTOR*) EFI_INVALID_TABLE_ADDR;

Style: Misplaced blank.

> @@ -1063,8 +1124,31 @@ static void __init efi_exit_boot(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *Syste
>          if ( EFI_ERROR(status) )
>              PrintErrMesg(L"Cannot obtain memory map", status);
>  
> +        for ( i = 0; i < efi_memmap_size; i += efi_mdesc_size )
> +        {
> +            EFI_MEMORY_DESCRIPTOR *desc = efi_memmap + i;
> +            switch ( is_esrt_valid(desc) )

Style: Blank line please between declaration(s) and statement(s).
I question the need for the local variable though - you only need
it ...

> +            {
> +            case ESRT_NOTFOUND:
> +                break;
> +            case ESRT_GOOD:
> +                esrt_status = ESRT_GOOD;
> +                esrt_desc = desc;

... here.

> +                break;
> +            case ESRT_BAD:
> +                esrt_status = ESRT_BAD;
> +                break;
> +            }
> +        }
> +        if ( esrt_status != ESRT_GOOD )
> +            efi.esrt = EFI_INVALID_TABLE_ADDR;

In the end you don't distinguish ESRT_BAD from ESRT_NOTFOUND, so I
don't see the need for the enum. The function can simply return
bool then.

> +        /*
> +         * We cannot pass efi.esrt because we need to explicitly compare the
> +         * descriptor pointers for equality.
> +         */
>          efi_arch_process_memory_map(SystemTable, efi_memmap, efi_memmap_size,
> -                                    efi_mdesc_size, mdesc_ver);
> +                                    efi_mdesc_size, mdesc_ver, esrt_desc);

I'm not convinced this is "need" (as the comment says), but at best
"want", for being an implementation choice. But the model itself is
suspect to me in the first place, as said above.

> --- a/xen/include/xen/efi.h
> +++ b/xen/include/xen/efi.h
> @@ -19,6 +19,7 @@ struct efi {
>      unsigned long acpi20;       /* ACPI table (ACPI 2.0) */
>      unsigned long smbios;       /* SM BIOS table */
>      unsigned long smbios3;      /* SMBIOS v3 table */
> +    unsigned long esrt;         /* EFI System Reference Table */
>  };

I have to admit that I'm having difficulty spotting why this field
needs adding here: No other component in Xen wants to use it. Yet
that's what the struct is for.

Jan
diff mbox series

Patch

diff --git a/xen/arch/arm/efi/efi-boot.h b/xen/arch/arm/efi/efi-boot.h
index cf9c37153f..d8de478022 100644
--- a/xen/arch/arm/efi/efi-boot.h
+++ b/xen/arch/arm/efi/efi-boot.h
@@ -142,7 +142,8 @@  static bool __init meminfo_add_bank(struct meminfo *mem,
 
 static EFI_STATUS __init efi_process_memory_map_bootinfo(EFI_MEMORY_DESCRIPTOR *map,
                                                 UINTN mmap_size,
-                                                UINTN desc_size)
+                                                UINTN desc_size,
+                                                const EFI_MEMORY_DESCRIPTOR *const esrt_desc)
 {
     int Index;
     EFI_MEMORY_DESCRIPTOR *desc_ptr = map;
@@ -154,6 +155,7 @@  static EFI_STATUS __init efi_process_memory_map_bootinfo(EFI_MEMORY_DESCRIPTOR *
               desc_ptr->Type == EfiLoaderCode ||
               desc_ptr->Type == EfiLoaderData ||
               (!map_bs &&
+               desc != esrt_desc &&
                (desc_ptr->Type == EfiBootServicesCode ||
                 desc_ptr->Type == EfiBootServicesData))) )
         {
@@ -359,11 +361,12 @@  static void __init efi_arch_process_memory_map(EFI_SYSTEM_TABLE *SystemTable,
                                                void *map,
                                                UINTN map_size,
                                                UINTN desc_size,
-                                               UINT32 desc_ver)
+                                               UINT32 desc_ver,
+                                               const EFI_MEMORY_DESCRIPTOR *const esrt_desc)
 {
     EFI_STATUS status;
 
-    status = efi_process_memory_map_bootinfo(map, map_size, desc_size);
+    status = efi_process_memory_map_bootinfo(map, map_size, desc_size, esrt_desc);
     if ( EFI_ERROR(status) )
         blexit(L"EFI memory map processing failed");
 
diff --git a/xen/arch/x86/efi/efi-boot.h b/xen/arch/x86/efi/efi-boot.h
index fb217031ff..f381855368 100644
--- a/xen/arch/x86/efi/efi-boot.h
+++ b/xen/arch/x86/efi/efi-boot.h
@@ -154,7 +154,8 @@  static void __init efi_arch_process_memory_map(EFI_SYSTEM_TABLE *SystemTable,
                                                void *map,
                                                UINTN map_size,
                                                UINTN desc_size,
-                                               UINT32 desc_ver)
+                                               UINT32 desc_ver,
+                                               const EFI_MEMORY_DESCRIPTOR *const esrt_desc)
 {
     struct e820entry *e;
     unsigned int i;
@@ -171,7 +172,7 @@  static void __init efi_arch_process_memory_map(EFI_SYSTEM_TABLE *SystemTable,
         {
         case EfiBootServicesCode:
         case EfiBootServicesData:
-            if ( map_bs )
+            if ( map_bs || desc == esrt_desc )
             {
         default:
                 type = E820_RESERVED;
diff --git a/xen/common/efi/boot.c b/xen/common/efi/boot.c
index 758f9d74d2..0603172074 100644
--- a/xen/common/efi/boot.c
+++ b/xen/common/efi/boot.c
@@ -93,6 +93,23 @@  typedef struct _EFI_LOAD_OPTION {
     CHAR16 Description[];
 } EFI_LOAD_OPTION;
 
+struct esrt_entry {
+    EFI_GUID fw_class;
+    uint32_t fw_type;
+    uint32_t fw_version;
+    uint32_t fw_lowest_supported_version;
+    uint32_t fw_capsule_flags;
+    uint32_t fw_last_attempt_version;
+    uint32_t fw_last_attempt_status;
+};
+
+struct esrt {
+    uint32_t esrt_count;
+    uint32_t esrt_max;
+    uint64_t esrt_version;
+    struct esrt_entry esrt_entries[];
+};
+
 #define LOAD_OPTION_ACTIVE              0x00000001
 
 union string {
@@ -166,6 +183,41 @@  static void __init PrintErr(const CHAR16 *s)
     StdErr->OutputString(StdErr, (CHAR16 *)s );
 }
 
+enum esrt_status {
+    ESRT_NOTFOUND = 0,
+    ESRT_GOOD,
+    ESRT_BAD,
+};
+
+static enum esrt_status __init is_esrt_valid(
+    const EFI_MEMORY_DESCRIPTOR *const desc) {
+    size_t available_len, esrt_len, len;
+    const uintptr_t physical_start = desc->PhysicalStart;
+    const uintptr_t esrt = efi.esrt;
+    const struct esrt *esrt_ptr;
+
+    len = desc->NumberOfPages << EFI_PAGE_SHIFT;
+    if ( esrt == EFI_INVALID_TABLE_ADDR )
+        return ESRT_BAD;
+    if ( physical_start > esrt || esrt - physical_start >= len )
+        return ESRT_NOTFOUND;
+    if ( ! ( desc->Attribute & EFI_MEMORY_RUNTIME ) &&
+           ( desc->Type != EfiRuntimeServicesData ) &&
+           ( desc->Type != EfiBootServicesData ) )
+        return ESRT_BAD;
+    available_len = len - (esrt - physical_start);
+    if ( available_len < offsetof(struct esrt, esrt_entries) )
+        return ESRT_BAD;
+    esrt_ptr = (const struct esrt *) esrt;
+    if ( esrt_ptr->esrt_count <= 0 ||
+         __builtin_mul_overflow(esrt_ptr->esrt_count,
+                                sizeof(struct esrt_entry),
+                                &esrt_len) ||
+         esrt_len > available_len - offsetof(struct esrt, esrt_entries) )
+        return ESRT_BAD;
+    return ESRT_GOOD;
+}
+
 /*
  * Include architecture specific implementation here, which references the
  * static globals defined above.
@@ -835,6 +887,10 @@  static void __init efi_tables(void)
 {
     unsigned int i;
 
+    BUILD_BUG_ON(sizeof(struct esrt_entry) != 40);
+    BUILD_BUG_ON(_Alignof(struct esrt_entry) != 4);
+    BUILD_BUG_ON(offsetof(struct esrt, esrt_entries) != 16);
+
     /* Obtain basic table pointers. */
     for ( i = 0; i < efi_num_ct; ++i )
     {
@@ -843,6 +899,7 @@  static void __init efi_tables(void)
         static EFI_GUID __initdata mps_guid = MPS_TABLE_GUID;
         static EFI_GUID __initdata smbios_guid = SMBIOS_TABLE_GUID;
         static EFI_GUID __initdata smbios3_guid = SMBIOS3_TABLE_GUID;
+        static EFI_GUID __initdata esrt_guid = ESRT_GUID;
 
         if ( match_guid(&acpi2_guid, &efi_ct[i].VendorGuid) )
 	       efi.acpi20 = (long)efi_ct[i].VendorTable;
@@ -854,6 +911,8 @@  static void __init efi_tables(void)
 	       efi.smbios = (long)efi_ct[i].VendorTable;
         if ( match_guid(&smbios3_guid, &efi_ct[i].VendorGuid) )
 	       efi.smbios3 = (long)efi_ct[i].VendorTable;
+        if ( match_guid(&esrt_guid, &efi_ct[i].VendorGuid) )
+	       efi.esrt = (long)efi_ct[i].VendorTable;
     }
 
 #ifndef CONFIG_ARM /* TODO - disabled until implemented on ARM */
@@ -1042,9 +1101,7 @@  static void __init efi_exit_boot(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *Syste
     EFI_STATUS status;
     UINTN info_size = 0, map_key;
     bool retry;
-#ifdef CONFIG_EFI_SET_VIRTUAL_ADDRESS_MAP
     unsigned int i;
-#endif
 
     efi_bs->GetMemoryMap(&info_size, NULL, &map_key,
                          &efi_mdesc_size, &mdesc_ver);
@@ -1055,6 +1112,10 @@  static void __init efi_exit_boot(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *Syste
 
     for ( retry = false; ; retry = true )
     {
+        enum esrt_status esrt_status = ESRT_NOTFOUND;
+        const EFI_MEMORY_DESCRIPTOR *esrt_desc =
+            (const EFI_MEMORY_DESCRIPTOR*) EFI_INVALID_TABLE_ADDR;
+
         efi_memmap_size = info_size;
         status = SystemTable->BootServices->GetMemoryMap(&efi_memmap_size,
                                                          efi_memmap, &map_key,
@@ -1063,8 +1124,31 @@  static void __init efi_exit_boot(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *Syste
         if ( EFI_ERROR(status) )
             PrintErrMesg(L"Cannot obtain memory map", status);
 
+        for ( i = 0; i < efi_memmap_size; i += efi_mdesc_size )
+        {
+            EFI_MEMORY_DESCRIPTOR *desc = efi_memmap + i;
+            switch ( is_esrt_valid(desc) )
+            {
+            case ESRT_NOTFOUND:
+                break;
+            case ESRT_GOOD:
+                esrt_status = ESRT_GOOD;
+                esrt_desc = desc;
+                break;
+            case ESRT_BAD:
+                esrt_status = ESRT_BAD;
+                break;
+            }
+        }
+        if ( esrt_status != ESRT_GOOD )
+            efi.esrt = EFI_INVALID_TABLE_ADDR;
+
+        /*
+         * We cannot pass efi.esrt because we need to explicitly compare the
+         * descriptor pointers for equality.
+         */
         efi_arch_process_memory_map(SystemTable, efi_memmap, efi_memmap_size,
-                                    efi_mdesc_size, mdesc_ver);
+                                    efi_mdesc_size, mdesc_ver, esrt_desc);
 
         efi_arch_pre_exit_boot();
 
diff --git a/xen/common/efi/runtime.c b/xen/common/efi/runtime.c
index 375b94229e..8ae086feaa 100644
--- a/xen/common/efi/runtime.c
+++ b/xen/common/efi/runtime.c
@@ -68,6 +68,7 @@  struct efi __read_mostly efi = {
 	.mps    = EFI_INVALID_TABLE_ADDR,
 	.smbios = EFI_INVALID_TABLE_ADDR,
 	.smbios3 = EFI_INVALID_TABLE_ADDR,
+	.esrt   = EFI_INVALID_TABLE_ADDR,
 };
 
 const struct efi_pci_rom *__read_mostly efi_pci_roms;
diff --git a/xen/include/efi/efiapi.h b/xen/include/efi/efiapi.h
index a616d1238a..42ef3e1c8c 100644
--- a/xen/include/efi/efiapi.h
+++ b/xen/include/efi/efiapi.h
@@ -882,6 +882,9 @@  typedef struct _EFI_BOOT_SERVICES {
 #define SAL_SYSTEM_TABLE_GUID    \
     { 0xeb9d2d32, 0x2d88, 0x11d3, {0x9a, 0x16, 0x0, 0x90, 0x27, 0x3f, 0xc1, 0x4d} }
 
+#define ESRT_GUID    \
+    { 0xb122a263, 0x3661, 0x4f68, {0x99, 0x29, 0x78, 0xf8, 0xb0, 0xd6, 0x21, 0x80} }
+
 
 typedef struct _EFI_CONFIGURATION_TABLE {
     EFI_GUID                VendorGuid;
diff --git a/xen/include/xen/efi.h b/xen/include/xen/efi.h
index 94a7e547f9..bb149fbfb9 100644
--- a/xen/include/xen/efi.h
+++ b/xen/include/xen/efi.h
@@ -19,6 +19,7 @@  struct efi {
     unsigned long acpi20;       /* ACPI table (ACPI 2.0) */
     unsigned long smbios;       /* SM BIOS table */
     unsigned long smbios3;      /* SMBIOS v3 table */
+    unsigned long esrt;         /* EFI System Reference Table */
 };
 
 extern struct efi efi;