diff mbox

[edk2] OvmfPkg/PlatformBootManagerLib: remove stale FvFile boot options

Message ID 1468266651-16178-1-git-send-email-lersek@redhat.com
State Superseded
Headers show

Commit Message

Laszlo Ersek July 11, 2016, 7:50 p.m. UTC
Changing the base address or the size of DXEFV, changing PcdShellFile, or
omitting the UEFI Shell from the OVMF binary, all lead to "EFI Internal
Shell" boot options that are technically different from each other and do
not describe any built-in shell binary exactly. Such options can
accumulate in a varstore over time, and while they remain generally
bootable (thanks to the efforts of BmGetFileBufferByFvFilePath()), they
look bad.

Filter out any stale options.

This functionality is not added to QemuBootOrderLib, because it is
independent from QEMU and fw_cfg.

Cc: Ard Biesheuvel <ard.biesheuvel@linaro.org>
Cc: Gary Lin <glin@suse.com>
Cc: Jordan Justen <jordan.l.justen@intel.com>
Cc: Ruiyu Ni <ruiyu.ni@intel.com>
Contributed-under: TianoCore Contribution Agreement 1.0
Signed-off-by: Laszlo Ersek <lersek@redhat.com>

---

Notes:
    Once this patch converges, I'll port it to ArmVirtPkg.

 OvmfPkg/Library/PlatformBootManagerLib/PlatformBootManagerLib.inf |   1 +
 OvmfPkg/Library/PlatformBootManagerLib/BdsPlatform.c              | 115 ++++++++++++++++++++
 2 files changed, 116 insertions(+)

-- 
1.8.3.1

_______________________________________________
edk2-devel mailing list
edk2-devel@lists.01.org
https://lists.01.org/mailman/listinfo/edk2-devel

Comments

Laszlo Ersek July 12, 2016, 8:57 a.m. UTC | #1
On 07/12/16 04:36, Ni, Ruiyu wrote:
> Laszlo,

> OVMF uses old style of FV device path node: MemoryMapped(...).

> If you check Nt32Pkg/Nt32Pkg.fdf, there is FvNameGuid field under[FV] section.

> -----

> [FV.FvRecovery]

> ......

> FvNameGuid         = 6D99E806-3D38-42c2-A095-5F4300BFD7DC

> ----

> 

> If you add FvNameGuid field under [FV] section for OVMF, the firmware will

> produce Fv(...) device path node for FV.

> Which is also the recommended way to identify a FV because it won't cause

> the FV boot option file path changing across boot.

> 

> You patch looks good but it assumes a FV boot option should always starts

> with MemoryMapped(...) node, which is not always TRUE.

> 

> I recommend you change FDF to add FvNameGuid field to [FV] section

> and then just use the following algorithm to remove stale options:

> 

> For each option:

>   Node = option.FilePath

>    Status = gBS->LocateDevicePath (&gEfiFirmwareVolume2ProtocolGuid,

>                     &Node, &FvHandle);

>   If (EFI_ERROR (Status)) {

>     // Current one is not a FV boot option

>     Continue;

>   }

>   //... Use Fv->ReadFile as what you did in below patch to check.

>   

> With the above code, you don't need to check whether the first node is

> a MemoryMapped(...) node and second node is a FvFile(...) node.


This sounds great, but my concern is that then the code would not cover
UEFI shell boot options that have accumulated in user-side variable
stores thus far. The idea I had for this patch was to eliminate all
future proliferation, but also kill off any stale options that might
already exist.

This bug was reported to me by our QE department, and it would be nice
if I could just give them a package update without asking them to delete
boot options / varstores manually. I guess I could tell them to do that
"one last time" before it becomes automatic... Hm. :)

The most robust solution would be to implement FvNameGuid (and the
dependent code) *and* the currently proposed code as well.

I think I should be able to do that; it boils down to setting the
SearchNode variable flexibly enough.

Also, thinking about your suggestion above, for maximum robustness I'd
then like to prepare the code for the case when we change FvNameGuid
itself. I don't see any reason why we would do that, but it would be
best to write this code once and then forget about it.

I'll have to experiment with the code to get more concrete thoughts. :)

Thanks!
Laszlo

>> -----Original Message-----

