Skip to content

AD3002: DoNotMarkStackAsExecutable

Summary

Property Value
ID AD3002
Name DoNotMarkStackAsExecutable
Category Security
Severity Error
Applies to ELF (Linux/Unix)

Description

This checks if a binary has an executable stack. An executable stack allows attackers to redirect code flow into stack memory, which is an easy place for an attacker to store shellcode.

Ensure you are compiling with -z noexecstack to mark the stack as non-executable.

How It Works

The rule examines the PT_GNU_STACK program header in the ELF file. This header specifies stack permissions:

  • No PT_GNU_STACK: Stack is executable (legacy default)
  • PT_GNU_STACK with PF_X: Stack explicitly marked executable
  • PT_GNU_STACK without PF_X: Stack is non-executable (correct)

Why This Matters

An executable stack is the oldest and most direct exploitation primitive. It allows attackers to place their code on the stack and jump directly to itโ€”the simplest possible path to code execution.

Historical Context

Before NX/DEP, the stack was executable by default:

1988: Morris Worm uses stack-based shellcode
      ...
1996: Aleph One's "Smashing the Stack for Fun and Profit"
      (Classic shellcode-on-stack technique)
      ...
2004: AMD64 introduces NX bit
2005: Intel EM64T adds XD bit
Today: Executable stack is a critical vulnerability

The Classic Stack Smash

With executable stack, exploitation is trivial:

void vulnerable(char *input) {
    char buffer[64];
    strcpy(buffer, input);  // Overflow!
}

// Attack payload:
// [SHELLCODE]  [PADDING]  [RETURN_ADDR โ†’ buffer]
//     โ†“
// Jump to stack, execute shellcode

Without executable stack, this direct approach fails.

Attack Difficulty Comparison

Stack Permission Exploitation Complexity
Executable Simple shellcode on stack
Non-executable Must use ROP/JOP techniques
Non-executable + CFG ROP becomes harder
Non-executable + CET ROP largely defeated

Hardware Enforcement

Non-executable stack uses CPU features:

Platform Feature Bit in Page Table
AMD64 NX (No-Execute) Bit 63
Intel XD (Execute Disable) Bit 63
ARM XN (Execute Never) Varies by version

Hardware enforcement means: - No performance overhead - Cannot be bypassed by application code - Enforced on every instruction fetch

Common Causes of Executable Stack

Cause Solution
Nested functions (GCC extension) Avoid or use -ftrampolines
Assembly without .note.GNU-stack Add proper section
Old compiler/linker defaults Update toolchain
Deliberate for JIT Use mprotect carefully

ELF Stack Permission Indicator

PT_GNU_STACK segment flags:
  PF_R | PF_W        = Non-executable (correct)
  PF_R | PF_W | PF_X = Executable (vulnerable)
  Missing segment   = Executable (legacy default)

Defense in Depth Role

Non-executable stack is foundational:

First line:  Non-executable stack (blocks direct shellcode)
Second line: ASLR (randomizes addresses)
Third line:  Stack canaries (detects overflow)
Fourth line: CFG/CET (blocks control flow hijacking)
  • Shellcode prevention: Can't execute code placed on stack
  • Buffer overflow protection: Classic exploits become harder
  • NX/DEP equivalent: Hardware-enforced protection
  • Defense in depth: Complements other mitigations

Resolution

GCC/Clang

Add the noexecstack linker flag:

gcc -Wl,-z,noexecstack source.c -o binary

For Assembly Files

Assembly files can mark stack as executable. Fix with:

# Compile assembly with noexecstack
gcc -Wa,--noexecstack -c asm.S -o asm.o

Or add to assembly file:

.section .note.GNU-stack,"",@progbits

CMake

add_link_options(-Wl,-z,noexecstack)

# For specific target
target_link_options(myapp PRIVATE -Wl,-z,noexecstack)

Makefile

LDFLAGS += -Wl,-z,noexecstack

When to Suppress

This rule should rarely be suppressed. Exceptions might include:

  • Trampoline code: Some nested functions need executable stack
  • JIT compilers: May manage their own executable memory
  • Legacy compatibility: Very old code patterns

Caveats

  • GCC nested functions may require executable stack (use alternatives)
  • Some assembly files implicitly mark stack executable
  • Legacy binaries may have this issue

Common Causes

  1. Assembly without .note.GNU-stack
  2. GCC trampolines for nested functions
  3. Old object files linked in
  4. Missing -z noexecstack flag

Fixing Assembly Files

Add this section to mark stack as non-executable:

# AT&T syntax
.section .note.GNU-stack,"",@progbits

# Intel syntax
section .note.GNU-stack noalloc noexec nowrite progbits

References