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
+ );