>> From: Laszlo Ersek [mailto:lersek@redhat.com]

>> Sent: Tuesday, July 12, 2016 3:51 AM

>> To: edk2-devel-01 <edk2-devel@ml01.01.org>

>> Cc: Ard Biesheuvel <ard.biesheuvel@linaro.org>; Gary Lin <glin@suse.com>;

>> Justen, Jordan L <jordan.l.justen@intel.com>; Ni, Ruiyu <ruiyu.ni@intel.com>

>> Subject: [PATCH] OvmfPkg/PlatformBootManagerLib: remove stale FvFile

>> boot options

>>

>> Changing the base address or the size of DXEFV, changing PcdShellFile, or

>> omitting the UEFI Shell from the OVMF binary, all lead to "EFI Internal Shell"

>> boot options that are technically different from each other and do not

>> describe any built-in shell binary exactly. Such options can accumulate in a

>> varstore over time, and while they remain generally bootable (thanks to the

>> efforts of BmGetFileBufferByFvFilePath()), they look bad.

>>

>> Filter out any stale options.

>>

>> This functionality is not added to QemuBootOrderLib, because it is

>> independent from QEMU and fw_cfg.

>>

>> Cc: Ard Biesheuvel <ard.biesheuvel@linaro.org>

>> Cc: Gary Lin <glin@suse.com>

>> Cc: Jordan Justen <jordan.l.justen@intel.com>

>> Cc: Ruiyu Ni <ruiyu.ni@intel.com>

>> Contributed-under: TianoCore Contribution Agreement 1.0

>> Signed-off-by: Laszlo Ersek <lersek@redhat.com>

>> ---

>>

>> Notes:

>>     Once this patch converges, I'll port it to ArmVirtPkg.

>>

>>  OvmfPkg/Library/PlatformBootManagerLib/PlatformBootManagerLib.inf |

>> 1 +

>>  OvmfPkg/Library/PlatformBootManagerLib/BdsPlatform.c              | 115

>> ++++++++++++++++++++

>>  2 files changed, 116 insertions(+)

>>

>> diff --git

>> a/OvmfPkg/Library/PlatformBootManagerLib/PlatformBootManagerLib.inf

>> b/OvmfPkg/Library/PlatformBootManagerLib/PlatformBootManagerLib.inf

>> index ffa1288e4d1a..f3303b9f2cf3 100644

>> ---

>> a/OvmfPkg/Library/PlatformBootManagerLib/PlatformBootManagerLib.inf

>> +++

>> b/OvmfPkg/Library/PlatformBootManagerLib/PlatformBootManagerLib.inf

>> @@ -72,6 +72,7 @@ [Protocols]

>>    gEfiS3SaveStateProtocolGuid                   # PROTOCOL

>> SOMETIMES_CONSUMED

>>    gEfiDxeSmmReadyToLockProtocolGuid             # PROTOCOL

>> SOMETIMES_PRODUCED

>>    gEfiLoadedImageProtocolGuid                   # PROTOCOL

>> SOMETIMES_PRODUCED

>> +  gEfiFirmwareVolume2ProtocolGuid               # PROTOCOL

>> SOMETIMES_CONSUMED

>>

>>  [Guids]

>>    gEfiXenInfoGuid

>> diff --git a/OvmfPkg/Library/PlatformBootManagerLib/BdsPlatform.c

>> b/OvmfPkg/Library/PlatformBootManagerLib/BdsPlatform.c

>> index 912c5ed1ece4..727c8b4792c0 100644

>> --- a/OvmfPkg/Library/PlatformBootManagerLib/BdsPlatform.c

>> +++ b/OvmfPkg/Library/PlatformBootManagerLib/BdsPlatform.c

>> @@ -15,6 +15,7 @@

>>  #include "BdsPlatform.h"

>>  #include <Guid/XenInfo.h>

>>  #include <Guid/RootBridgesConnectedEventGroup.h>

>> +#include <Protocol/FirmwareVolume2.h>

>>

>>

>>  //

>> @@ -149,6 +150,119 @@ PlatformRegisterFvBootOption (

>>    EfiBootManagerFreeLoadOptions (BootOptions, BootOptionCount);  }

>>

