AD5012: ValidateSegmentPermissions¶
Summary¶
| Property | Value |
|---|---|
| ID | AD5012 |
| Name | ValidateSegmentPermissions |
| Category | Security |
| Severity | Error |
| Applies to | Mach-O (macOS/iOS) |
Description¶
Segments should follow the W^X (Write XOR Execute) principle: memory should either be writable or executable, but not both. The __TEXT segment should be read-execute only (not writable), and __DATA segments should be read-write only (not executable). Violations allow attackers to inject and execute arbitrary code.
How It Works¶
The rule examines segment memory protections:
- __TEXT is read-execute only: Code segment should never be writable
- __DATA is read-write only: Data segment should never be executable
- No W^X violations: No segment should have both write AND execute permissions
Why This Matters¶
W^X (Write XOR Execute) is the most fundamental memory protection. Violating it by having writable+executable segments gives attackers a trivial path to code execution—write shellcode, execute it.
The Attack Made Trivial¶
With proper W^X:
Attacker: "I have a write primitive"
Result: Can write to DATA, but can't execute it
Can't write to TEXT (it's read-only)
Must use complex ROP/JOP
With W^X violation (RWX segment):
Attacker: "I have a write primitive"
Result: Write shellcode to RWX segment
Jump there
Done. No ROP needed.
Segment Permission Best Practices¶
| Segment | Correct Permissions | Wrong Permissions |
|---|---|---|
| __TEXT | R-X | RWX (writable code!) |
| __DATA | RW- | RWX (executable data!) |
| __DATA_CONST | R-- | RW- (modifiable constants!) |
| __LINKEDIT | R-- | RW- (modifiable metadata!) |
Attack Scenarios¶
Writable __TEXT:
1. Attacker exploits vulnerability
2. Gets write primitive
3. Writes directly to __TEXT segment
4. Replaces legitimate code with malicious code
5. Legitimate code paths now execute malware
Executable __DATA:
1. Attacker exploits vulnerability
2. Writes shellcode to __DATA
3. Hijacks control flow to __DATA
4. Shellcode executes
Hardware Enforcement¶
| Apple Platform | W^X Enforcement |
|---|---|
| iOS | Kernel enforces strictly |
| macOS Intel | CPU enforces via page tables |
| Apple Silicon | Hardware enforces, PAC adds more |
Why Violations Might Occur¶
| Cause | Better Approach |
|---|---|
| JIT compilation | Use proper mprotect lifecycle |
| Self-modifying code | Redesign architecture |
| Legacy toolchain | Update tools |
| Bad linker script | Fix segment definitions |
Proper JIT Pattern¶
Secure JIT:
1. Allocate RW memory
2. Write generated code
3. mprotect() to R-X
4. Execute
5. To modify: mprotect() to RW, write, mprotect() to R-X
Never simultaneously W and X.
Apple's Hardened Runtime¶
Hardened Runtime enforces proper permissions:
With Hardened Runtime:
- JIT capability requires entitlement
- Memory pages can't be W+X simultaneously
- Runtime checks enforce W^X
W^X Principle¶
The W^X (Write XOR Execute) principle is a fundamental security protection:
Allowed:
Read + Execute (code) ✓
Read + Write (data) ✓
Read only ✓
Forbidden:
Write + Execute ✗ ← Allows code injection
Attack Scenarios¶
Writable __TEXT:
// Attacker can modify code in memory
void* code_page = get_text_section();
memcpy(code_page, shellcode, sizeof(shellcode));
// Original code replaced with malicious code
Executable __DATA:
// Attacker stores shellcode in data section
char buffer[1024];
strcpy(buffer, shellcode); // Buffer overflow
jump_to(buffer); // Execute shellcode from data
Resolution¶
Default Behavior¶
Modern linkers create proper segment permissions by default. If you're seeing violations, check for:
- Custom linker scripts that modify segment permissions
- Inline assembly that requires writable code
- Legacy code that self-modifies
Xcode¶
Ensure no custom linker flags modify segment permissions:
- Select your target
- Go to Build Settings → Linking
- Check Other Linker Flags for segment permission overrides
Linker Flags to Avoid¶
# Do NOT use these flags:
-segprot __TEXT rwx rwx # Writable text
-segprot __DATA rwx rwx # Executable data
-allow_stack_execute # Executable stack
For JIT Compilers¶
If you need dynamic code generation (JIT):
// Allocate memory with write permission
void* code = mmap(NULL, size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
// Generate code
generate_code(code);
// Change to read-execute (remove write)
mprotect(code, size, PROT_READ | PROT_EXEC);
// Execute the code
((void(*)())code)();
When to Suppress¶
This rule may be suppressed for:
- JIT compilers: JavaScript engines, bytecode interpreters
- Dynamic code generation: Runtime code patching
- Debuggers: May need to modify code for breakpoints
Platform Notes¶
- iOS: Strictly enforces W^X; JIT is only allowed for Safari
- macOS with Hardened Runtime: Enforces W^X
- macOS without Hardened Runtime: May allow violations