public inbox for devel@edk2.groups.io
 help / color / mirror / Atom feed
* [edk2-devel] [PATCH v3 1/1] OvmfPkg/VirtNorFlashDxe: sanity-check variables
@ 2023-12-14 15:31 Gerd Hoffmann
  2024-01-03 12:56 ` Laszlo Ersek
  0 siblings, 1 reply; 8+ messages in thread
From: Gerd Hoffmann @ 2023-12-14 15:31 UTC (permalink / raw)
  To: devel; +Cc: Ard Biesheuvel, oliver, mike.maslenkin, Jiewen Yao, Gerd Hoffmann

Extend the ValidateFvHeader function, additionally to the header checks
walk over the list of variables and sanity check them.

In case we find inconsistencies indicating variable store corruption
return EFI_NOT_FOUND so the variable store will be re-initialized.

Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
 OvmfPkg/VirtNorFlashDxe/VirtNorFlashDxe.inf |   1 +
 OvmfPkg/VirtNorFlashDxe/VirtNorFlashFvb.c   | 125 +++++++++++++++++++-
 2 files changed, 121 insertions(+), 5 deletions(-)

diff --git a/OvmfPkg/VirtNorFlashDxe/VirtNorFlashDxe.inf b/OvmfPkg/VirtNorFlashDxe/VirtNorFlashDxe.inf
index 2a3d4a218e61..f549400280a1 100644
--- a/OvmfPkg/VirtNorFlashDxe/VirtNorFlashDxe.inf
+++ b/OvmfPkg/VirtNorFlashDxe/VirtNorFlashDxe.inf
@@ -34,6 +34,7 @@ [LibraryClasses]
   DxeServicesTableLib
   HobLib
   IoLib
+  SafeIntLib
   UefiBootServicesTableLib
   UefiDriverEntryPoint
   UefiLib
diff --git a/OvmfPkg/VirtNorFlashDxe/VirtNorFlashFvb.c b/OvmfPkg/VirtNorFlashDxe/VirtNorFlashFvb.c
index 5ee98e9b595a..d69bf8783d77 100644
--- a/OvmfPkg/VirtNorFlashDxe/VirtNorFlashFvb.c
+++ b/OvmfPkg/VirtNorFlashDxe/VirtNorFlashFvb.c
@@ -12,6 +12,7 @@
 #include <Library/BaseMemoryLib.h>
 #include <Library/MemoryAllocationLib.h>
 #include <Library/PcdLib.h>
+#include <Library/SafeIntLib.h>
 #include <Library/UefiLib.h>
 
 #include <Guid/NvVarStoreFormatted.h>
@@ -185,11 +186,19 @@ ValidateFvHeader (
   IN  NOR_FLASH_INSTANCE  *Instance
   )
 {
-  UINT16                      Checksum;
-  EFI_FIRMWARE_VOLUME_HEADER  *FwVolHeader;
-  VARIABLE_STORE_HEADER       *VariableStoreHeader;
-  UINTN                       VariableStoreLength;
-  UINTN                       FvLength;
+  UINT16                         Checksum;
+  EFI_FIRMWARE_VOLUME_HEADER     *FwVolHeader;
+  VARIABLE_STORE_HEADER          *VariableStoreHeader;
+  UINTN                          VarOffset;
+  UINTN                          VarHeaderEnd;
+  UINTN                          VarNameEnd;
+  UINTN                          VarEnd;
+  AUTHENTICATED_VARIABLE_HEADER  *VarHeader;
+  CHAR16                         *VarName;
+  CONST CHAR8                    *VarState;
+  UINTN                          VariableStoreLength;
+  UINTN                          FvLength;
+  RETURN_STATUS                  Status;
 
   FwVolHeader = (EFI_FIRMWARE_VOLUME_HEADER *)Instance->RegionBaseAddress;
 
@@ -260,6 +269,112 @@ ValidateFvHeader (
     return EFI_NOT_FOUND;
   }
 
+  //
+  // check variables
+  //
+  DEBUG ((DEBUG_INFO, "%a: checking variables\n", __func__));
+  VarOffset = sizeof (*VariableStoreHeader);
+  for ( ; ;) {
+    Status = SafeUintnAdd (VarOffset, sizeof (*VarHeader), &VarHeaderEnd);
+    if (RETURN_ERROR (Status)) {
+      DEBUG ((DEBUG_ERROR, "%a: integer overflow\n", __func__));
+      return EFI_NOT_FOUND;
+    }
+
+    if (VarHeaderEnd >= VariableStoreHeader->Size) {
+      DEBUG ((DEBUG_INFO, "%a: end of var list (no space left)\n", __func__));
+      break;
+    }
+
+    VarHeader = (VOID *)((UINTN)VariableStoreHeader + VarOffset);
+    if (VarHeader->StartId != 0x55aa) {
+      DEBUG ((DEBUG_INFO, "%a: end of var list (no startid)\n", __func__));
+      break;
+    }
+
+    VarName = NULL;
+    switch (VarHeader->State) {
+      // usage: State = VAR_HEADER_VALID_ONLY
+      case VAR_HEADER_VALID_ONLY:
+        VarState = "header-ok";
+        VarName  = L"<unknown>";
+        break;
+
+      // usage: State = VAR_ADDED
+      case VAR_ADDED:
+        VarState = "ok";
+        break;
+
+      // usage: State &= VAR_IN_DELETED_TRANSITION
+      case VAR_ADDED &VAR_IN_DELETED_TRANSITION:
+        VarState = "del-in-transition";
+        break;
+
+      // usage: State &= VAR_DELETED
+      case VAR_ADDED &VAR_DELETED:
+      case VAR_ADDED &VAR_DELETED &VAR_IN_DELETED_TRANSITION:
+        VarState = "deleted";
+        break;
+
+      default:
+        DEBUG ((
+          DEBUG_ERROR,
+          "%a: invalid variable state: 0x%x\n",
+          __func__,
+          VarHeader->State
+          ));
+        return EFI_NOT_FOUND;
+    }
+
+    Status = SafeUintnAdd (VarHeaderEnd, VarHeader->NameSize, &VarNameEnd);
+    if (RETURN_ERROR (Status)) {
+      DEBUG ((DEBUG_ERROR, "%a: integer overflow\n", __func__));
+      return EFI_NOT_FOUND;
+    }
+
+    Status = SafeUintnAdd (VarNameEnd, VarHeader->DataSize, &VarEnd);
+    if (RETURN_ERROR (Status)) {
+      DEBUG ((DEBUG_ERROR, "%a: integer overflow\n", __func__));
+      return EFI_NOT_FOUND;
+    }
+
+    if (VarEnd > VariableStoreHeader->Size) {
+      DEBUG ((
+        DEBUG_ERROR,
+        "%a: invalid variable size: 0x%Lx + 0x%Lx + 0x%x + 0x%x > 0x%x\n",
+        __func__,
+        (UINT64)VarOffset,
+        (UINT64)(sizeof (*VarHeader)),
+        VarHeader->NameSize,
+        VarHeader->DataSize,
+        VariableStoreHeader->Size
+        ));
+      return EFI_NOT_FOUND;
+    }
+
+    if (VarHeader->NameSize & 1) {
+      DEBUG ((DEBUG_ERROR, "%a: invalid name size\n", __func__));
+      return EFI_NOT_FOUND;
+    }
+
+    if (VarName == NULL) {
+      VarName = (VOID *)((UINTN)VariableStoreHeader + VarHeaderEnd);
+    }
+
+    DEBUG ((
+      DEBUG_VERBOSE,
+      "%a: +0x%04Lx: name=0x%x data=0x%x '%s' (%a)\n",
+      __func__,
+      (UINT64)VarOffset,
+      VarHeader->NameSize,
+      VarHeader->DataSize,
+      VarName,
+      VarState
+      ));
+
+    VarOffset = ALIGN_VALUE (VarEnd, 4);
+  }
+
   return EFI_SUCCESS;
 }
 
-- 
2.43.0



-=-=-=-=-=-=-=-=-=-=-=-
Groups.io Links: You receive all messages sent to this group.
View/Reply Online (#112541): https://edk2.groups.io/g/devel/message/112541
Mute This Topic: https://groups.io/mt/103171811/7686176
Group Owner: devel+owner@edk2.groups.io
Unsubscribe: https://edk2.groups.io/g/devel/unsub [rebecca@openfw.io]
-=-=-=-=-=-=-=-=-=-=-=-



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

* Re: [edk2-devel] [PATCH v3 1/1] OvmfPkg/VirtNorFlashDxe: sanity-check variables
  2023-12-14 15:31 [edk2-devel] [PATCH v3 1/1] OvmfPkg/VirtNorFlashDxe: sanity-check variables Gerd Hoffmann
@ 2024-01-03 12:56 ` Laszlo Ersek
  2024-01-03 13:09   ` Laszlo Ersek
  2024-01-03 15:11   ` Gerd Hoffmann
  0 siblings, 2 replies; 8+ messages in thread