>> +/**

>> +  Remove all MemoryMapped(...)/FvFile(...) boot options whose device

>> +paths do

>> +  not resolve exactly to an FvFile in the system.

>> +

>> +  This prevents the proliferation of UEFI Shell boot options if DXEFV's

>> +base

>> +  address or size changes, or a different FILE_GUID binary is used as Shell.

>> +  EfiBootManagerFindLoadOption() used in PlatformRegisterFvBootOption()

>> +only

>> +  avoids exact duplicates.

>> +**/

>> +VOID

>> +RemoveStaleFvFileOptions (

>> +  VOID

>> +  )

>> +{

>> +  EFI_BOOT_MANAGER_LOAD_OPTION *BootOptions;

>> +  UINTN                        BootOptionCount;

>> +  UINTN                        Index;

>> +

>> +  BootOptions = EfiBootManagerGetLoadOptions (&BootOptionCount,

>> +                  LoadOptionTypeBoot);

>> +

>> +  for (Index = 0; Index < BootOptionCount; ++Index) {

>> +    EFI_DEVICE_PATH_PROTOCOL *Node1, *Node2, *SearchNode;

>> +    EFI_STATUS               Status;

>> +    EFI_HANDLE               FvHandle;

>> +

>> +    Node1 = BootOptions[Index].FilePath;

>> +    if (DevicePathType (Node1) != HARDWARE_DEVICE_PATH ||

>> +        DevicePathSubType (Node1) != HW_MEMMAP_DP) {

>> +      continue;

>> +    }

>> +

>> +    Node2 = NextDevicePathNode (Node1);

>> +    if (DevicePathType (Node2) != MEDIA_DEVICE_PATH ||

>> +        DevicePathSubType (Node2) != MEDIA_PIWG_FW_FILE_DP) {

>> +      continue;

>> +    }

>> +

>> +    //

>> +    // Locate the Firmware Volume2 protocol instance that is denoted by the

>> +    // boot option. If this lookup fails (i.e., the boot option references a

>> +    // firmware volume that doesn't exist), then we'll proceed to delete the

>> +    // boot option.

>> +    //

>> +    SearchNode = Node1;

>> +    Status = gBS->LocateDevicePath (&gEfiFirmwareVolume2ProtocolGuid,

>> +                    &SearchNode, &FvHandle);

>> +

>> +    if (!EFI_ERROR (Status)) {

>> +      //

>> +      // The firmware volume was found; now let's see if it contains the FvFile

>> +      // identified by GUID.

>> +      //

>> +      EFI_FIRMWARE_VOLUME2_PROTOCOL     *FvProtocol;

>> +      MEDIA_FW_VOL_FILEPATH_DEVICE_PATH *FvFileNode;

>> +      UINTN                             BufferSize;

>> +      EFI_FV_FILETYPE                   FoundType;

>> +      EFI_FV_FILE_ATTRIBUTES            FileAttributes;

>> +      UINT32                            AuthenticationStatus;

>> +

>> +      Status = gBS->HandleProtocol (FvHandle,

>> &gEfiFirmwareVolume2ProtocolGuid,

>> +                      (VOID **)&FvProtocol);

>> +      ASSERT_EFI_ERROR (Status);

>> +

>> +      FvFileNode = (MEDIA_FW_VOL_FILEPATH_DEVICE_PATH *)Node2;

>> +      //

>> +      // Buffer==NULL means we request metadata only: BufferSize,

>> FoundType,

>> +      // FileAttributes.

>> +      //

>> +      Status = FvProtocol->ReadFile (

>> +                             FvProtocol,

>> +                             &FvFileNode->FvFileName, // NameGuid

>> +                             NULL,                    // Buffer

>> +                             &BufferSize,

>> +                             &FoundType,

>> +                             &FileAttributes,

>> +                             &AuthenticationStatus

>> +                             );

>> +      if (!EFI_ERROR (Status)) {

>> +        //

>> +        // The FvFile was found. Keep the boot option.

>> +        //

>> +        continue;

>> +      }

>> +    }

>> +

>> +    //

>> +    // Delete the boot option.

>> +    //

>> +    Status = EfiBootManagerDeleteLoadOptionVariable (

>> +               BootOptions[Index].OptionNumber, LoadOptionTypeBoot);

>> +    DEBUG_CODE (

>> +      CHAR16 *DevicePathString;

>> +

>> +      DevicePathString =

>> ConvertDevicePathToText(BootOptions[Index].FilePath,

>> +                           FALSE, FALSE);

>> +      DEBUG ((

>> +        EFI_ERROR (Status) ? EFI_D_WARN : EFI_D_VERBOSE,

>> +        "%a: removing stale Boot#%04x %s: %r\n",

>> +        __FUNCTION__,

>> +        (UINT32)BootOptions[Index].OptionNumber,

>> +        DevicePathString == NULL ? L"<unavailable>" : DevicePathString,

>> +        Status

>> +        ));

>> +      if (DevicePathString != NULL) {

>> +        FreePool (DevicePathString);

>> +      }

>> +      );

>> +  }

>> +

>> +  EfiBootManagerFreeLoadOptions (BootOptions, BootOptionCount); }

>> +

>>  VOID

>>  PlatformRegisterOptionsAndKeys (

>>    VOID

>> @@ -1343,6 +1457,7 @@ Routine Description:

>>      PcdGetPtr (PcdShellFile), L"EFI Internal Shell", LOAD_OPTION_ACTIVE

>>      );

>>

>> +  RemoveStaleFvFileOptions ();

>>    SetBootOrderFromQemu ();

>>  }

>>

>> --

>> 1.8.3.1

> 


_______________________________________________
edk2-devel mailing list
edk2-devel@lists.01.org
https://lists.01.org/mailman/listinfo/edk2-devel
diff mbox

Patch

diff --git a/OvmfPkg/Library/PlatformBootManagerLib/PlatformBootManagerLib.inf b/OvmfPkg/Library/PlatformBootManagerLib/PlatformBootManagerLib.inf
index ffa1288e4d1a..f3303b9f2cf3 100644
--- a/OvmfPkg/Library/PlatformBootManagerLib/PlatformBootManagerLib.inf
+++ b/OvmfPkg/Library/PlatformBootManagerLib/PlatformBootManagerLib.inf
@@ -72,6 +72,7 @@  [Protocols]
   gEfiS3SaveStateProtocolGuid                   # PROTOCOL SOMETIMES_CONSUMED
   gEfiDxeSmmReadyToLockProtocolGuid             # PROTOCOL SOMETIMES_PRODUCED
   gEfiLoadedImageProtocolGuid                   # PROTOCOL SOMETIMES_PRODUCED
+  gEfiFirmwareVolume2ProtocolGuid               # PROTOCOL SOMETIMES_CONSUMED
 
 [Guids]
   gEfiXenInfoGuid
diff --git a/OvmfPkg/Library/PlatformBootManagerLib/BdsPlatform.c b/OvmfPkg/Library/PlatformBootManagerLib/BdsPlatform.c
index 912c5ed1ece4..727c8b4792c0 100644
--- a/OvmfPkg/Library/PlatformBootManagerLib/BdsPlatform.c
+++ b/OvmfPkg/Library/PlatformBootManagerLib/BdsPlatform.c
@@ -15,6 +15,7 @@ 
 #include "BdsPlatform.h"
 #include <Guid/XenInfo.h>
 #include <Guid/RootBridgesConnectedEventGroup.h>
+#include <Protocol/FirmwareVolume2.h>
 
 
 //
@@ -149,6 +150,119 @@  PlatformRegisterFvBootOption (
   EfiBootManagerFreeLoadOptions (BootOptions, BootOptionCount);
 }
 
