Application build-time techniques
Application binaries are a primary target for exploitation. Memory corruption vulnerabilities in C and C++ code enable attackers to hijack control flow, execute arbitrary code, and bypass security boundaries. Hardening the binary at compile time embeds protections directly into the executable, making exploitation significantly more difficult even when vulnerabilities exist in the code.1
ARMORRED applies systematic binary hardening to all compiled components using compiler flags and toolchain features. These protections are verified with checksec after build to ensure they are correctly enabled.
RELRO (Relocation Read-Only)
RELRO prevents attackers from overwriting the Global Offset Table (GOT) and Procedure Linkage Table (PLT), which are used by dynamically linked binaries to resolve function addresses. Without RELRO, attackers who achieve arbitrary write can redirect library calls to malicious code.
Partial RELRO
Partial RELRO marks the GOT as read-only immediately after dynamic linking completes but before main() executes. This is the default behavior with modern GCC and provides basic protection.
Full RELRO
Full RELRO resolves all dynamic symbols at program startup and marks the entire GOT read-only before main() executes. This eliminates GOT overwrite attacks entirely but adds startup latency due to eager symbol resolution.
ARMORRED enables Full RELRO with the linker flags -Wl,-z,relro,-z,now:
Full RELRO protects against exploitation techniques such as:
Stack Canaries
Stack buffer overflow vulnerabilities (CWE-121)4 allow attackers to overwrite return addresses on the stack, redirecting execution when a function returns. Stack canaries place a random value on the stack between local variables and the saved return address. Before returning, the function verifies the canary is intact. If an overflow corrupts the canary, the program terminates before the attacker gains control.
GCC implements stack canaries with the -fstack-protector family of flags:
| Flag | Protection Level |
|---|---|
-fstack-protector | Protects functions with vulnerable buffers (8+ bytes) |
-fstack-protector-strong | Protects functions with any local array or address-taken variables |
-fstack-protector-all | Protects all functions (high performance cost) |
ARMORRED uses -fstack-protector-strong to balance security and performance:
Stack canaries are effective against classic stack overflow exploits but do not protect against heap overflows, format string vulnerabilities, or attacks that leak the canary value.
NX (No eXecute)
The NX bit marks memory regions as non-executable, preventing attackers from executing injected shellcode on the stack or heap. Without NX, stack overflow exploits can place machine code in a buffer and redirect execution to it. With NX enabled, attempting to execute code from these regions triggers a segmentation fault.
NX is implemented through hardware support (AMD NX bit, Intel XD bit) and enabled by the compiler by default on modern systems. The GNU linker marks the stack as non-executable using the -z noexecstack flag.
NX forces attackers to use return-oriented programming (ROP) or jump-oriented programming (JOP) instead of direct code injection. These techniques are significantly more complex and can be mitigated by additional protections such as CFI and SafeStack.
PIE (Position Independent Executable) and ASLR
Address Space Layout Randomization (ASLR) randomizes the base addresses of the stack, heap, and libraries each time a program executes. This makes it difficult for attackers to predict the location of code or data, breaking exploits that rely on hardcoded addresses.
ASLR only works effectively if the executable itself can be loaded at a random address. This requires compiling the binary as a Position Independent Executable (PIE) using -fPIE -pie:
With PIE enabled, the entire address space is randomized:
| Memory Region | Randomized Without PIE | Randomized With PIE |
|---|---|---|
| Stack | Yes | Yes |
| Heap | Yes | Yes |
| Libraries | Yes | Yes |
| Executable code | No | Yes |
| Executable data | No | Yes |
ASLR is enabled at the kernel level. On Linux, check with:
A value of 2 indicates full ASLR is enabled. PIE combined with ASLR makes exploit development significantly more difficult by eliminating reliable addresses for ROP gadgets and data structures.
FORTIFY_SOURCE
-D_FORTIFY_SOURCE replaces dangerous libc functions with bounds-checked variants that validate buffer sizes at runtime. When enabled, functions such as memcpy, strcpy, sprintf, and read are replaced with __memcpy_chk, __strcpy_chk, __sprintf_chk, and __read_chk.
These fortified functions validate that the destination buffer is large enough for the operation. If a buffer overflow is detected, the program aborts before memory corruption occurs.
FORTIFY_SOURCE requires optimization (-O1 or higher) because the compiler must determine buffer sizes at compile time. ARMORRED uses -D_FORTIFY_SOURCE=2 which enables additional checks:
ARMORRED achieves 50% fortification coverage in nginx. The unfortified functions are typically those where the compiler cannot statically determine buffer sizes. FORTIFY_SOURCE protects against:
- CWE-120: Buffer Copy without Checking Size of Input5
- CWE-126: Buffer Over-read6
- CWE-787: Out-of-bounds Write7
Comparison with Upstream
Upstream nginx 1.26 has lower FORTIFY_SOURCE coverage:
| Image | Fortified Functions | Fortifiable Functions | Coverage |
|---|---|---|---|
| Upstream nginx:1.26.3 | 3 | 10 | 30% |
| ARMORRED hardened | 7 | 14 | 50% |
| ARMORRED locked | 7 | 14 | 50% |
The increased coverage in ARMORRED images comes from compiling with optimization and -D_FORTIFY_SOURCE=2 consistently across all components.
SafeStack
SafeStack is a Clang feature that separates the stack into two regions: a safe stack for return addresses and register spills, and an unsafe stack for everything else. This prevents stack buffer overflows in local variables from corrupting return addresses, even without stack canaries.
SafeStack is enabled with -fsanitize=safe-stack and requires the Clang compiler:
SafeStack provides protection even when stack canaries are bypassed through information disclosure attacks. It is enabled in both hardened and locked tiers.
SafeStack Coverage
| Image | SafeStack |
|---|---|
| Upstream nginx:1.26.3 | Disabled |
| ARMORRED hardened | Enabled |
| ARMORRED locked | Enabled |
Control Flow Integrity (CFI)
Control Flow Integrity validates that indirect function calls and returns target legitimate destinations according to the program's control flow graph. This prevents ROP attacks and vtable hijacking by ensuring that control flow transfers only occur to valid locations.
Clang implements CFI with -fsanitize=cfi, which requires Link-Time Optimization (-flto) to analyze the entire program's control flow. CFI adds runtime checks before indirect calls, terminating the program if a violation is detected.
CFI has a performance cost and can break compatibility with certain dynamic linking patterns. Whether CFI is enabled depends on the specific image and tier. In the current nginx 1.26 build, CFI is enabled in the locked tier:
CFI Coverage (nginx 1.26)
| Image | Control Flow Integrity |
|---|---|
| Upstream nginx:1.26.3 | Disabled |
| ARMORRED hardened | Disabled |
| ARMORRED locked | Enabled |
CFI protects against advanced exploitation techniques including:
- ROP (Return-Oriented Programming)
- JOP (Jump-Oriented Programming)
- Vtable hijacking in C++ code
- Function pointer corruption
Complete Hardening Comparison
The following table summarizes binary hardening features for nginx 1.26. These specific values are for this image and version; other images may differ:
| Protection | Upstream | ARMORRED Hardened | ARMORRED Locked |
|---|---|---|---|
| RELRO | Full | Full | Full |
| Stack Canary | Yes | Yes | Yes |
| NX | Yes | Yes | Yes |
| PIE | Yes | Yes | Yes |
| FORTIFY_SOURCE | 30% (3/10) | 50% (7/14) | 50% (7/14) |
| SafeStack | No | Yes | Yes |
| Control Flow Integrity | No | No | Yes |
Consult the Images section for the checksec analysis of each specific image.
Verifying Hardening Features
Users can verify hardening features in running containers using checksec:
Compare against the upstream image:
The presence of SafeStack and CFI in ARMORRED images demonstrates measurably stronger protection against memory corruption exploits.
References
Recommendation for Application Whitelisting - NIST SP 800-167. https://csrc.nist.gov/publications/detail/sp/800-167/final
CWE-123: Write-what-where Condition - MITRE. https://cwe.mitre.org/data/definitions/123.html
CWE-822: Untrusted Pointer Dereference - MITRE. https://cwe.mitre.org/data/definitions/822.html
CWE-121: Stack-based Buffer Overflow - MITRE. https://cwe.mitre.org/data/definitions/121.html
CWE-120: Buffer Copy without Checking Size of Input - MITRE. https://cwe.mitre.org/data/definitions/120.html
CWE-126: Buffer Over-read - MITRE. https://cwe.mitre.org/data/definitions/126.html
CWE-787: Out-of-bounds Write - MITRE. https://cwe.mitre.org/data/definitions/787.html