From: Laszlo Ersek @ 2024-01-03 12:56 UTC (permalink / raw)
  To: devel, kraxel; +Cc: Ard Biesheuvel, oliver, mike.maslenkin, Jiewen Yao

On 12/14/23 16:31, Gerd Hoffmann wrote:
> Extend the ValidateFvHeader function, additionally to the header checks
> walk over the list of variables and sanity check them.
>
> In case we find inconsistencies indicating variable store corruption
> return EFI_NOT_FOUND so the variable store will be re-initialized.
>
> Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
> ---
>  OvmfPkg/VirtNorFlashDxe/VirtNorFlashDxe.inf |   1 +
>  OvmfPkg/VirtNorFlashDxe/VirtNorFlashFvb.c   | 125 +++++++++++++++++++-
>  2 files changed, 121 insertions(+), 5 deletions(-)
>
> diff --git a/OvmfPkg/VirtNorFlashDxe/VirtNorFlashDxe.inf b/OvmfPkg/VirtNorFlashDxe/VirtNorFlashDxe.inf
> index 2a3d4a218e61..f549400280a1 100644
> --- a/OvmfPkg/VirtNorFlashDxe/VirtNorFlashDxe.inf
> +++ b/OvmfPkg/VirtNorFlashDxe/VirtNorFlashDxe.inf
> @@ -34,6 +34,7 @@ [LibraryClasses]
>    DxeServicesTableLib
>    HobLib
>    IoLib
> +  SafeIntLib
>    UefiBootServicesTableLib
>    UefiDriverEntryPoint
>    UefiLib
> diff --git a/OvmfPkg/VirtNorFlashDxe/VirtNorFlashFvb.c b/OvmfPkg/VirtNorFlashDxe/VirtNorFlashFvb.c
> index 5ee98e9b595a..d69bf8783d77 100644
> --- a/OvmfPkg/VirtNorFlashDxe/VirtNorFlashFvb.c
> +++ b/OvmfPkg/VirtNorFlashDxe/VirtNorFlashFvb.c
> @@ -12,6 +12,7 @@
>  #include <Library/BaseMemoryLib.h>
>  #include <Library/MemoryAllocationLib.h>
>  #include <Library/PcdLib.h>
> +#include <Library/SafeIntLib.h>
>  #include <Library/UefiLib.h>
>
>  #include <Guid/NvVarStoreFormatted.h>
> @@ -185,11 +186,19 @@ ValidateFvHeader (
>    IN  NOR_FLASH_INSTANCE  *Instance
>    )
>  {
> -  UINT16                      Checksum;
> -  EFI_FIRMWARE_VOLUME_HEADER  *FwVolHeader;
> -  VARIABLE_STORE_HEADER       *VariableStoreHeader;
> -  UINTN                       VariableStoreLength;
> -  UINTN                       FvLength;
> +  UINT16                         Checksum;
> +  EFI_FIRMWARE_VOLUME_HEADER     *FwVolHeader;
> +  VARIABLE_STORE_HEADER          *VariableStoreHeader;
> +  UINTN                          VarOffset;
> +  UINTN                          VarHeaderEnd;
> +  UINTN                          VarNameEnd;
> +  UINTN                          VarEnd;
> +  AUTHENTICATED_VARIABLE_HEADER  *VarHeader;

(1) The fact that we try to traverse the variables using
AUTHENTICATED_VARIABLE_HEADER creates a *very slight* inconsistency in
the code.

More precisely, the inconsistency is already there, but this patch makes
it "stronger".

Or even more precisely... this patch in fact introduces a bug, as far as
I can tell, for the RISC-V virt platform firmware.

Here's the details:

In ancient times, authenticated and non-authenticated variable stores
were handled by separate variable drivers. Over time they got unified --
in commit fa0737a839d0 ("MdeModulePkg Variable: Merge from Auth Variable
driver in SecurityPkg", 2015-07-01). The unified driver would then
manage both kinds of varstores.

This got reflected under OvmfPkg in commit d92eaabefbe0 ("OvmfPkg:
simplify VARIABLE_STORE_HEADER generation", 2016-02-15). I recommend
reviewing *that* commit in detail.

ArmVirtPkg's varstore generation was implemented in commit bf57a42a0e2c
("ArmVirtPkg: add FDF definition for empty varstore", 2016-06-23),
duplicating the (simplified, auth-only) varstore header signature from
OvmfPkg.

The upshot was that the OVMF and ArmVirt build processes would only
generate varstores with "gEfiAuthenticatedVariableGuid" in the varstore
header signature.

At the time of commit bf57a42a0e2c ("ArmVirtPkg: add FDF definition for
empty varstore", 2016-06-23), we didn't have VirtNorFlashDxe yet. The
various platform flash drivers back then tolerated (recognized) the
"gEfiVariableGuid" varstore header signature as well. So it's pretty
difficult to say now whether the exclusive generation of the
"gEfiAuthenticatedVariableGuid" signature in OVMF and ArmVirt *should
have been followed* of a restriction of the ValidateFvHeader() functions
in all those drivers -- i.e., whether we should have updated all those
drivers too, to reject "gEfiVariableGuid". After all, the unified
variable driver would just deal with "gEfiVariableGuid" fine, so the
"tolerance" of "gEfiVariableGuid"  in the flash drivers' validation code
was harmless.

But that's not the case anymore, with this patch:

For the variable traversal, we now expect each variable to come with an
AUTHENTICATED_VARIABLE_HEADER.

First, that is inconsistent with our tolerance of "gEfiVariableGuid" in
the varstore header signature. If one part of the code says "OK, this
can validly be 'gEfiVariableGuid'", then the other part of the code
should read the variable headers *as* VARIABLE_HEADER. Or else, in the
opposite direction: if we're only willing to parse the variable list via
AUTHENTICATED_VARIABLE_HEADER, then we should now reject the
"gEfiVariableGuid" header signature *upfront*.

Second (and worse): the bug. In "OvmfPkg/RiscVVirt/VarStore.fdf.inc", it
turns out that we *still* generate the gEfiVariableGuid varstore header
signature, in case SECURE_BOOT_ENABLE is FALSE. For some reason, commit
92b27c2e6ada ("OvmfPkg/RiscVVirt: Add build files for Qemu Virt
platform", 2023-02-16) did not consider commit d92eaabefbe0 ("OvmfPkg:
simplify VARIABLE_STORE_HEADER generation", 2016-02-15), and
*resurrected* the non-unified varstore generation for RiscVVirt.
Furthermore, RiscVVirt uses "VirtNorFlashDxe" as its platform flash
driver. As a result, if you now build RiscVVirt with this patch applied,
and with SECURE_BOOT_ENABLE=FALSE, I expect the ValidateFvHeader()
function to always fail, becase it will try to validate the contents of
the varstore through AUTHENTICATED_VARIABLE_HEADER entries, despite the
varstore containing (arguably valid) VARIABLE_HEADER entries.

So here's what I propose:

- keep this patch, but *prepend* two other patches:

- first, reflect commit d92eaabefbe0 to
"OvmfPkg/RiscVVirt/VarStore.fdf.inc" -- only generate the authenticated
signature GUID, regardless of SECURE_BOOT_ENABLE,

- second, in this function, stop accepting the "gEfiVariableGuid"
varstore header signature.


(2) Minor: consider CONST-ifying FwVolHeader, VariableStoreHeader,
VarHeader. (Feel free to ignore if it causes many problems.)


(3) Minor: consider declaring all of the new variables except VarOffset
(i.e., the variables VarHeaderEnd, VarNameEnd, VarEnd, VarHeader,
VarName, VarState, Status) at the top of the body of the "for" loop
below. That restricts their scopes as narrowly as possible, which helps
us understand the data flow.

> +  CHAR16                         *VarName;
> +  CONST CHAR8                    *VarState;
> +  UINTN                          VariableStoreLength;
> +  UINTN                          FvLength;
> +  RETURN_STATUS                  Status;
>
>    FwVolHeader = (EFI_FIRMWARE_VOLUME_HEADER *)Instance->RegionBaseAddress;
>
> @@ -260,6 +269,112 @@ ValidateFvHeader (
>      return EFI_NOT_FOUND;
>    }
>
> +  //
> +  // check variables
> +  //
> +  DEBUG ((DEBUG_INFO, "%a: checking variables\n", __func__));
> +  VarOffset = sizeof (*VariableStoreHeader);
> +  for ( ; ;) {
> +    Status = SafeUintnAdd (VarOffset, sizeof (*VarHeader), &VarHeaderEnd);
> +    if (RETURN_ERROR (Status)) {
> +      DEBUG ((DEBUG_ERROR, "%a: integer overflow\n", __func__));
> +      return EFI_NOT_FOUND;
> +    }
> +
> +    if (VarHeaderEnd >= VariableStoreHeader->Size) {
> +      DEBUG ((DEBUG_INFO, "%a: end of var list (no space left)\n", __func__));
> +      break;
> +    }

(4) In case of inequality, the variable header is truncated. Accepting
it as "success" looks doubtful.


(5) In case of equality, the variable header fits, but it is followed by
no payload at all. I think there are sub-cases to distinguish there:

- if the StartId differs from 0x55aa, then we may consider the variable
list to be terminated, and break out of the loop (returning success from
the function)

- if the StartId is 0x55aa, then we need to look further, beause we
can't decide yet. For example, if State is VAR_HEADER_VALID_ONLY (0x7f),
then it might be fine for the variable header (at the very end of the
varstore) *not* to be followed by payload bytes (name, data).

I find this code hard to review because I don't know (and the Intel
whitepaper doesn't seem to tell) precisely how a valid variable list is
supposed to be terminated.

> +
> +    VarHeader = (VOID *)((UINTN)VariableStoreHeader + VarOffset);

This addition is unchecked, but it's safe. We're operating under the
assumption that the complete variable store (starting at
VariableStoreHeader, running for VariableStoreHeader->Size bytes) has
been mapped into guest-physical address space. Furthermore, at the very
end of the address space, the "code" flash device is mapped (with the
reset vector at the tail). Therefore, as long as our relative offsets
are smaller than, or equal to, VariableStoreHeader->Size, we can safely
evaluate the *absolute addresses* (possibly pointing just one past the
varstore, but still not overflowing, in case of equality), and if the
relative offset is strictly smaller than VariableStoreHeader->Size, then
we can even dereference those absolute addresses.

> +    if (VarHeader->StartId != 0x55aa) {
> +      DEBUG ((DEBUG_INFO, "%a: end of var list (no startid)\n", __func__));
> +      break;
> +    }
> +
> +    VarName = NULL;
> +    switch (VarHeader->State) {
> +      // usage: State = VAR_HEADER_VALID_ONLY
> +      case VAR_HEADER_VALID_ONLY:
> +        VarState = "header-ok";
> +        VarName  = L"<unknown>";
> +        break;
> +
> +      // usage: State = VAR_ADDED
> +      case VAR_ADDED:
> +        VarState = "ok";
> +        break;
> +
> +      // usage: State &= VAR_IN_DELETED_TRANSITION
> +      case VAR_ADDED &VAR_IN_DELETED_TRANSITION:
> +        VarState = "del-in-transition";
> +        break;
> +
> +      // usage: State &= VAR_DELETED
> +      case VAR_ADDED &VAR_DELETED:
> +      case VAR_ADDED &VAR_DELETED &VAR_IN_DELETED_TRANSITION:
> +        VarState = "deleted";
> +        break;
> +
> +      default:
> +        DEBUG ((
> +          DEBUG_ERROR,
> +          "%a: invalid variable state: 0x%x\n",
> +          __func__,
> +          VarHeader->State
> +          ));
> +        return EFI_NOT_FOUND;
> +    }
> +
> +    Status = SafeUintnAdd (VarHeaderEnd, VarHeader->NameSize, &VarNameEnd);
> +    if (RETURN_ERROR (Status)) {
> +      DEBUG ((DEBUG_ERROR, "%a: integer overflow\n", __func__));
> +      return EFI_NOT_FOUND;
> +    }
> +
> +    Status = SafeUintnAdd (VarNameEnd, VarHeader->DataSize, &VarEnd);
> +    if (RETURN_ERROR (Status)) {
> +      DEBUG ((DEBUG_ERROR, "%a: integer overflow\n", __func__));
> +      return EFI_NOT_FOUND;
> +    }
> +
> +    if (VarEnd > VariableStoreHeader->Size) {
> +      DEBUG ((
> +        DEBUG_ERROR,
> +        "%a: invalid variable size: 0x%Lx + 0x%Lx + 0x%x + 0x%x > 0x%x\n",
> +        __func__,
> +        (UINT64)VarOffset,
> +        (UINT64)(sizeof (*VarHeader)),
> +        VarHeader->NameSize,
> +        VarHeader->DataSize,
> +        VariableStoreHeader->Size
> +        ));
> +      return EFI_NOT_FOUND;
> +    }
> +
> +    if (VarHeader->NameSize & 1) {
> +      DEBUG ((DEBUG_ERROR, "%a: invalid name size\n", __func__));
> +      return EFI_NOT_FOUND;
> +    }
> +
> +    if (VarName == NULL) {
> +      VarName = (VOID *)((UINTN)VariableStoreHeader + VarHeaderEnd);

From the perspective of "VarName" being well-aligned, I think this is
fine. Our assumption thus far is that VarHeader is well-aligned, and
given the sizeof AUTHENTICATED_VARIABLE_HEADER, VarName should be
well-aligned as well.

(6) I suggest two further checks (within the braces here):

- enforce

  VarHeader->NameSize > 0

- enforce

  VarName[VarHeader->NameSize / 2 - 1] == L'\0'

because the "AUTHENTICATED_VARIABLE_HEADER.NameSize" documentation in
"MdeModulePkg/Include/Guid/VariableFormat.h" specifies NUL-termination.

(This is also important for the immediately subsequent code: we print
the name!)

> +    }
> +
> +    DEBUG ((
> +      DEBUG_VERBOSE,
> +      "%a: +0x%04Lx: name=0x%x data=0x%x '%s' (%a)\n",
> +      __func__,
> +      (UINT64)VarOffset,
> +      VarHeader->NameSize,
> +      VarHeader->DataSize,
> +      VarName,
> +      VarState
> +      ));

(7) Not really important, I'm just throwing it out: how about logging
"VarHeader->VendorGuid" too?

It would require something like this:

  CONST EFI_GUID  *VarGuid;

  ...

  VarGuid = &gZeroGuid;
  if (VarName == NULL) {
    ...
    VarGuid = &VarHeader->VendorGuid;
    ...
  }

and then format VarGuid with "%g" in the DEBUG macro invocation.

This would be useful for the non-standard namespace GUIDs especially.

> +
> +    VarOffset = ALIGN_VALUE (VarEnd, 4);

(8) Apologies if it was me who suggested ALIGN_VALUE() previously, but
this is, in effect, an unchecked addition. I can't off-hand see evidence
that it can never overflow (the previous checks don't seem to prevent an
overflow here), so I suggest:

  //
  // the next variable header starts aligned at 4 bytes
  //
  Status = SafeUintnAdd (VarEnd, (4 - (VarEnd & 4)) & 4, &VarOffset);
  if RETURN_ERROR (Status) {
    DEBUG ((DEBUG_ERROR, "%a: integer overflow\n", __func__));
    return EFI_NOT_FOUND;
  }

> +  }
> +
>    return EFI_SUCCESS;
>  }
>

Thanks
Laszlo



-=-=-=-=-=-=-=-=-=-=-=-
Groups.io Links: You receive all messages sent to this group.
View/Reply Online (#113084): https://edk2.groups.io/g/devel/message/113084
Mute This Topic: https://groups.io/mt/103171811/7686176
Group Owner: devel+owner@edk2.groups.io
Unsubscribe: https://edk2.groups.io/g/devel/leave/12367111/7686176/1913456212/xyzzy [rebecca@openfw.io]
-=-=-=-=-=-=-=-=-=-=-=-



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

* Re: [edk2-devel] [PATCH v3 1/1] OvmfPkg/VirtNorFlashDxe: sanity-check variables
  2024-01-03 12:56 ` Laszlo Ersek
@ 2024-01-03 13:09   ` Laszlo Ersek
  2024-01-03 13:13     ` Laszlo Ersek
  2024-01-03 15:11   ` Gerd Hoffmann
  1 sibling, 1 reply; 8+ messages in thread
From: Laszlo Ersek @ 2024-01-03 13:09 UTC (permalink / raw)
  To: devel, kraxel; +Cc: Ard Biesheuvel, oliver, mike.maslenkin, Jiewen Yao

On 1/3/24 13:56, Laszlo Ersek wrote:

> (8) Apologies if it was me who suggested ALIGN_VALUE() previously, but
> this is, in effect, an unchecked addition. I can't off-hand see evidence
> that it can never overflow (the previous checks don't seem to prevent an
> overflow here), so I suggest:
> 
>   //
>   // the next variable header starts aligned at 4 bytes
>   //
>   Status = SafeUintnAdd (VarEnd, (4 - (VarEnd & 4)) & 4, &VarOffset);
>   if RETURN_ERROR (Status) {
>     DEBUG ((DEBUG_ERROR, "%a: integer overflow\n", __func__));
>     return EFI_NOT_FOUND;
>   }

Heh, what I wrote is bogus. Man, binary is hard. :) So, let me try again:

  Status = SafeUintnAdd (VarEnd, (4 - (VarEnd & 3)) & 3, &VarOffset);

Ideally, we'd have a SafeIntLib set of APIs for aligning up...

Sorry :)
Laszlo



-=-=-=-=-=-=-=-=-=-=-=-
Groups.io Links: You receive all messages sent to this group.
View/Reply Online (#113087): https://edk2.groups.io/g/devel/message/113087
Mute This Topic: https://groups.io/mt/103171811/7686176
Group Owner: devel+owner@edk2.groups.io
Unsubscribe: https://edk2.groups.io/g/devel/leave/12367111/7686176/1913456212/xyzzy [rebecca@openfw.io]
-=-=-=-=-=-=-=-=-=-=-=-



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

* Re: [edk2-devel] [PATCH v3 1/1] OvmfPkg/VirtNorFlashDxe: sanity-check variables
  2024-01-03 13:09   ` Laszlo Ersek
@ 2024-01-03 13:13     ` Laszlo Ersek
  0 siblings, 0 replies; 8+ messages in thread
From: Laszlo Ersek @ 2024-01-03 13:13 UTC (permalink / raw)
  To: devel, kraxel; +Cc: Ard Biesheuvel, oliver, mike.maslenkin, Jiewen Yao

On 1/3/24 14:09, Laszlo Ersek wrote:
> On 1/3/24 13:56, Laszlo Ersek wrote:
> 
>> (8) Apologies if it was me who suggested ALIGN_VALUE() previously, but
>> this is, in effect, an unchecked addition. I can't off-hand see evidence
>> that it can never overflow (the previous checks don't seem to prevent an
>> overflow here), so I suggest:
>>
>>   //
>>   // the next variable header starts aligned at 4 bytes
>>   //
>>   Status = SafeUintnAdd (VarEnd, (4 - (VarEnd & 4)) & 4, &VarOffset);
>>   if RETURN_ERROR (Status) {
>>     DEBUG ((DEBUG_ERROR, "%a: integer overflow\n", __func__));
>>     return EFI_NOT_FOUND;
>>   }
> 
> Heh, what I wrote is bogus. Man, binary is hard. :) So, let me try again:
> 
>   Status = SafeUintnAdd (VarEnd, (4 - (VarEnd & 3)) & 3, &VarOffset);
> 
> Ideally, we'd have a SafeIntLib set of APIs for aligning up...

BTW the reason I messed it up was that I'm much more attracted to the
"%" operator, so initially I wrote it as

  Status = SafeUintnAdd (VarEnd, (4 - (VarEnd % 4)) % 4, &VarOffset);

which was correct, but then I thought, from your code, that you probably
liked "&" more (and that "&" might be faster for the CPU...), and then I
"replaced" the "%" operator with "&", but forgot to replace the divisor
4 with the bitmask 3...

Laszlo



-=-=-=-=-=-=-=-=-=-=-=-
Groups.io Links: You receive all messages sent to this group.
View/Reply Online (#113088): https://edk2.groups.io/g/devel/message/113088
Mute This Topic: https://groups.io/mt/103171811/7686176
Group Owner: devel+owner@edk2.groups.io
Unsubscribe: https://edk2.groups.io/g/devel/leave/12367111/7686176/1913456212/xyzzy [rebecca@openfw.io]
-=-=-=-=-=-=-=-=-=-=-=-



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

* Re: [edk2-devel] [PATCH v3 1/1] OvmfPkg/VirtNorFlashDxe: sanity-check variables
  2024-01-03 12:56 ` Laszlo Ersek
  2024-01-03 13:09   ` Laszlo Ersek
@ 2024-01-03 15:11   ` Gerd Hoffmann
  2024-01-04 13:21     ` Laszlo Ersek
  1 sibling, 1 reply; 8+ messages in thread
From: Gerd Hoffmann @ 2024-01-03 15:11 UTC (permalink / raw)
  To: Laszlo Ersek; +Cc: devel, Ard Biesheuvel, oliver, mike.maslenkin, Jiewen Yao

  Hi,

> Second (and worse): the bug. In "OvmfPkg/RiscVVirt/VarStore.fdf.inc", it
> turns out that we *still* generate the gEfiVariableGuid varstore header
> signature, in case SECURE_BOOT_ENABLE is FALSE. For some reason, commit
> 92b27c2e6ada ("OvmfPkg/RiscVVirt: Add build files for Qemu Virt
> platform", 2023-02-16) did not consider commit d92eaabefbe0 ("OvmfPkg:
> simplify VARIABLE_STORE_HEADER generation", 2016-02-15), and
> *resurrected* the non-unified varstore generation for RiscVVirt.
> Furthermore, RiscVVirt uses "VirtNorFlashDxe" as its platform flash
> driver. As a result, if you now build RiscVVirt with this patch applied,
> and with SECURE_BOOT_ENABLE=FALSE, I expect the ValidateFvHeader()
> function to always fail, becase it will try to validate the contents of
> the varstore through AUTHENTICATED_VARIABLE_HEADER entries, despite the
> varstore containing (arguably valid) VARIABLE_HEADER entries.

I expect it will fail only once.  In case the checks don't pass
VirtNorFlashDxe will re-initialize the flash varstore with
gEfiAuthenticatedVariableGuid, so on next boot everything is fine.

> So here's what I propose:
> 
> - keep this patch, but *prepend* two other patches:
> 
> - first, reflect commit d92eaabefbe0 to
> "OvmfPkg/RiscVVirt/VarStore.fdf.inc" -- only generate the authenticated
> signature GUID, regardless of SECURE_BOOT_ENABLE,
> 
> - second, in this function, stop accepting the "gEfiVariableGuid"
> varstore header signature.

Makes sense.

> > +    if (VarHeaderEnd >= VariableStoreHeader->Size) {
> > +      DEBUG ((DEBUG_INFO, "%a: end of var list (no space left)\n", __func__));
> > +      break;
> > +    }
> 
> (4) In case of inequality, the variable header is truncated. Accepting
> it as "success" looks doubtful.

We don't know whenever it is supposed to be a valid header, we didn't
check the StartId yet.

Reversing the check ordering looks wrong too (looking at header fields
before we know the header is inside the store).

> (5) In case of equality, the variable header fits, but it is followed by
> no payload at all. I think there are sub-cases to distinguish there:
> 
> - if the StartId differs from 0x55aa, then we may consider the variable
> list to be terminated, and break out of the loop (returning success from
> the function)
> 
> - if the StartId is 0x55aa, then we need to look further, beause we
> can't decide yet. For example, if State is VAR_HEADER_VALID_ONLY (0x7f),
> then it might be fine for the variable header (at the very end of the
> varstore) *not* to be followed by payload bytes (name, data).

Not sure this makes sense.  VAR_HEADER_VALID_ONLY is a temporary state,
while the variable driver writes name and data just after the header,
to be updated to VAR_ADDED when the write completed successfully.  So
I'd expect to never find a header without space for name + data.

> I find this code hard to review because I don't know (and the Intel
> whitepaper doesn't seem to tell) precisely how a valid variable list is
> supposed to be terminated.

Which is why the code logs the condition why it considers the list to be
terminated ...

> (6) I suggest two further checks (within the braces here):
> 
> - enforce
> 
>   VarHeader->NameSize > 0

NameSize >= 4 ?  (room for one char and the terminating null)

> - enforce
> 
>   VarName[VarHeader->NameSize / 2 - 1] == L'\0'

ok

> (This is also important for the immediately subsequent code: we print
> the name!)

Indeed.

> (7) Not really important, I'm just throwing it out: how about logging
> "VarHeader->VendorGuid" too?
> 
> It would require something like this:
> 
>   CONST EFI_GUID  *VarGuid;
> 
>   ...
> 
>   VarGuid = &gZeroGuid;
>   if (VarName == NULL) {
>     ...
>     VarGuid = &VarHeader->VendorGuid;
>     ...
>   }

I think we can just use VarHeader->VendorGuid directly, given that the
guid is part of the fixed header it should be valid even in case the
state is VAR_HEADER_VALID_ONLY.

take care,
  Gerd



-=-=-=-=-=-=-=-=-=-=-=-
Groups.io Links: You receive all messages sent to this group.
View/Reply Online (#113100): https://edk2.groups.io/g/devel/message/113100
Mute This Topic: https://groups.io/mt/103171811/7686176
Group Owner: devel+owner@edk2.groups.io
Unsubscribe: https://edk2.groups.io/g/devel/unsub [rebecca@openfw.io]
-=-=-=-=-=-=-=-=-=-=-=-



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

* Re: [edk2-devel] [PATCH v3 1/1] OvmfPkg/VirtNorFlashDxe: sanity-check variables
  2024-01-03 15:11   ` Gerd Hoffmann
@ 2024-01-04 13:21     ` Laszlo Ersek
  2024-01-04 15:06       ` Gerd Hoffmann
  0 siblings, 1 reply; 8+ messages in thread
From: Laszlo Ersek @ 2024-01-04 13:21 UTC (permalink / raw)
  To: Gerd Hoffmann; +Cc: devel, Ard Biesheuvel, oliver, mike.maslenkin, Jiewen Yao

On 1/3/24 16:11, Gerd Hoffmann wrote:
>   Hi,
> 
>> Second (and worse): the bug. In "OvmfPkg/RiscVVirt/VarStore.fdf.inc", it
>> turns out that we *still* generate the gEfiVariableGuid varstore header
>> signature, in case SECURE_BOOT_ENABLE is FALSE. For some reason, commit
>> 92b27c2e6ada ("OvmfPkg/RiscVVirt: Add build files for Qemu Virt
>> platform", 2023-02-16) did not consider commit d92eaabefbe0 ("OvmfPkg:
>> simplify VARIABLE_STORE_HEADER generation", 2016-02-15), and
>> *resurrected* the non-unified varstore generation for RiscVVirt.
>> Furthermore, RiscVVirt uses "VirtNorFlashDxe" as its platform flash
>> driver. As a result, if you now build RiscVVirt with this patch applied,
>> and with SECURE_BOOT_ENABLE=FALSE, I expect the ValidateFvHeader()
>> function to always fail, becase it will try to validate the contents of
>> the varstore through AUTHENTICATED_VARIABLE_HEADER entries, despite the
>> varstore containing (arguably valid) VARIABLE_HEADER entries.
> 
> I expect it will fail only once.  In case the checks don't pass
> VirtNorFlashDxe will re-initialize the flash varstore with
> gEfiAuthenticatedVariableGuid, so on next boot everything is fine.

Good point about reinit, but it might still needlessly cause the loss of
preexistent variables, so if we can avoid it easily, we should.

> 
>> So here's what I propose:
>>
>> - keep this patch, but *prepend* two other patches:
>>
>> - first, reflect commit d92eaabefbe0 to
>> "OvmfPkg/RiscVVirt/VarStore.fdf.inc" -- only generate the authenticated
>> signature GUID, regardless of SECURE_BOOT_ENABLE,
>>
>> - second, in this function, stop accepting the "gEfiVariableGuid"
>> varstore header signature.
> 
> Makes sense.
> 
>>> +    if (VarHeaderEnd >= VariableStoreHeader->Size) {
>>> +      DEBUG ((DEBUG_INFO, "%a: end of var list (no space left)\n", __func__));
>>> +      break;
>>> +    }
>>
>> (4) In case of inequality, the variable header is truncated. Accepting
>> it as "success" looks doubtful.
> 
> We don't know whenever it is supposed to be a valid header, we didn't
> check the StartId yet.
> 
> Reversing the check ordering looks wrong too (looking at header fields
> before we know the header is inside the store).

Oh I certainly don't imply that we should reverse the order of the
checks; I meant to say (a) we should separate

  VarHeaderEnd > VariableStoreHeader->Size

from

  VarHeaderEnd == VariableStoreHeader->Size

and (b) we should perhaps consider the former condition (i.e.,
inequality) as a hard failure (and not as success), i.e., cause for
reformatting the varstore.

> 
>> (5) In case of equality, the variable header fits, but it is followed by
>> no payload at all. I think there are sub-cases to distinguish there:
>>
>> - if the StartId differs from 0x55aa, then we may consider the variable
>> list to be terminated, and break out of the loop (returning success from
>> the function)
>>
>> - if the StartId is 0x55aa, then we need to look further, beause we
>> can't decide yet. For example, if State is VAR_HEADER_VALID_ONLY (0x7f),
>> then it might be fine for the variable header (at the very end of the
>> varstore) *not* to be followed by payload bytes (name, data).
> 
> Not sure this makes sense.  VAR_HEADER_VALID_ONLY is a temporary state,
> while the variable driver writes name and data just after the header,
> to be updated to VAR_ADDED when the write completed successfully.  So
> I'd expect to never find a header without space for name + data.

I have two comments here:

- if you are right, then I agree it's a good argument for keeping the
two conditions

  VarHeaderEnd > VariableStoreHeader->Size
  VarHeaderEnd == VariableStoreHeader->Size

unified as

  VarHeaderEnd >= VariableStoreHeader->Size

*but* then it only strengthens my argument that the *handling* for this
case should not be a "break" statement, but "return EFI_NOT_FOUND"! (And
then the only successful exit from the loop would be for (StartId !=
0x55aa).)

- Do we know for sure that VAR_HEADER_VALID_ONLY is never expected to be
seen? What if the variable update design defines VAR_HEADER_VALID_ONLY
specifically so that the variable driver can recover from a power loss
"in the middle"? In that case, we should not consider
VAR_HEADER_VALID_ONLY reason for reformatting the whole varstore -- in
fact, the swith statement at the end of the patch tolaretes
VAR_HEADER_VALID_ONLY. So I figure, if we accept VAR_HEADER_VALID_ONLY
in that logic, then we should also accept VAR_HEADER_VALID_ONLY if it's
at the very end of the varstore.

> 
>> I find this code hard to review because I don't know (and the Intel
>> whitepaper doesn't seem to tell) precisely how a valid variable list is
>> supposed to be terminated.
> 
> Which is why the code logs the condition why it considers the list to be
> terminated ...

OK!

> 
>> (6) I suggest two further checks (within the braces here):
>>
>> - enforce
>>
>>   VarHeader->NameSize > 0
> 
> NameSize >= 4 ?  (room for one char and the terminating null)

Sure, that works too.

> 
>> - enforce
>>
>>   VarName[VarHeader->NameSize / 2 - 1] == L'\0'
> 
> ok
> 
>> (This is also important for the immediately subsequent code: we print
>> the name!)
> 
> Indeed.
> 
>> (7) Not really important, I'm just throwing it out: how about logging
>> "VarHeader->VendorGuid" too?
>>
>> It would require something like this:
>>
>>   CONST EFI_GUID  *VarGuid;
>>
>>   ...
>>
>>   VarGuid = &gZeroGuid;
>>   if (VarName == NULL) {
>>     ...
>>     VarGuid = &VarHeader->VendorGuid;
>>     ...
>>   }
> 
> I think we can just use VarHeader->VendorGuid directly, given that the
> guid is part of the fixed header it should be valid even in case the
> state is VAR_HEADER_VALID_ONLY.

Good point -- I think I briefly considered it, but ruled it out because
<guid>:"<unknown>" in the log didn't look useful to me at once. But now
you're making me reconsider -- it simplifies the code, and it doesn't
"hurt" in the log either.

Thanks!
Laszlo



-=-=-=-=-=-=-=-=-=-=-=-
Groups.io Links: You receive all messages sent to this group.
View/Reply Online (#113168): https://edk2.groups.io/g/devel/message/113168
Mute This Topic: https://groups.io/mt/103171811/7686176
Group Owner: devel+owner@edk2.groups.io
Unsubscribe: https://edk2.groups.io/g/devel/leave/12367111/7686176/1913456212/xyzzy [rebecca@openfw.io]
-=-=-=-=-=-=-=-=-=-=-=-



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

* Re: [edk2-devel] [PATCH v3 1/1] OvmfPkg/VirtNorFlashDxe: sanity-check variables
  2024-01-04 13:21     ` Laszlo Ersek
@ 2024-01-04 15:06       ` Gerd Hoffmann
  2024-01-05 13:50         ` Laszlo Ersek
  0 siblings, 1 reply; 8+ messages in thread
From: Gerd Hoffmann @ 2024-01-04 15:06 UTC (permalink / raw)
  To: Laszlo Ersek; +Cc: devel, Ard Biesheuvel, oliver, mike.maslenkin, Jiewen Yao

  Hi,

> >> - if the StartId is 0x55aa, then we need to look further, beause we
> >> can't decide yet. For example, if State is VAR_HEADER_VALID_ONLY (0x7f),
> >> then it might be fine for the variable header (at the very end of the
> >> varstore) *not* to be followed by payload bytes (name, data).
> > 
> > Not sure this makes sense.  VAR_HEADER_VALID_ONLY is a temporary state,
> > while the variable driver writes name and data just after the header,
> > to be updated to VAR_ADDED when the write completed successfully.  So
> > I'd expect to never find a header without space for name + data.
> 
> - Do we know for sure that VAR_HEADER_VALID_ONLY is never expected to be
> seen?

Writing goes like this:

  (1) find free space
  (2) write header, with VAR_HEADER_VALID_ONLY.
  (3) write name + data
  (4) update header, set state = VAR_ADDED.

> What if the variable update design defines VAR_HEADER_VALID_ONLY
> specifically so that the variable driver can recover from a power loss
> "in the middle"?

Power loss in step (3) can surely lead to variables in
VAR_HEADER_VALID_ONLY state, and I'd expect the variable driver can
actually recover from that.

[ side note:  The (2) write should be small enough that it fits into the
              flash block write buffer (128 bytes).  Which could be
              important to maintain variable store consistency. ]

Nevertheless we should never find a header at the end of the variable
store, without space allocated for name + date.  Minimal space for the
name is 4 bytes (one char16 + '\0'), for the data 1 byte, alignment
rounds the latter to 4 bytes too, so this should be true:

VarOffset + sizeof(*VarHeader) + 8 <= VariableStoreHeader->Size

> So I figure, if we accept VAR_HEADER_VALID_ONLY in that logic, then we
> should also accept VAR_HEADER_VALID_ONLY if it's at the very end of
> the varstore.

Disagree, see above.  Storing the header at a place which leaves no room
for name + data doesn't make sense to me.

We could go the extra mile and look at the next StartId location, verify
StartId != 0x55aa, in the no-space-left-for-header case.

take care,
  Gerd



-=-=-=-=-=-=-=-=-=-=-=-
Groups.io Links: You receive all messages sent to this group.
View/Reply Online (#113182): https://edk2.groups.io/g/devel/message/113182
Mute This Topic: https://groups.io/mt/103171811/7686176
Group Owner: devel+owner@edk2.groups.io
Unsubscribe: https://edk2.groups.io/g/devel/unsub [rebecca@openfw.io]
-=-=-=-=-=-=-=-=-=-=-=-



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

* Re: [edk2-devel] [PATCH v3 1/1] OvmfPkg/VirtNorFlashDxe: sanity-check variables
  2024-01-04 15:06       ` Gerd Hoffmann
@ 2024-01-05 13:50         ` Laszlo Ersek
  0 siblings, 0 replies; 8+ messages in thread
From: Laszlo Ersek @ 2024-01-05 13:50 UTC (permalink / raw)
  To: Gerd Hoffmann; +Cc: devel, Ard Biesheuvel, oliver, mike.maslenkin, Jiewen Yao

On 1/4/24 16:06, Gerd Hoffmann wrote:
>   Hi,
> 
>>>> - if the StartId is 0x55aa, then we need to look further, beause we
>>>> can't decide yet. For example, if State is VAR_HEADER_VALID_ONLY (0x7f),
>>>> then it might be fine for the variable header (at the very end of the
>>>> varstore) *not* to be followed by payload bytes (name, data).
>>>
>>> Not sure this makes sense.  VAR_HEADER_VALID_ONLY is a temporary state,
>>> while the variable driver writes name and data just after the header,
>>> to be updated to VAR_ADDED when the write completed successfully.  So
>>> I'd expect to never find a header without space for name + data.
>>
>> - Do we know for sure that VAR_HEADER_VALID_ONLY is never expected to be
>> seen?
> 
> Writing goes like this:
> 
>   (1) find free space
>   (2) write header, with VAR_HEADER_VALID_ONLY.
>   (3) write name + data
>   (4) update header, set state = VAR_ADDED.
> 
>> What if the variable update design defines VAR_HEADER_VALID_ONLY
>> specifically so that the variable driver can recover from a power loss
>> "in the middle"?
> 
> Power loss in step (3) can surely lead to variables in
> VAR_HEADER_VALID_ONLY state, and I'd expect the variable driver can
> actually recover from that.
> 
> [ side note:  The (2) write should be small enough that it fits into the
>               flash block write buffer (128 bytes).  Which could be
>               important to maintain variable store consistency. ]
> 
> Nevertheless we should never find a header at the end of the variable
> store, without space allocated for name + date.  Minimal space for the
> name is 4 bytes (one char16 + '\0'), for the data 1 byte, alignment
> rounds the latter to 4 bytes too, so this should be true:
> 
> VarOffset + sizeof(*VarHeader) + 8 <= VariableStoreHeader->Size
> 
>> So I figure, if we accept VAR_HEADER_VALID_ONLY in that logic, then we
>> should also accept VAR_HEADER_VALID_ONLY if it's at the very end of
>> the varstore.
> 
> Disagree, see above.  Storing the header at a place which leaves no room
> for name + data doesn't make sense to me.

OK, that sounds convincing, thanks!
Laszlo

> We could go the extra mile and look at the next StartId location, verify
> StartId != 0x55aa, in the no-space-left-for-header case.
> 
> take care,
>   Gerd
> 



-=-=-=-=-=-=-=-=-=-=-=-
Groups.io Links: You receive all messages sent to this group.
View/Reply Online (#113283): https://edk2.groups.io/g/devel/message/113283
Mute This Topic: https://groups.io/mt/103171811/7686176
Group Owner: devel+owner@edk2.groups.io
Unsubscribe: https://edk2.groups.io/g/devel/leave/12367111/7686176/1913456212/xyzzy [rebecca@openfw.io]
-=-=-=-=-=-=-=-=-=-=-=-



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

end of thread, other threads:[~2024-01-05 13:50 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2023-12-14 15:31 [edk2-devel] [PATCH v3 1/1] OvmfPkg/VirtNorFlashDxe: sanity-check variables Gerd Hoffmann
2024-01-03 12:56 ` Laszlo Ersek
2024-01-03 13:09   ` Laszlo Ersek
2024-01-03 13:13     ` Laszlo Ersek
2024-01-03 15:11   ` Gerd Hoffmann
2024-01-04 13:21     ` Laszlo Ersek
2024-01-04 15:06       ` Gerd Hoffmann
2024-01-05 13:50         ` Laszlo Ersek

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox