From c108a8bfffac4e9f2eff962dbde1a06f2d63a650 Mon Sep 17 00:00:00 2001 From: Marc Chen Date: Thu, 6 Aug 2026 18:19:18 +0800 Subject: [PATCH] MdeModulePkg: Add optional variable runtime hooks Add a default-disabled protocol that lets platforms run pre- and post-hooks around runtime SetVariable MM communication. Discover the first valid provider before EndOfDxe and convert cached callbacks for runtime use. Require EFI_SUCCESS from each pre-hook before entering MM. Every independent runtime request invokes the provider so it can serialize shared platform resources. Signed-off-by: Marc Chen Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../Protocol/VariableSmmRuntimeDxeHook.h | 84 +++++ MdeModulePkg/MdeModulePkg.dec | 10 + .../RuntimeDxe/VariableSmmRuntimeDxe.c | 46 ++- .../RuntimeDxe/VariableSmmRuntimeDxe.inf | 4 + .../RuntimeDxe/VariableSmmRuntimeDxeHook.c | 302 ++++++++++++++++++ .../VariableSmmRuntimeDxeHookInternal.h | 74 +++++ 6 files changed, 515 insertions(+), 5 deletions(-) create mode 100644 MdeModulePkg/Include/Protocol/VariableSmmRuntimeDxeHook.h create mode 100644 MdeModulePkg/Universal/Variable/RuntimeDxe/VariableSmmRuntimeDxeHook.c create mode 100644 MdeModulePkg/Universal/Variable/RuntimeDxe/VariableSmmRuntimeDxeHookInternal.h diff --git a/MdeModulePkg/Include/Protocol/VariableSmmRuntimeDxeHook.h b/MdeModulePkg/Include/Protocol/VariableSmmRuntimeDxeHook.h new file mode 100644 index 0000000000..2ed052d891 --- /dev/null +++ b/MdeModulePkg/Include/Protocol/VariableSmmRuntimeDxeHook.h @@ -0,0 +1,84 @@ +/** @file + Optional hooks around VariableSmmRuntimeDxe SetVariable() MM communication. + + A provider must be a DXE_RUNTIME_DRIVER, or otherwise guarantee that its code + and data remain runtime-resident. It must not use boot services or allocate + boot-services memory after ExitBootServices(). + + Hook inputs are untrusted runtime-service inputs. Providers must validate all + inputs they consume, bound all waits, and must not call SetVariable() from + either hook. These hooks do not replace SMM validation, VarCheck, variable + policy, Secure Boot authentication, MOR handling, or flash-write + authorization. + + A provider MAY access MMIO, PIO, mailbox, or device resources reserved + exclusively for firmware and not exposed to or accessed by the OS. If a + resource may also be accessed by the OS, the provider MUST use a + platform-defined ownership or synchronization mechanism shared with the OS. + UEFI locks and TPL do not synchronize with OS drivers. For this contract, + "not exposed to the OS" means absent from OS discovery and description + mechanisms such as ACPI, PCI BAR assignment, and device tree. It is not + sufficient that the currently loaded OS driver does not use the resource. + + Copyright (c) Microsoft Corporation. + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#pragma once + +#define EDKII_VARIABLE_RUNTIME_HOOK_PROTOCOL_GUID \ + { \ + 0xdeddcd7a, 0xd76d, 0x46a3, { 0xba, 0xa5, 0xbf, 0x25, 0x50, 0x77, 0xdc, 0x10 } \ + } + +/** + Invoked immediately before runtime SetVariable() communication is sent to MM. + + The provider owns its runtime-safe synchronization state. The interface + intentionally carries no opaque per-operation context. + + @param[in] VariableName Name of the variable. + @param[in] VendorGuid Variable vendor GUID. + @param[in] Attributes Variable attributes. + @param[in] DataSize Size of Data in bytes. + @param[in] Data Variable data. + + @retval EFI_SUCCESS Continue with the existing MM variable service. + @retval Others Reject or defer the operation without entering MM. +**/ +typedef +EFI_STATUS +(EFIAPI *EDKII_VARIABLE_PRE_SET_VARIABLE)( + IN CONST CHAR16 *VariableName, + IN CONST EFI_GUID *VendorGuid, + IN UINT32 Attributes, + IN UINTN DataSize, + IN CONST VOID *Data + ); + +/** + Invoked after runtime SetVariable() communication completes. + + This notification is called only when PreSetVariable() returned success. + SetVariableStatus is authoritative and is returned unchanged by the existing + variable service. + + @param[in] SetVariableStatus Result from the existing MM variable service. +**/ +typedef +VOID +(EFIAPI *EDKII_VARIABLE_POST_SET_VARIABLE)( + IN EFI_STATUS SetVariableStatus + ); + +/// +/// This protocol is optional. Both callbacks are required. The first valid +/// provider discovered before EndOfDxe is used. +/// +typedef struct { + EDKII_VARIABLE_PRE_SET_VARIABLE PreSetVariable; + EDKII_VARIABLE_POST_SET_VARIABLE PostSetVariable; +} EDKII_VARIABLE_RUNTIME_HOOK_PROTOCOL; + +extern EFI_GUID gEdkiiVariableSmmRuntimeDxeHookProtocolGuid; diff --git a/MdeModulePkg/MdeModulePkg.dec b/MdeModulePkg/MdeModulePkg.dec index f6b1a73d70..91c81835bf 100644 --- a/MdeModulePkg/MdeModulePkg.dec +++ b/MdeModulePkg/MdeModulePkg.dec @@ -639,6 +639,10 @@ # Include/Protocol/VariableLock.h gEdkiiVariableLockProtocolGuid = { 0xcd3d0a05, 0x9e24, 0x437c, { 0xa8, 0x91, 0x1e, 0xe0, 0x53, 0xdb, 0x76, 0x38 }} + ## Optional hooks around runtime SetVariable() MM communication. + # Include/Protocol/VariableSmmRuntimeDxeHook.h + gEdkiiVariableSmmRuntimeDxeHookProtocolGuid = { 0xdeddcd7a, 0xd76d, 0x46a3, { 0xba, 0xa5, 0xbf, 0x25, 0x50, 0x77, 0xdc, 0x10 }} + ## Include/Protocol/VarCheck.h gEdkiiVarCheckProtocolGuid = { 0xaf23b340, 0x97b4, 0x4685, { 0x8d, 0x4f, 0xa3, 0xf2, 0x81, 0x69, 0xb2, 0x1d } } @@ -801,6 +805,12 @@ # @Prompt Enable the UEFI variable runtime cache. gEfiMdeModulePkgTokenSpaceGuid.PcdEnableVariableRuntimeCache|TRUE|BOOLEAN|0x00010039 + ## Indicates if optional hooks around runtime SetVariable() MM communication are enabled.

