Skip to content

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

  1. Select your target in Xcode
  2. Go to Build Settings → Linking
  3. Add -Wl,-no_heap_execution to Other Linker Flags

Command Line

clang source.c -o binary -Wl,-no_heap_execution

CMake

target_link_options(myapp PRIVATE -Wl,-no_heap_execution)

Makefile

LDFLAGS += -Wl,-no_heap_execution

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
  • AD5002 - Stack non-execution check
  • AD5001 - ASLR/PIE check

References