Full Report
Stacking smashing protections, also known as stack canaries, is a memory corruption protection put on the stack. This is done by putting a special variable on the stack called the canary, that is random per process. If this value is different within the stack frame than the saved value when calling ret, then the program crashes. Compilers can implement this via added code; no extra architecture-level things are required. Stack canaries protect against contiguous buffer overflows on the stack very well. To defeat this, an indexed write primitive or a stack canary leak must be found. In AArch64, the protection does not detect/defend against overflows of dynamically sized variables like variable-length arrays (VLAs) using alloca() or user controlled increases of the stack frame in other ways. Why does this only happen in AArch64? GCC backend lays out the stack frame differently than on other architectures. Instead of saving the return address (LR register) at the top of the stack frame (highest address), it saves it near the bottom of the frame. This allows for local variables to not be a problem when overwriting vars. Hurray! This feels like a feature. If there is no LR to modify then who needs a canary? In practice, there's always another stack frame below with one. So, with a larger overflow, we can modify the LR register and other items on the stack. The picture for the stack frame in the GCC source code doesn't have a stack canary. Why is this? The stack canary is treated as a local variable that it adds manually to the frame at compile time. In the compiler, there is an assumption that locals will occupy a single space but this assumption doesn't hold true for AArch64. Dynamic allocations live at the very bottom of the stack frame, below the saved registers. This means that there is no intervening guard in this stack frame setup. The ending contains a super simple PoC that causes a crash but doesn't catch the overflow. Overall, a really strange yet impactful bug in the GCC compiler. An assumption was made about how all stack frames were generated for the canary, which broke this codes effectiveness. Neat!
Analysis Summary
# Vulnerability: GCC Stack Protector Bypass on AArch64 (ARM64)
## CVE Details
- **CVE ID:** CVE-2023-4039
- **CVSS Score:** Not explicitly scored in text, but categorized as a **Medium** severity mitigation bypass.
- **CWE:** CWE-693 (Protection Mechanism Failure) / CWE-121 (Stack-based Buffer Overflow)
## Affected Systems
- **Products:** GNU Compiler Collection (GCC)
- **Versions:** All versions of GCC supporting AArch64/ARM64 (prior to September 2023 patches).
- **Configurations:**
- Target architecture is **AArch64 (64-bit ARM)**.
- Code is compiled with stack smashing protection enabled (`-fstack-protector`, `-fstack-protector-strong`, or `-fstack-protector-all`).
- Code utilizes dynamic stack allocations such as **Variable-Length Arrays (VLAs)** or the `alloca()` function.
## Vulnerability Description
On the AArch64 target, the GCC backend implements an unconventional stack frame layout. Unlike other architectures that save the return address (Link Register/LR) at the top of the stack frame, AArch64 saves it near the bottom, below local variables.
The compiler incorrectly assumes the stack canary (guard) will protect all local variables. However, in AArch64, **dynamically-sized variables** are placed at the very bottom of the stack frame, below even the saved registers and the stack canary. Consequently, a contiguous buffer overflow originating from a VLA or `alloca()` buffer can overwrite the return address (LR) and other saved registers without ever touching or tripping the stack canary.
## Exploitation
- **Status:** PoC available.
- **Complexity:** Medium (Requires a secondary vulnerability—a stack buffer overflow via dynamic allocation).
- **Attack Vector:** Local (This is a compiler mitigation failure, typically leading to local privilege escalation or remote code execution depending on the vulnerable application).
## Impact
- **Confidentiality:** High (Potential for full system compromise if exploited).
- **Integrity:** High (Allows for control-flow hijacking).
- **Availability:** High (Can lead to application crashes or system instability).
## Remediation
### Patches
- A fix has been merged into the GCC master branch and backported to active release branches.
- Refer to the GCC mailing list (September 2023) and specific distribution updates (e.g., Debian, Ubuntu, RedHat) for the updated GCC package.
### Workarounds
- **Avoid Dynamic Allocations:** Replace Variable-Length Arrays (VLAs) and `alloca()` calls with fixed-size buffers or heap allocations (`malloc`/`free`).
- **Compiler Flags:** If patching is not immediately possible, use `-Wvla` to identify and eliminate VLAs in critical code.
## Detection
- **Indicators of Compromise:** Standard buffer overflow exploitation signs (unexpected crashes, EIP/RIP/LR control).
- **Detection Methods:**
- **Static Analysis:** Scan source code for `alloca()` and VLAs used in conjunction with user-controlled input.
- **Binary Analysis:** Examine AArch64 function prologs/epilogs to determine if dynamic allocations are positioned below the stack canary.
## References
- Arm Security Advisory: [https://developer.arm.com/Arm%20Security%20Center/GCC%20Stack%20Protector%20Vulnerability%20AArch64]
- Meta Red Team X Disclosure: [https://rtx.meta.security/exploitation/2023/09/12/CVE-2023-4039-GCC-stack-protector-vulnerability-ARM64.html]
- GCC Patch Archive: [https://gcc.gnu.org/pipermail/gcc-patches/2023-September/630054.html]
- GitHub Advisory: [https://github.com/metaredteam/external-disclosures/security/advisories/GHSA-x7ch-h5rf-w2mf]