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 <marc.chen@microsoft.com>

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Marc Chen 2026-08-06 18:19:18 +08:00
parent 86ecae29c6
commit c108a8bfff
6 changed files with 515 additions and 5 deletions

View file

@ -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;

View file

@ -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.<BR><BR>
# TRUE - Discover and invoke the variable runtime hook protocol.<BR>
# FALSE - Do not discover or invoke the variable runtime hook protocol.<BR>
# @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

View file

@ -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
//

View file

@ -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]

View file

@ -0,0 +1,302 @@
/** @file
Support for the optional VariableSmmRuntimeDxe hook protocol.
Copyright (c) Microsoft Corporation.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include <Uefi.h>
#include <Guid/EventGroup.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeLib.h>
#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);
}

View file

@ -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 <Protocol/VariableSmmRuntimeDxeHook.h>
/**
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
);