+ # TRUE - Discover and invoke the variable runtime hook protocol.
+ # FALSE - Do not discover or invoke the variable runtime hook protocol.
+ # @Prompt Enable variable runtime hooks. + gEfiMdeModulePkgTokenSpaceGuid.PcdEnableVariableSmmRuntimeDxeHook|FALSE|BOOLEAN|0x00010043 + ## Indicates if the statistics about variable usage will be collected. This information is # stored as a vendor configuration table into the EFI system table. # Set this PCD to TRUE to use VariableInfo application in MdeModulePkg\Application directory to get diff --git a/MdeModulePkg/Universal/Variable/RuntimeDxe/VariableSmmRuntimeDxe.c b/MdeModulePkg/Universal/Variable/RuntimeDxe/VariableSmmRuntimeDxe.c index 51d74d73b2..0980e83988 100644 --- a/MdeModulePkg/Universal/Variable/RuntimeDxe/VariableSmmRuntimeDxe.c +++ b/MdeModulePkg/Universal/Variable/RuntimeDxe/VariableSmmRuntimeDxe.c @@ -44,6 +44,7 @@ SPDX-License-Identifier: BSD-2-Clause-Patent #include "PrivilegePolymorphic.h" #include "VariableParsing.h" +#include "VariableSmmRuntimeDxeHookInternal.h" EFI_HANDLE mHandle = NULL; EFI_SMM_VARIABLE_PROTOCOL *mSmmVariable = NULL; @@ -1154,6 +1155,7 @@ RuntimeServiceSetVariable ( UINTN PayloadSize; SMM_VARIABLE_COMMUNICATE_ACCESS_VARIABLE *SmmVariableHeader; UINTN VariableNameSize; + BOOLEAN HookInvoked; // // Check input parameters. @@ -1178,6 +1180,21 @@ RuntimeServiceSetVariable ( return EFI_INVALID_PARAMETER; } + HookInvoked = FALSE; + if (FeaturePcdGet (PcdEnableVariableSmmRuntimeDxeHook)) { + Status = VariableRuntimeHookPreSetVariable ( + VariableName, + VendorGuid, + Attributes, + DataSize, + Data, + &HookInvoked + ); + if (Status != EFI_SUCCESS) { + return Status; + } + } + AcquireLockOnlyAtBootTime (&mVariableServicesLock); // @@ -1207,6 +1224,10 @@ RuntimeServiceSetVariable ( Done: ReleaseLockOnlyAtBootTime (&mVariableServicesLock); + if (FeaturePcdGet (PcdEnableVariableSmmRuntimeDxeHook)) { + VariableRuntimeHookPostSetVariable (HookInvoked, Status); + } + if (!EfiAtRuntime ()) { if (!EFI_ERROR (Status)) { SecureBootHook ( @@ -1307,6 +1328,10 @@ OnExitBootServices ( IN VOID *Context ) { + if (FeaturePcdGet (PcdEnableVariableSmmRuntimeDxeHook)) { + VariableRuntimeHookStopDiscovery (); + } + // // Init the communicate buffer. The buffer data size is: // SMM_COMMUNICATE_HEADER_SIZE + SMM_VARIABLE_COMMUNICATE_HEADER_SIZE. @@ -1388,6 +1413,9 @@ VariableAddressChangeEvent ( EfiConvertPointer (EFI_OPTIONAL_PTR, (VOID **)&mVariableRtCacheInfo.RuntimeHobCacheBuffer); EfiConvertPointer (EFI_OPTIONAL_PTR, (VOID **)&mVariableRtCacheInfo.RuntimeNvCacheBuffer); EfiConvertPointer (EFI_OPTIONAL_PTR, (VOID **)&mVariableRtCacheInfo.RuntimeVolatileCacheBuffer); + if (FeaturePcdGet (PcdEnableVariableSmmRuntimeDxeHook)) { + VariableRuntimeHookConvertPointers (); + } } /** @@ -1961,14 +1989,22 @@ VariableSmmRuntimeInitialize ( IN EFI_SYSTEM_TABLE *SystemTable ) { - VOID *SmmVariableRegistration; - VOID *SmmVariableWriteRegistration; - EFI_EVENT OnReadyToBootEvent; - EFI_EVENT ExitBootServiceEvent; - EFI_EVENT LegacyBootEvent; + VOID *SmmVariableRegistration; + VOID *SmmVariableWriteRegistration; + EFI_EVENT OnReadyToBootEvent; + EFI_EVENT ExitBootServiceEvent; + EFI_EVENT LegacyBootEvent; + EFI_STATUS Status; EfiInitializeLock (&mVariableServicesLock, TPL_NOTIFY); + if (FeaturePcdGet (PcdEnableVariableSmmRuntimeDxeHook)) { + Status = VariableRuntimeHookInitialize (); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_WARN, "Variable runtime hook discovery failed: %r\n", Status)); + } + } + // // Smm variable service is ready // diff --git a/MdeModulePkg/Universal/Variable/RuntimeDxe/VariableSmmRuntimeDxe.inf b/MdeModulePkg/Universal/Variable/RuntimeDxe/VariableSmmRuntimeDxe.inf index af8a32a754..b177a39e51 100644 --- a/MdeModulePkg/Universal/Variable/RuntimeDxe/VariableSmmRuntimeDxe.inf +++ b/MdeModulePkg/Universal/Variable/RuntimeDxe/VariableSmmRuntimeDxe.inf @@ -38,6 +38,8 @@ [Sources] VariableSmmRuntimeDxe.c + VariableSmmRuntimeDxeHook.c + VariableSmmRuntimeDxeHookInternal.h PrivilegePolymorphic.h Measurement.c VariableParsing.c @@ -72,10 +74,12 @@ ## UNDEFINED # Used to do smm communication gEfiSmmVariableProtocolGuid gEdkiiVariableLockProtocolGuid ## PRODUCES + gEdkiiVariableSmmRuntimeDxeHookProtocolGuid ## SOMETIMES_CONSUMES ## NOTIFY gEdkiiVarCheckProtocolGuid ## PRODUCES gEdkiiVariablePolicyProtocolGuid ## PRODUCES [FeaturePcd] + gEfiMdeModulePkgTokenSpaceGuid.PcdEnableVariableSmmRuntimeDxeHook ## CONSUMES gEfiMdeModulePkgTokenSpaceGuid.PcdVariableCollectStatistics ## CONSUMES [Pcd] diff --git a/MdeModulePkg/Universal/Variable/RuntimeDxe/VariableSmmRuntimeDxeHook.c b/MdeModulePkg/Universal/Variable/RuntimeDxe/VariableSmmRuntimeDxeHook.c new file mode 100644 index 0000000000..c4de827f1d --- /dev/null +++ b/MdeModulePkg/Universal/Variable/RuntimeDxe/VariableSmmRuntimeDxeHook.c @@ -0,0 +1,302 @@ +/** @file + Support for the optional VariableSmmRuntimeDxe hook protocol. + + Copyright (c) Microsoft Corporation. + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include + +#include +#include +#include + +#include "VariableSmmRuntimeDxeHookInternal.h" + +STATIC EDKII_VARIABLE_PRE_SET_VARIABLE mPreSetVariable; +STATIC EDKII_VARIABLE_POST_SET_VARIABLE mPostSetVariable; +STATIC EFI_EVENT mVariableRuntimeHookNotifyEvent; +STATIC EFI_EVENT mVariableRuntimeHookEndOfDxeEvent; +STATIC VOID *mVariableRuntimeHookRegistration; +STATIC BOOLEAN mVariableRuntimeHookDiscoveryEnded; + +/** + Caches the first valid variable runtime hook provider. + + @param[in] Protocol Variable runtime hook protocol instance. + + @retval TRUE The provider was valid and its callbacks were cached. + @retval FALSE The provider was invalid or provider discovery has ended. +**/ +STATIC +BOOLEAN +CacheVariableRuntimeHook ( + IN EDKII_VARIABLE_RUNTIME_HOOK_PROTOCOL *Protocol + ) +{ + if (mVariableRuntimeHookDiscoveryEnded || + (mPreSetVariable != NULL) || + (Protocol == NULL) || + (Protocol->PreSetVariable == NULL) || + (Protocol->PostSetVariable == NULL)) + { + return FALSE; + } + + mPreSetVariable = Protocol->PreSetVariable; + mPostSetVariable = Protocol->PostSetVariable; + return TRUE; +} + +/** + Closes an event and clears its cached event handle. + + @param[in,out] Event Address of the cached event handle. +**/ +STATIC +VOID +CloseVariableRuntimeHookEvent ( + IN OUT EFI_EVENT *Event + ) +{ + EFI_EVENT EventToClose; + + EventToClose = *Event; + *Event = NULL; + if (EventToClose != NULL) { + gBS->CloseEvent (EventToClose); + } +} + +/** + Closes the variable runtime hook protocol notification event and clears its + registration key. +**/ +STATIC +VOID +CloseVariableRuntimeHookNotifyEvent ( + VOID + ) +{ + CloseVariableRuntimeHookEvent (&mVariableRuntimeHookNotifyEvent); + mVariableRuntimeHookRegistration = NULL; +} + +/** + Notification function invoked when the variable runtime hook protocol is + installed. + + @param[in] Event Event whose notification function is being invoked. + @param[in] Context Pointer to the notification function context. +**/ +STATIC +VOID +EFIAPI +VariableRuntimeHookInstalled ( + IN EFI_EVENT Event, + IN VOID *Context + ) +{ + EFI_STATUS Status; + EDKII_VARIABLE_RUNTIME_HOOK_PROTOCOL *Protocol; + + if (mVariableRuntimeHookDiscoveryEnded || (mPreSetVariable != NULL)) { + return; + } + + do { + Protocol = NULL; + Status = gBS->LocateProtocol ( + &gEdkiiVariableSmmRuntimeDxeHookProtocolGuid, + mVariableRuntimeHookRegistration, + (VOID **)&Protocol + ); + if (EFI_ERROR (Status)) { + return; + } + } while (!CacheVariableRuntimeHook (Protocol)); + + CloseVariableRuntimeHookNotifyEvent (); +} + +/** + Stops variable runtime hook provider discovery and closes discovery events. +**/ +VOID +VariableRuntimeHookStopDiscovery ( + VOID + ) +{ + mVariableRuntimeHookDiscoveryEnded = TRUE; + CloseVariableRuntimeHookNotifyEvent (); + CloseVariableRuntimeHookEvent (&mVariableRuntimeHookEndOfDxeEvent); +} + +/** + EndOfDxe notification function that accepts any pending provider and then + stops provider discovery. + + @param[in] Event Event whose notification function is being invoked. + @param[in] Context Pointer to the notification function context. +**/ +STATIC +VOID +EFIAPI +VariableRuntimeHookEndOfDxe ( + IN EFI_EVENT Event, + IN VOID *Context + ) +{ + if ((mPreSetVariable == NULL) && (mVariableRuntimeHookNotifyEvent != NULL)) { + VariableRuntimeHookInstalled (mVariableRuntimeHookNotifyEvent, NULL); + } + + VariableRuntimeHookStopDiscovery (); +} + +/** + Discovers the first valid variable runtime hook provider and registers for + notification if a provider is not yet available. + + @retval EFI_SUCCESS Discovery was initialized or a provider was cached. + @retval Others Event or protocol notification registration failed. +**/ +EFI_STATUS +VariableRuntimeHookInitialize ( + VOID + ) +{ + EFI_STATUS Status; + EDKII_VARIABLE_RUNTIME_HOOK_PROTOCOL *Protocol; + + if (mVariableRuntimeHookDiscoveryEnded || (mPreSetVariable != NULL)) { + return EFI_SUCCESS; + } + + Protocol = NULL; + Status = gBS->LocateProtocol ( + &gEdkiiVariableSmmRuntimeDxeHookProtocolGuid, + NULL, + (VOID **)&Protocol + ); + if (!EFI_ERROR (Status) && CacheVariableRuntimeHook (Protocol)) { + return EFI_SUCCESS; + } + + Status = gBS->CreateEventEx ( + EVT_NOTIFY_SIGNAL, + TPL_CALLBACK, + VariableRuntimeHookEndOfDxe, + NULL, + &gEfiEndOfDxeEventGroupGuid, + &mVariableRuntimeHookEndOfDxeEvent + ); + if (EFI_ERROR (Status)) { + return Status; + } + + Status = gBS->CreateEvent ( + EVT_NOTIFY_SIGNAL, + TPL_CALLBACK, + VariableRuntimeHookInstalled, + NULL, + &mVariableRuntimeHookNotifyEvent + ); + if (EFI_ERROR (Status)) { + VariableRuntimeHookStopDiscovery (); + return Status; + } + + Status = gBS->RegisterProtocolNotify ( + &gEdkiiVariableSmmRuntimeDxeHookProtocolGuid, + mVariableRuntimeHookNotifyEvent, + &mVariableRuntimeHookRegistration + ); + if (EFI_ERROR (Status)) { + VariableRuntimeHookStopDiscovery (); + return Status; + } + + // + // Process providers installed between the initial lookup and registration. + // + VariableRuntimeHookInstalled (mVariableRuntimeHookNotifyEvent, NULL); + return EFI_SUCCESS; +} + +/** + Converts cached variable runtime hook callback pointers to virtual addresses. +**/ +VOID +VariableRuntimeHookConvertPointers ( + VOID + ) +{ + EfiConvertPointer (EFI_OPTIONAL_PTR, (VOID **)&mPreSetVariable); + EfiConvertPointer (EFI_OPTIONAL_PTR, (VOID **)&mPostSetVariable); +} + +/** + Invokes the cached pre-hook for an OS runtime SetVariable() request. + + @param[in] VariableName Name of the variable. + @param[in] VendorGuid Variable vendor GUID. + @param[in] Attributes Variable attributes. + @param[in] DataSize Size of Data in bytes. + @param[in] Data Variable data. + @param[out] HookInvoked TRUE if the pre-hook returned success. + + @retval EFI_SUCCESS Continue with MM communication. + @retval Others The pre-hook rejected or deferred the operation. +**/ +EFI_STATUS +VariableRuntimeHookPreSetVariable ( + IN CONST CHAR16 *VariableName, + IN CONST EFI_GUID *VendorGuid, + IN UINT32 Attributes, + IN UINTN DataSize, + IN CONST VOID *Data, + OUT BOOLEAN *HookInvoked + ) +{ + EFI_STATUS Status; + + *HookInvoked = FALSE; + if (!EfiAtRuntime () || (mPreSetVariable == NULL)) { + return EFI_SUCCESS; + } + + Status = mPreSetVariable ( + VariableName, + VendorGuid, + Attributes, + DataSize, + Data + ); + if (Status != EFI_SUCCESS) { + return Status; + } + + *HookInvoked = TRUE; + return EFI_SUCCESS; +} + +/** + Invokes the cached post-hook after MM communication completes. + + @param[in] HookInvoked TRUE if the pre-hook returned success. + @param[in] SetVariableStatus Authoritative MM variable service result. +**/ +VOID +VariableRuntimeHookPostSetVariable ( + IN BOOLEAN HookInvoked, + IN EFI_STATUS SetVariableStatus + ) +{ + if (!HookInvoked) { + return; + } + + mPostSetVariable (SetVariableStatus); +} diff --git a/MdeModulePkg/Universal/Variable/RuntimeDxe/VariableSmmRuntimeDxeHookInternal.h b/MdeModulePkg/Universal/Variable/RuntimeDxe/VariableSmmRuntimeDxeHookInternal.h new file mode 100644 index 0000000000..7050783415 --- /dev/null +++ b/MdeModulePkg/Universal/Variable/RuntimeDxe/VariableSmmRuntimeDxeHookInternal.h @@ -0,0 +1,74 @@ +/** @file + Internal support for the optional VariableSmmRuntimeDxe hook protocol. + + Copyright (c) Microsoft Corporation. + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#pragma once + +#include + +/** + Discovers the first valid variable runtime hook provider and registers for + notification if a provider is not yet available. + + @retval EFI_SUCCESS Discovery was initialized or a provider was cached. + @retval Others Event or protocol notification registration failed. +**/ +EFI_STATUS +VariableRuntimeHookInitialize ( + VOID + ); + +/** + Stops variable runtime hook provider discovery and closes discovery events. +**/ +VOID +VariableRuntimeHookStopDiscovery ( + VOID + ); + +/** + Converts cached variable runtime hook callback pointers to virtual addresses. +**/ +VOID +VariableRuntimeHookConvertPointers ( + VOID + ); + +/** + Invokes the cached pre-hook for an OS runtime SetVariable() request. + + @param[in] VariableName Name of the variable. + @param[in] VendorGuid Variable vendor GUID. + @param[in] Attributes Variable attributes. + @param[in] DataSize Size of Data in bytes. + @param[in] Data Variable data. + @param[out] HookInvoked TRUE if the pre-hook returned success. + + @retval EFI_SUCCESS Continue with MM communication. + @retval Others The pre-hook rejected or deferred the operation. +**/ +EFI_STATUS +VariableRuntimeHookPreSetVariable ( + IN CONST CHAR16 *VariableName, + IN CONST EFI_GUID *VendorGuid, + IN UINT32 Attributes, + IN UINTN DataSize, + IN CONST VOID *Data, + OUT BOOLEAN *HookInvoked + ); + +/** + Invokes the cached post-hook after MM communication completes. + + @param[in] HookInvoked TRUE if the pre-hook returned success. + @param[in] SetVariableStatus Authoritative MM variable service result. +**/ +VOID +VariableRuntimeHookPostSetVariable ( + IN BOOLEAN HookInvoked, + IN EFI_STATUS SetVariableStatus + );