diff --git a/MdePkg/Library/StackCheckLib/AArch64/StackCookieInterrupt.S b/MdePkg/Library/StackCheckLib/AArch64/StackCookieInterrupt.S new file mode 100644 index 0000000000..bce13643e3 --- /dev/null +++ b/MdePkg/Library/StackCheckLib/AArch64/StackCookieInterrupt.S @@ -0,0 +1,21 @@ +//------------------------------------------------------------------------------ +// AArch64/StackCookieInterrupt.S +// +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: BSD-2-Clause-Patent +//------------------------------------------------------------------------------ + + .text + +//------------------------------------------------------------------------------ +// Calls an interrupt using the vector specified by PcdStackCookieExceptionVector +// +// VOID +// TriggerStackCookieInterrupt ( +// VOID +// ); +//------------------------------------------------------------------------------ +.global ASM_PFX(TriggerStackCookieInterrupt) +ASM_PFX(TriggerStackCookieInterrupt): + svc FixedPcdGet8 (PcdStackCookieExceptionVector) + ret diff --git a/MdePkg/Library/StackCheckLib/AArch64/StackCookieInterrupt.asm b/MdePkg/Library/StackCheckLib/AArch64/StackCookieInterrupt.asm new file mode 100644 index 0000000000..ff5ee35acc --- /dev/null +++ b/MdePkg/Library/StackCheckLib/AArch64/StackCookieInterrupt.asm @@ -0,0 +1,25 @@ +;------------------------------------------------------------------------------ +; AArch64/StackCookieInterrupt.asm +; +; Copyright (c) Microsoft Corporation. +; SPDX-License-Identifier: BSD-2-Clause-Patent +;------------------------------------------------------------------------------ + + EXPORT TriggerStackCookieInterrupt + + AREA |.text|, CODE, READONLY + +;------------------------------------------------------------------------------ +; Calls an interrupt using the vector specified by PcdStackCookieExceptionVector +; +; VOID +; TriggerStackCookieInterrupt ( +; VOID +; ); +;------------------------------------------------------------------------------ +TriggerStackCookieInterrupt PROC + SVC FixedPcdGet8 (PcdStackCookieExceptionVector) + RET +TriggerStackCookieInterrupt ENDP + + END diff --git a/MdePkg/Library/StackCheckLib/Arm/StackCookieInterrupt.S b/MdePkg/Library/StackCheckLib/Arm/StackCookieInterrupt.S new file mode 100644 index 0000000000..1f10bb8aea --- /dev/null +++ b/MdePkg/Library/StackCheckLib/Arm/StackCookieInterrupt.S @@ -0,0 +1,21 @@ +//------------------------------------------------------------------------------ +// Arm/StackCookieInterrupt.S +// +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: BSD-2-Clause-Patent +//------------------------------------------------------------------------------ + + .text + +//------------------------------------------------------------------------------ +// Calls an interrupt using the vector specified by PcdStackCookieExceptionVector +// +// VOID +// TriggerStackCookieInterrupt ( +// VOID +// ); +//------------------------------------------------------------------------------ +.global ASM_PFX(TriggerStackCookieInterrupt) +ASM_PFX(TriggerStackCookieInterrupt): + swi FixedPcdGet8 (PcdStackCookieExceptionVector) + bx lr diff --git a/MdePkg/Library/StackCheckLib/Arm/StackCookieInterrupt.asm b/MdePkg/Library/StackCheckLib/Arm/StackCookieInterrupt.asm new file mode 100644 index 0000000000..f1b1e53943 --- /dev/null +++ b/MdePkg/Library/StackCheckLib/Arm/StackCookieInterrupt.asm @@ -0,0 +1,25 @@ +;------------------------------------------------------------------------------ +; Arm/StackCookieInterrupt.asm +; +; Copyright (c) Microsoft Corporation. +; SPDX-License-Identifier: BSD-2-Clause-Patent +;------------------------------------------------------------------------------ + + EXPORT TriggerStackCookieInterrupt + + AREA |.text|, CODE, READONLY + +;------------------------------------------------------------------------------ +; Calls an interrupt using the vector specified by PcdStackCookieExceptionVector +; +; VOID +; TriggerStackCookieInterrupt ( +; VOID +; ); +;------------------------------------------------------------------------------ +TriggerStackCookieInterrupt PROC + SWI FixedPcdGet8 (PcdStackCookieExceptionVector) + BX LR +TriggerStackCookieInterrupt ENDP + + END diff --git a/MdePkg/Library/StackCheckLib/IA32/CheckCookieMsvc.nasm b/MdePkg/Library/StackCheckLib/IA32/CheckCookieMsvc.nasm new file mode 100644 index 0000000000..a52ee90ad9 --- /dev/null +++ b/MdePkg/Library/StackCheckLib/IA32/CheckCookieMsvc.nasm @@ -0,0 +1,43 @@ +;------------------------------------------------------------------------------ +; IA32/CheckCookieMsvc.nasm +; +; Copyright (c) Microsoft Corporation. +; SPDX-License-Identifier: BSD-2-Clause-Patent +;------------------------------------------------------------------------------ + + DEFAULT REL + SECTION .text + +extern ASM_PFX(StackCheckFailure) +extern ASM_PFX(__security_cookie) +extern ASM_PFX(CpuDeadLoop) + +; Called when a buffer check fails. This functionality is dependent on MSVC +; C runtime libraries and so is unsupported in UEFI. +global ASM_PFX(__report_rangecheckfailure) +ASM_PFX(__report_rangecheckfailure): + jmp ASM_PFX(CpuDeadLoop) + ret + +; The GS handler is for checking the stack cookie during SEH or +; EH exceptions and is unsupported in UEFI. +global ASM_PFX(__GSHandlerCheck) +ASM_PFX(__GSHandlerCheck): + jmp ASM_PFX(CpuDeadLoop) + ret + +;------------------------------------------------------------------------------ +; Checks the stack cookie value against __security_cookie and calls the +; stack cookie failure handler if there is a mismatch. +; +; VOID +; EFIAPI +; __security_check_cookie ( +; IN UINTN CheckValue +; ); +;------------------------------------------------------------------------------ +global @__security_check_cookie@4 +@__security_check_cookie@4: + cmp ecx, [ASM_PFX(__security_cookie)] + jne ASM_PFX(StackCheckFailure) + ret diff --git a/MdePkg/Library/StackCheckLib/IA32/StackCookieInterrupt.nasm b/MdePkg/Library/StackCheckLib/IA32/StackCookieInterrupt.nasm new file mode 100644 index 0000000000..83a686ddb3 --- /dev/null +++ b/MdePkg/Library/StackCheckLib/IA32/StackCookieInterrupt.nasm @@ -0,0 +1,23 @@ +;------------------------------------------------------------------------------ +; IA32/StackCookieInterrupt.nasm +; +; Copyright (c) Microsoft Corporation. +; SPDX-License-Identifier: BSD-2-Clause-Patent +;------------------------------------------------------------------------------ + + DEFAULT REL + SECTION .text + +;------------------------------------------------------------------------------ +; Checks the stack cookie value against __security_cookie and calls the +; stack cookie failure handler if there is a mismatch. +; +; VOID +; TriggerStackCookieInterrupt ( +; VOID +; ); +;------------------------------------------------------------------------------ +global ASM_PFX(TriggerStackCookieInterrupt) +ASM_PFX(TriggerStackCookieInterrupt): + int FixedPcdGet8 (PcdStackCookieExceptionVector) + ret diff --git a/MdePkg/Library/StackCheckLib/Readme.md b/MdePkg/Library/StackCheckLib/Readme.md new file mode 100644 index 0000000000..636cd047f0 --- /dev/null +++ b/MdePkg/Library/StackCheckLib/Readme.md @@ -0,0 +1,126 @@ +# StackCheckLib + +## Table of Contents + +- [StackCheckLib](#stackchecklib) + - [Table of Contents](#table-of-contents) + - [Introduction and Library Instances](#introduction-and-library-instances) + - [StackCheckLibStaticInit](#stackchecklibstaticinit) + - [StackCheckLibDynamicInit](#stackchecklibdynamicinit) + - [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, 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. + +### StackCheckLibStaticInit + +`StackCheckLibStaticInit` is an instance of `StackCheckLib` which does not update the +stack cookie value for the module at runtime. It's always preferable to use +`StackCheckLibDynamicInit` 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 Cache-as-RAM (CAR) phase of the boot process. 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. + +### StackCheckLibDynamicInit + +This section is future work. The below is the proposed instance. + +`StackCheckLibDynamicInit` is an instance of `StackCheckLib` 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 by calling `GetRandomNumber32()` or `GetRandomNumber64()` to generate a random +value via the platform's random number generator protocol. If the random number generator +returns an error, then the value will still have the build-time randomized value to fall +back on. + +### 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. + +## 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 `___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` instances linked to satisfy +these references. So every module doesn't need to add `StackCheckLib` to the LibraryClasses +section of the INF file, `StackCheckLib` instances should be linked as NULL in the platform +DSC fies. The only exception to this is 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`. + +SEC and PEI_CORE modules should always use `StackCheckLibNull` and pre-memory modules +should use `StackCheckLibStaticInit`. All other modules should use `StackCheckLibDynamicInit`. +Below is an **example** of how to link the `StackCheckLib` instances in the platform DSC file +but it may need customization based on the platform's requirements: + +```text +[LibraryClasses.common.SEC, LibraryClasses.common.PEI_CORE] + NULL|MdePkg/Library/StackCheckLibNull/StackCheckLibNull.inf + +[LibraryClasses.common.PEIM] + NULL|MdePkg/Library/StackCheckLib/StackCheckLibStaticInit.inf + +[LibraryClasses.common.MM_CORE_STANDALONE, LibraryClasses.common.MM_STANDALONE, LibraryClasses.common.DXE_CORE, LibraryClasses.common.SMM_CORE, LibraryClasses.common.DXE_SMM_DRIVER, LibraryClasses.common.DXE_DRIVER, LibraryClasses.common.DXE_RUNTIME_DRIVER, LibraryClasses.common.DXE_SAL_DRIVER, LibraryClasses.common.UEFI_DRIVER, LibraryClasses.common.UEFI_APPLICATION] + NULL|MdePkg/Library/StackCheckLib/StackCheckLibDynamicInit.inf +``` diff --git a/MdePkg/Library/StackCheckLib/StackCheckLibCommonGcc.c b/MdePkg/Library/StackCheckLib/StackCheckLibCommonGcc.c new file mode 100644 index 0000000000..4146012b90 --- /dev/null +++ b/MdePkg/Library/StackCheckLib/StackCheckLibCommonGcc.c @@ -0,0 +1,38 @@ +/** @file + Provides the required functionality for handling stack + cookie check failures in GCC. + + Copyright (c) Microsoft Corporation. + SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include + +#include +#include +#include + +/** + Triggers an interrupt using the vector specified by PcdStackCookieExceptionVector +**/ +VOID +TriggerStackCookieInterrupt ( + VOID + ); + +VOID *__stack_chk_guard = (VOID *)(UINTN)STACK_COOKIE_VALUE; + +/** + This function gets called when a gcc/clang generated stack cookie fails. This implementation calls into a platform + failure hook lib and then triggers the stack cookie interrupt. + +**/ +VOID +__stack_chk_fail ( + VOID + ) +{ + DEBUG ((DEBUG_ERROR, "Stack cookie check failed at address 0x%llx!\n", RETURN_ADDRESS (0))); + StackCheckFailureHook (RETURN_ADDRESS (0)); + TriggerStackCookieInterrupt (); +} diff --git a/MdePkg/Library/StackCheckLib/StackCheckLibCommonMsvc.c b/MdePkg/Library/StackCheckLib/StackCheckLibCommonMsvc.c new file mode 100644 index 0000000000..406b2d0116 --- /dev/null +++ b/MdePkg/Library/StackCheckLib/StackCheckLibCommonMsvc.c @@ -0,0 +1,40 @@ +/** @file + Provides the required functionality for handling stack + cookie check failures for MSVC. + + Copyright (c) Microsoft Corporation. + SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include + +#include +#include +#include + +/** + Triggers an interrupt using the vector specified by PcdStackCookieExceptionVector +**/ +VOID +TriggerStackCookieInterrupt ( + VOID + ); + +VOID *__security_cookie = (VOID *)(UINTN)STACK_COOKIE_VALUE; + +/** + This function gets called when an MSVC generated stack cookie fails. This implementation calls into a platform + failure hook lib and then triggers the stack cookie interrupt. + + @param[in] ActualCookieValue The value that was written onto the stack, corrupting the stack cookie. + +**/ +VOID +StackCheckFailure ( + VOID *ActualCookieValue + ) +{ + DEBUG ((DEBUG_ERROR, "Stack cookie check failed at address 0x%llx!\n", RETURN_ADDRESS (0))); + StackCheckFailureHook (RETURN_ADDRESS (0)); + TriggerStackCookieInterrupt (); +} diff --git a/MdePkg/Library/StackCheckLib/StackCheckLibStaticInit.inf b/MdePkg/Library/StackCheckLib/StackCheckLibStaticInit.inf new file mode 100644 index 0000000000..ce8bc11f2b --- /dev/null +++ b/MdePkg/Library/StackCheckLib/StackCheckLibStaticInit.inf @@ -0,0 +1,58 @@ +## @file +# Provides the required functionality for checking the stack cookie. +# +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: BSD-2-Clause-Patent +## + +[Defines] + INF_VERSION = 1.29 + BASE_NAME = StackCheckLibStaticInit + FILE_GUID = 2b24dc50-e33d-4c9f-8b62-e826f06e483f + MODULE_TYPE = BASE + VERSION_STRING = 1.0 + LIBRARY_CLASS = NULL + +[Sources] + StackCheckLibCommonMsvc.c | MSFT + StackCheckLibCommonGcc.c | GCC + +[Sources.IA32] + IA32/CheckCookieMsvc.nasm | MSFT + +[Sources.X64] + X64/CheckCookieMsvc.nasm | MSFT + +[Sources.IA32, Sources.X64] + IA32/StackCookieInterrupt.nasm + +[Sources.ARM] + Arm/StackCookieInterrupt.S |GCC + Arm/StackCookieInterrupt.asm |MSFT + +[Sources.AARCH64] + AArch64/StackCookieInterrupt.S |GCC + AArch64/StackCookieInterrupt.asm |MSFT + +[Packages] + MdePkg/MdePkg.dec + +[LibraryClasses] + StackCheckFailureHookLib + BaseLib + DebugLib + +[FixedPcd] + gEfiMdePkgTokenSpaceGuid.PcdStackCookieExceptionVector + +[BuildOptions] + # We cannot build the MSVC version with /GL (whole program optimization) because we run into linker error + # LNK1237, which is a failure to link against a symbol from a library compiled with /GL. The whole program + # optimization tries to do away with references to this symbol. The solution is to not compile the stack + # check libs with /GL + MSFT:*_*_*_CC_FLAGS = /GL- + + # We cannot build the GCC version with LTO (link time optimization) because we run into linker errors where + # the stack cookie variable has been optimized away, as it looks to GCC like the variable is not used, because + # the compiler inserts the usage. + GCC:*_*_*_CC_FLAGS = -fno-lto diff --git a/MdePkg/Library/StackCheckLib/X64/CheckCookieMsvc.nasm b/MdePkg/Library/StackCheckLib/X64/CheckCookieMsvc.nasm new file mode 100644 index 0000000000..ebc4f75712 --- /dev/null +++ b/MdePkg/Library/StackCheckLib/X64/CheckCookieMsvc.nasm @@ -0,0 +1,43 @@ +;------------------------------------------------------------------------------ +; X64/CheckCookieMsvc.nasm +; +; Copyright (c) Microsoft Corporation. +; SPDX-License-Identifier: BSD-2-Clause-Patent +;------------------------------------------------------------------------------ + + DEFAULT REL + SECTION .text + +extern ASM_PFX(StackCheckFailure) +extern ASM_PFX(__security_cookie) +extern ASM_PFX(CpuDeadLoop) + +; Called when a buffer check fails. This functionality is dependent on MSVC +; C runtime libraries and so is unsupported in UEFI. +global ASM_PFX(__report_rangecheckfailure) +ASM_PFX(__report_rangecheckfailure): + jmp ASM_PFX(CpuDeadLoop) + ret + +; The GS handler is for checking the stack cookie during SEH or +; EH exceptions and is unsupported in UEFI. +global ASM_PFX(__GSHandlerCheck) +ASM_PFX(__GSHandlerCheck): + jmp ASM_PFX(CpuDeadLoop) + ret + +;------------------------------------------------------------------------------ +; Checks the stack cookie value against __security_cookie and calls the +; stack cookie failure handler if there is a mismatch. +; +; VOID +; EFIAPI +; __security_check_cookie ( +; IN UINTN CheckValue +; ); +;------------------------------------------------------------------------------ +global ASM_PFX(__security_check_cookie) +ASM_PFX(__security_check_cookie): + cmp rcx, [ASM_PFX(__security_cookie)] + jne ASM_PFX(StackCheckFailure) + ret diff --git a/MdePkg/MdePkg.dsc b/MdePkg/MdePkg.dsc index f410a89a00..92809e4a75 100644 --- a/MdePkg/MdePkg.dsc +++ b/MdePkg/MdePkg.dsc @@ -141,6 +141,7 @@ MdePkg/Library/StackCheckFailureHookLibNull/StackCheckFailureHookLibNull.inf MdePkg/Library/StackCheckLibNull/StackCheckLibNull.inf + MdePkg/Library/StackCheckLib/StackCheckLibStaticInit.inf [Components.IA32, Components.X64, Components.ARM, Components.AARCH64] #