AD5005: DoNotAllowExecutableHeap¶
Summary¶
| Property | Value |
|---|---|
| ID | AD5005 |
| Name | DoNotAllowExecutableHeap |
| Category | Security |
| Severity | Warning |
| Applies to | Mach-O executables (macOS/iOS) |
Description¶
The MH_NO_HEAP_EXECUTION flag prevents code execution from heap memory. Attackers often store shellcode in the heap and redirect execution there. Disabling heap execution blocks this common attack technique.
How It Works¶
The rule checks if the MH_NO_HEAP_EXECUTION (0x1000000) flag is set in the Mach-O header. This flag instructs the dynamic linker to mark heap memory as non-executable.
Why This Matters¶
Heap-based code execution is a primary exploitation technique. By marking heap memory non-executable, macOS forces attackers to use more complex techniques like ROP, significantly raising the difficulty of exploitation.
The Heap Execution Attack¶
Attackers often use the heap for shellcode:
// Heap spray or controlled allocation
void *heap_mem = malloc(4096);
memcpy(heap_mem, shellcode, sizeof(shellcode));
// ... memory corruption redirects execution ...
// Without MH_NO_HEAP_EXECUTION:
// CPU executes shellcode from heap
// With MH_NO_HEAP_EXECUTION:
// CPU raises exception, process crashes safely
Why Heap Is Attractive to Attackers¶
| Property | Attack Benefit |
|---|---|
| Large allocations | Plenty of space for payload |
| Attacker-controlled | Can influence heap layout |
| Long-lived | Payload persists |
| Sprays possible | Increase hit probability |
Protection Comparison¶
| Memory Region | Default Protection | Flag |
|---|---|---|
| Stack | Non-executable | Default |
| Heap | Executable (legacy) | MH_NO_HEAP_EXECUTION needed |
| Code | Executable | Normal |
| Data | Non-executable | Default |
Attack Difficulty¶
| Heap Permissions | Exploitation |
|---|---|
| Executable | Direct shellcode |
| Non-executable | Must use ROP/JOP |
JIT and Heap Execution¶
Some legitimate uses need executable heap:
| Use Case | Approach |
|---|---|
| JIT compilers | Use mmap with PROT_EXEC |
| Game engines | Careful memory management |
| Dynamic code gen | Separate executable pages |
Most applications don't need executable heap.
macOS Implementation¶
With MH_NO_HEAP_EXECUTION:
- Dynamic linker sets up heap as RW only
- malloc() returns non-executable pages
- Attempt to execute = EXC_BAD_ACCESS
Without MH_NO_HEAP_EXECUTION:
- Heap may be RWX (legacy behavior)
- Shellcode can execute directly
Hardened Runtime Interaction¶
macOS Hardened Runtime includes heap protection:
| Protection | Hardened Runtime | Manual Flag |
|---|---|---|
| Non-exec heap | Included | MH_NO_HEAP_EXECUTION |
| Library validation | Included | Separate |
| Debug protection | Included | Separate |
- Blocks shellcode injection: Prevents execution of attacker-controlled heap data
- Defense in depth: Complements stack non-execution and ASLR
- Exploit mitigation: Makes heap-based exploits significantly harder
- Standard practice: Modern macOS applications should have this enabled
Performance Considerations¶
Non-executable heap has zero runtime overhead:
| Aspect | Impact |
|---|---|
| Runtime overhead | None |
| malloc() performance | Identical |
| Memory usage | Same |
| Startup time | Negligible |
Why there's no overhead: - Non-executable is a page table permission, not a runtime check - The CPU's memory management unit handles permissions in hardware - No software instrumentation or validation required
JIT compiler note:
Applications that need to execute generated code should use mmap() with explicit PROT_EXEC permission for specific pages, not executable heap globally.
There is no performance reason to allow executable heap.
Resolution¶
Xcode¶
- Select your target in Xcode
- Go to Build Settings → Linking
- Add
-Wl,-no_heap_executionto Other Linker Flags
Command Line¶
CMake¶
Makefile¶
When to Suppress¶
This rule may be suppressed for:
- JIT compilers: Applications that generate code at runtime (JavaScript engines, etc.)
- Dynamic code generation: Legitimate use of executable heap memory
- Legacy applications: Older code that relies on executable heap
Important Notes¶
- This check only applies to executables, not dynamic libraries
- iOS enforces this by default through code signing
- On modern macOS with SIP, many applications have this enforced