1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
|
# StackCheckLib Overview
## Table of Contents
- [StackCheckLib](#stackchecklib)
- [Table of Contents](#table-of-contents)
- [Introduction and Library Instances](#introduction-and-library-instances)
- [StackCheckLib](#stackchecklib)
- [DynamicStackCookieEntryPointLib](#dynamicstackcookieentrypointlib)
- [StackCheckLibNull](#stackchecklibnull)
- [How Failures are Handled](#how-failures-are-handled)
- [Debugging Stack Cookie Check Failures](#debugging-stack-cookie-check-failures)
- [Usage](#usage)
## 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](https://developer.arm.com/documentation/ddi0597/2024-03/Base-Instructions/SVC--Supervisor-Call-?lang=en)
- 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](https://developer.arm.com/documentation/ddi0602/2024-03/Base-Instructions/SVC--Supervisor-Call-?lang=en)
## 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
```inf
[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.:
```inf
[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
```inf
[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.:
```inf
[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:
```inf
[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:
```inf
SomePkg/SomeDirectory/SomeModule.inf {
<LibraryClasses>
StackCheckLib|MdePkg/Library/StackCheckLibNull/StackCheckLibNull.inf
}
```
It is not recommended to disable stack cookie checking in production scenarios.
|