AD3023: ProperLoadSegments
Summary
| Property |
Value |
| ID |
AD3023 |
| Name |
ProperLoadSegments |
| Category |
Correctness |
| Severity |
Error |
| Applies to |
ELF (Linux/Unix) |
Description
This rule checks that ELF LOAD segments have proper permission flags and do not violate the W^X (Write XOR Execute) principle. Segments that are both writable and executable represent a significant security risk.
Why This Matters
W^X (Write XOR Execute) is a fundamental security principle: memory should be writable OR executable, never both. Violating this through improper LOAD segments creates trivially exploitable conditions.
The W^X Principle
Secure memory layout:
.text R-X (Code: run but don't modify)
.rodata R-- (Constants: read only)
.data RW- (Variables: modify but don't run)
.bss RW- (Uninitialized: modify but don't run)
Insecure layout:
.text RWX (Code: can inject and execute)
↑ Attacker writes shellcode to .text, executes it
Exploitation Impact
| Segment Permissions |
Exploitation |
| R-X (code) |
Must use ROP/JOP |
| RW- (data) |
Cannot execute |
| RWX (writable+exec) |
Direct shellcode injection |
Why RWX Is Trivially Exploitable
With RWX segment:
1. Attacker finds write primitive
2. Writes shellcode to RWX region
3. Jumps to shellcode
4. Game over
Without RWX:
1. Attacker finds write primitive
2. Cannot write executable code
3. Must use ROP (complex chains)
4. Eventually call mprotect/VirtualProtect
5. Then execute payload
RWX removes many exploitation hurdles.
Common Causes of RWX Segments
| Cause |
Why It Happens |
Fix |
| Nested functions |
GCC trampolines on stack |
Avoid or -fno-trampolines |
| JIT compilers |
Dynamic code generation |
Use mprotect properly |
| Old linker scripts |
Legacy defaults |
Update scripts |
| Self-modifying code |
Intentional but dangerous |
Redesign |
Proper JIT Design
Bad JIT:
1. Allocate RWX memory
2. Write code
3. Execute
4. RWX forever (attackable)
Good JIT:
1. Allocate RW memory
2. Write code
3. mprotect → R-X
4. Execute
5. To modify: mprotect → RW, write, mprotect → R-X
Never simultaneously W and X.
Hardened System Enforcement
| System |
RWX Handling |
| SELinux (strict) |
Blocks RWX mmap |
| PaX (grsecurity) |
MPROTECT prevents W→X |
| OpenBSD |
W^X enforced |
| iOS |
No RWX for third-party apps |
Segment Permission Best Practices
| Segment |
Correct Permissions |
| .text |
R-X |
| .rodata |
R-- |
| .data |
RW- |
| .bss |
RW- |
| .plt |
R-X |
| .got |
R-- (with Full RELRO) |
- W^X violation: RWX segments allow code injection attacks
- Code integrity: Executable code should never be writable
- Data protection: Writable data should never be executable
- Exploit mitigation: Proper segment permissions are fundamental to security
What This Rule Checks
- No RWX segments: No LOAD segment should be Read+Write+Execute
- Code segments: Should be R-X (readable, executable, not writable)
- Data segments: Should be RW- (readable, writable, not executable)
- Read-only data: Should be R-- (readable only)
How to Fix
Ensure proper compiler/linker flags
# Standard compilation should produce proper segments
gcc -o myapp myapp.c
# Explicitly ensure no executable stack
gcc -Wl,-z,noexecstack -o myapp myapp.c
# Check for RWX segments
readelf -l myapp | grep -E "RWE|RWX"
Common causes of RWX segments
- Nested functions (GCC trampolines): Avoid nested functions
- JIT compilation: Use proper mprotect() calls
- Self-modifying code: Redesign to avoid
- Old linker scripts: Update to modern practices
Fix nested functions
// Bad: Nested function requires executable stack
void outer() {
void inner() { /* ... */ }
inner();
}
// Good: Use static function or function pointer
static void inner() { /* ... */ }
void outer() {
inner();
}
Example
Fail: Binary has RWX segment
LOAD 0x001000 0x00401000 0x00401000 0x1000 0x1000 RWE 0x1000
Pass: Proper segment permissions
LOAD 0x000000 0x00400000 0x00400000 0x1000 0x1000 R 0x1000
LOAD 0x001000 0x00401000 0x00401000 0x2000 0x2000 R E 0x1000
LOAD 0x003000 0x00403000 0x00403000 0x1000 0x1000 RW 0x1000
See Also