+/**
+  Remove all MemoryMapped(...)/FvFile(...) boot options whose device paths do
+  not resolve exactly to an FvFile in the system.
+
+  This prevents the proliferation of UEFI Shell boot options if DXEFV's base
+  address or size changes, or a different FILE_GUID binary is used as Shell.
+  EfiBootManagerFindLoadOption() used in PlatformRegisterFvBootOption() only
+  avoids exact duplicates.
+**/
+VOID
+RemoveStaleFvFileOptions (
+  VOID
+  )
+{
+  EFI_BOOT_MANAGER_LOAD_OPTION *BootOptions;
+  UINTN                        BootOptionCount;
+  UINTN                        Index;
+
+  BootOptions = EfiBootManagerGetLoadOptions (&BootOptionCount,
+                  LoadOptionTypeBoot);
+
+  for (Index = 0; Index < BootOptionCount; ++Index) {
+    EFI_DEVICE_PATH_PROTOCOL *Node1, *Node2, *SearchNode;
+    EFI_STATUS               Status;
+    EFI_HANDLE               FvHandle;
+
+    Node1 = BootOptions[Index].FilePath;
+    if (DevicePathType (Node1) != HARDWARE_DEVICE_PATH ||
+        DevicePathSubType (Node1) != HW_MEMMAP_DP) {
+      continue;
+    }
+
+    Node2 = NextDevicePathNode (Node1);
+    if (DevicePathType (Node2) != MEDIA_DEVICE_PATH ||
+        DevicePathSubType (Node2) != MEDIA_PIWG_FW_FILE_DP) {
+      continue;
+    }
+
+    //
+    // Locate the Firmware Volume2 protocol instance that is denoted by the
+    // boot option. If this lookup fails (i.e., the boot option references a
+    // firmware volume that doesn't exist), then we'll proceed to delete the
+    // boot option.
+    //
+    SearchNode = Node1;
+    Status = gBS->LocateDevicePath (&gEfiFirmwareVolume2ProtocolGuid,
+                    &SearchNode, &FvHandle);
+
+    if (!EFI_ERROR (Status)) {
+      //
+      // The firmware volume was found; now let's see if it contains the FvFile
+      // identified by GUID.
+      //
+      EFI_FIRMWARE_VOLUME2_PROTOCOL     *FvProtocol;
+      MEDIA_FW_VOL_FILEPATH_DEVICE_PATH *FvFileNode;
+      UINTN                             BufferSize;
+      EFI_FV_FILETYPE                   FoundType;
+      EFI_FV_FILE_ATTRIBUTES            FileAttributes;
+      UINT32                            AuthenticationStatus;
+
+      Status = gBS->HandleProtocol (FvHandle, &gEfiFirmwareVolume2ProtocolGuid,
+                      (VOID **)&FvProtocol);
+      ASSERT_EFI_ERROR (Status);
+
+      FvFileNode = (MEDIA_FW_VOL_FILEPATH_DEVICE_PATH *)Node2;
+      //
+      // Buffer==NULL means we request metadata only: BufferSize, FoundType,
+      // FileAttributes.
+      //
+      Status = FvProtocol->ReadFile (
+                             FvProtocol,
+                             &FvFileNode->FvFileName, // NameGuid
+                             NULL,                    // Buffer
+                             &BufferSize,
+                             &FoundType,
+                             &FileAttributes,
+                             &AuthenticationStatus
+                             );
+      if (!EFI_ERROR (Status)) {
+        //
+        // The FvFile was found. Keep the boot option.
+        //
+        continue;
+      }
+    }
+
+    //
+    // Delete the boot option.
+    //
+    Status = EfiBootManagerDeleteLoadOptionVariable (
+               BootOptions[Index].OptionNumber, LoadOptionTypeBoot);
+    DEBUG_CODE (
+      CHAR16 *DevicePathString;
+
+      DevicePathString = ConvertDevicePathToText(BootOptions[Index].FilePath,
+                           FALSE, FALSE);
+      DEBUG ((
+        EFI_ERROR (Status) ? EFI_D_WARN : EFI_D_VERBOSE,
+        "%a: removing stale Boot#%04x %s: %r\n",
+        __FUNCTION__,
+        (UINT32)BootOptions[Index].OptionNumber,
+        DevicePathString == NULL ? L"<unavailable>" : DevicePathString,
+        Status
+        ));
+      if (DevicePathString != NULL) {
+        FreePool (DevicePathString);
+      }
+      );
+  }
+
+  EfiBootManagerFreeLoadOptions (BootOptions, BootOptionCount);
+}
+
 VOID
 PlatformRegisterOptionsAndKeys (
   VOID
@@ -1343,6 +1457,7 @@  Routine Description:
     PcdGetPtr (PcdShellFile), L"EFI Internal Shell", LOAD_OPTION_ACTIVE
     );
 
+  RemoveStaleFvFileOptions ();
   SetBootOrderFromQemu ();
 }