SEC, PEI_CORE and PEIM type modules generally execute in place from memory that is not writable. This means that it is not generally possible to use an entrypoint implementation that stores a dynamically generated stack cookie into a global variable. For PEIMs in particular, there may be other options, such as a DEPEX on the permanent memory PPI, but the current dynamic implementations of the stack checking library entrypoints for PEI_CORE and PEIM modules must not be used as-is. So remove them, and update the readme accordingly. Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
12 KiB
StackCheckLib Overview
Table of Contents
Introduction and Library Instances
StackCheckLib
contains the required functionality for initializing the stack cookie
value (based on a randomly generated value during build time), checking the value,
and triggering an interrupt when a mismatch occurs. The stack cookie is a random value
placed on the stack between the stack variables and the return address so that
continuously writing past the stack variables will cause the stack cookie to be
overwritten. Before the function returns, the stack cookie value will be checked and
if there is a mismatch then StackCheckLib
handles the failure.
Because UEFI doesn't use the C runtime libraries provided by MSVC, the stack check code is written in assembly within this library. GCC and Clang compilers have built-in support for stack cookie checking, so this library only handles failures.
The stack cookie value is initialized at compile time via updates to the AutoGen process.
Each module will define STACK_COOKIE_VALUE
which is used for the module stack cookie
value.
The entry point libraries under MdePkg/DynamicStackCookieEntryPointLib/
update the stack
cookie value at runtime for improved security, but there are cases where the stack cookie
global cannot be written to such as in execute-in-place (XIP) modules and during the
temporary RAM phase of the boot process. It is always preferable to use
one of the dynamic stack cookie entry points when possible.
StackCheckLib
StackCheckLib
provides the stack cookie checking functionality per architecture and
toolchain. The currently supported pairs are IA32{GCC,MSVC}, X64{GCC, MSVC},
ARM{GCC, MSVC}, and AARCH64{GCC, MSVC}. StackCheckLib
is agnostic as to
whether the stack cookie was updated during build time or run time; it simply
checks the cookie in the MSVC case and in both GCC and MSVC responds to stack
cookie checking failures.
To add support for other architectures/toolchains, additional assembly files
should be added to StackCheckLib.inf
and scoped to that architecture/toolchain.
Note: Stack cookie failures generate exceptions and SEC and PEI_CORE may not have
exception handlers registered. In order to safely use stack cookie checking in
these phases, a platform should implement exception handlers because unhandled
exceptions may lead to a hung system state. If a platform does not implement
exception handlers in SEC and PEI_CORE, it is recommended to use StackCheckLibNull
for these phases, except for development purposes.
DynamicStackCookieEntryPointLib
Each EntryPoint lib under MdePkg/DynamicStackCookieEntryPointLib/
is an instance of
that module type entry point lib which updates the stack cookie value for the module at
runtime. This is the preferred method for stack cookie initialization as it provides
improved security. The stack cookie value is initialized at runtime in _ModuleEntryPoint
by calling rdrand
on x86 and RNDR
on AARCH64. If the random number generator is not
supported on that platform or otherwise returns an error, then the value will still have
the build-time randomized value to fall back on.
Typically, dynamic cookies cannot be used for SEC, PEI_CORE, and PEIM modules, due to the lack of the ability to write to globals for many architectures. If a given platform can write to globals during these phases, it is recommended to use the provided dynamic stack cookie entry point lib for those types. Note that SEC does not have a universal entry point, so there is no dynamic stack cookie entry point lib there.
The dynamic stack cookie entry point lib is used in place of the standard entry point lib, e.g. for UefiDriverEntryPoint to have dynamic stack cookies, a platform would remove MdePkg/Library/UefiDriverEntryPoint/UefiDriverEntryPoint.inf from its DSC and instead include MdePkg/Library/DynamicStackCookieEntryPointLib/UefiDriverEntryPoint.inf.
See the Usage section for other ways of including these libraries.
Note: Standalone MM Core support for dynamic cookies for AARCH64 is currently not supported, due to the unique entry point mechanism there. This support will be added at a future date.
StackCheckLibNull
StackCheckLibNull
is an instance of StackCheckLib
which does not perform any stack
cookie checks. This is useful for modules which will fail if stack cookie checks are
inserted. Of course, this is not recommended for production code outside of
SEC and PEI_CORE.
How Failures are Handled
When a stack cookie check fails, the StackCheckLib
library will first call into a hook
function StackCheckFailureHook()
which only has a NULL implementation in edk2.
The NULL implementation will simply print the failure address and return, but a platform
can implement their own instance of this library which can perform additional actions
before the system triggers an interrupt.
After StackCheckFailureHook()
returns, the library will trigger an interrupt with
PcdStackCookieExceptionVector.
- On IA32 and X64 platforms, PcdStackCookieExceptionVector is used as an index into the Interrupt Descriptor Table.
- On ARM platforms, a software interrupt (
SWI
) is called with the value of PcdStackCookieExceptionVector. The value can be retrieved by the handler by reading bits [7:0] of the instruction opcode which will allow the handler to determine if the interrupt was triggered by the stack cookie check. Reference: Arm A64 Instruction Set Architecture Version 2024-3 - On AARCH64 platforms, a supervisor call (
SVC
) is called with the value of PcdStackCookieExceptionVector. This value can similarly be retrieved by the handler to determine if the interrupt was triggered by the stack cookie check. Reference: Arm A64 Instruction Set Architecture Version 2024-3
Debugging Stack Cookie Check Failures
Tracking down the origin of stack cookie failures can be difficult. Programmers may attempt printf debugging to determine which function has an overflow only to find that the failure disappears on the next boot. This curiosity is usually due to the black-box heuristic used by compilers to determine where to put stack cookie checks or compiler optimization features removing the failing check. The address where the failed stack cookie check occurred will be printed using DebugLib. If .map files are available, the address combined with the image offset can be used to determine the function which failed.
GNU-based compilers have the -fstack-protector-all
flag to force stack cookie checks on
all functions which could create a more consistent environment for debugging assuming an
earlier failure doesn't mask the targeted one and the flash space can accommodate the
increased size.
The Visual Studio (MSVC) toolchain has the ability to generate .cod
files during compilation
which interleave C and the generated assembly code. These files will contain the stack cookie
checks and are useful for determining where the checks are placed. To generate these files,
append /FAcs
to the build options for each target module. The easiest way to do this is to
update the tools_def file so the <TARGET>_<TOOLCHAIN>_<ARCH>_CC_FLAGS
includes /FAcs
.
Usage
edk2 updated the tools_def to add /GS
to VS2022 and VS2019 IA32/X64 builds and
-fstack-protector
to GCC builds. This will cause stack cookie references to be inserted
throughout the code. Every module should have a StackCheckLib
instance linked to satisfy
these references. So every module doesn't need to add StackCheckLib
to the LibraryClasses
section of the INF file, StackCheckLib
is added as a dependency for each entry point lib.
This means that custom entry point libs need to have StackCheckLib added as a dependency.
The only exception to this is MSVC built host-based unit tests as they will be
compiled with the runtime libraries which already contain the stack cookie definitions
and will collide with StackCheckLib
. A StackCheckLibHostApplication.inf
is linked
by UnitTestFrameworkPkg/UnitTestFrameworkPkgHost.dsc.inc
that provides the stack
cookie functions for GCC HOST_APPLICATIONS but not for MSVC HOST_APPLICATIONS.
Default Stack Check Library Configuration
MdePkg/MdeLibs.dsc.inc
links StackCheckLibNull
and StackCheckFailureLibNull
for all
types.
As stated above, all HOST_APPLICATIONS will link against a HOST_APPLICATION specific
implementation provided in UnitTestFrameworkPkg/UnitTestFrameworkPkgHost.dsc.inc
.
Custom Stack Check Library Configuration
In order to use a different instance of StackCheckLib than MdeLibs.dsc.inc
provides, a DSC
should add one of the following:
Static Stack Check Cookie Configuration
[Defines]
DEFINE CUSTOM_STACK_CHECK_LIB = STATIC
This will cause MdeLibs.dsc.inc
to not link StackCheckLibNull
and instead link
StackCheckLib
to perform stack cookie checking on the static stack cookies, but not update
any of the stack cookies at runtime.
Because edk2 does not implement exception handling for SEC
and PEI_CORE
, MdeLibs.dsc.inc
uses StackCheckLibNull
for these phases always. If a platform wishes to use StackCheckLib
for these phases, it should override this in its DSC, e.g.:
[LibraryClasses.common.SEC, LibraryClasses.common.PEI_CORE]
StackCheckLib|MdePkg/Library/StackCheckLib/StackCheckLib.inf
It is recommended that a platform only do this for debugging or if they have implemented exception handlers for these phases.
Dynamic Stack Cookie Configuration
[Defines]
DEFINE CUSTOM_STACK_CHECK_LIB = DYNAMIC
This will cause MdeLibs.dsc.inc
to not link StackCheckLibNull
and instead link
StackCheckLib
to perform stack cookie checking. It will also link the dynamic
stack cookie updating versions of DxeCoreEntryPoint
, StandaloneMmDriverEntryPoint
,
UefiApplicationEntryPoint
, and UefiDriverEntryPoint
.
Because edk2 does not implement exception handling for SEC
and PEI_CORE
,
MdeLibs.dsc.inc
uses StackCheckLibNull
for these phases always. If a
platform wishes to use StackCheckLib
for these phases, it can enable static
stack cookie checking, as documented in the previous section. Due to the fact
that writable global variables are not supported in the SEC
or PEI
phases
of execution, dynamic stack cookie checking is not supported here.
It is recommended that a platform only do this for debugging or if they have implemented exception handlers for these phases.
Note: StandaloneMmCoreEntryPoint
is recommended to use the dynamic stack cookie if
possible, but as it is not supported on AARCH64 today, it is not included in MdeLibs.dsc.inc.
Platforms should include this separately, e.g.:
[LibraryClasses.X64]
StandaloneMmCoreEntryPoint|MdePkg/Library/DynamicStackCookieEntryPointLib/StandaloneMmCoreEntryPoint.inf
Platforms then must remove any references to these entry point libs in their DSC, so that
the MdeLibs.dsc.inc
versions are chosen. Alternatively, for better DSC readability,
a platform can directly reference the dynamic stack cookie entry points.
Disable Stack Check Library
If a platform would like to disable stack cookies (say for debugging purposes), they can add the following to their DSC:
[BuildOptions]
MSVC:*_*_*_CC_FLAGS = /GS-
GCC:*_*_*_CC_FLAGS = -fno-stack-protector
The same build options can be put in a module's INF to only disable stack cookies for that module.
Alternatively, a module can have the stack cookies inserted but checking disabled by including the following in a DSC:
SomePkg/SomeDirectory/SomeModule.inf {
<LibraryClasses>
StackCheckLib|MdePkg/Library/StackCheckLibNull/StackCheckLibNull.inf
}
It is not recommended to disable stack cookie checking in production scenarios.