Too Subtle to Notice: Investigating Executable Stack Issues in Linux Systems

Hengkai Ye (Pensday)

Network and Distributed System Security (NDSS) Symposium 2025 · Day 2 · Software Security: Vulnerability Detection

Overview

In this presentation, Hengkai Ye from Pensday delves into a surprising and persistent security vulnerability: the existence of executable stacks in modern Linux systems, despite decades of established defenses. The talk introduces a novel problem termed "badass" (bad assembly files), which highlights how seemingly innocuous omissions in assembly code can inadvertently re-enable code injection attack vectors. Ye’s investigation reveals that this issue is not confined to obscure legacy applications but affects widely used and open-source software, including security-critical tools.

Watch on YouTube · Slides

Key moments

  1. 0:00 Introduction: Investigating executable stack issues in Linux systems
  2. 0:40 Code injection attacks and the Write XOR Execute defense
  3. 2:00 Executable stack still found in modern popular programs
  4. 2:30 Main reason: Missing note GNU stack in assembly files
  5. 3:05 Simple example demonstrating the 'Badass' issue with assembly
  6. 6:05 Even security experts' tools suffer from the 'Badass' issue
  7. 6:50 Detailed enforcement analysis of Write XOR Execute mitigation

Too Subtle to Notice: Investigating Executable Stack Issues in Linux Systems

Speakers: Hengkai Ye (Pensday)

Conference: NDSS Symposium

YouTube: https://www.youtube.com/watch?v=Q91s02Mt_F0

Overview

In this presentation, Hengkai Ye from Pensday delves into a surprising and persistent security vulnerability: the existence of executable stacks in modern Linux systems, despite decades of established defenses. The talk introduces a novel problem termed "badass" (bad assembly files), which highlights how seemingly innocuous omissions in assembly code can inadvertently re-enable code injection attack vectors. Ye’s investigation reveals that this issue is not confined to obscure legacy applications but affects widely used and open-source software, including security-critical tools.

The significance of this research lies in challenging the prevailing belief that code injection attacks, particularly those leveraging executable stacks, are a relic of the past. Since the late 1980s and the rise of stack smashing techniques, robust mitigations like W^X (Write XOR Execute) have been widely adopted. This talk demonstrates that the enforcement of W^X is far more intricate than commonly perceived, involving a delicate chain of collaboration across compilers, assemblers, linkers, and the kernel. The "badass" issue exposes a critical blind spot in this chain, making it a significant concern for developers, security experts, and system architects striving for robust system security.

Background

▶ Watch: Introduction: Investigating executable stack issues in Linux systems (0:00)

The history of code injection attacks dates back to the late 1980s, with notable incidents like the 1988 Morris Worm impacting thousands of computers. The 1996 "Smashing The Stack For Fun And Profit" paper provided detailed technical insights into stack smashing, illustrating how an attacker could overflow a buffer on the stack, inject malicious code (often referred to as shellcode), and then overwrite the function's return address to point to the injected code. Upon function return, program execution would then jump directly to the attacker's shellcode, granting arbitrary code execution.

To counter these devastating attacks, a fundamental defense mechanism known as W^X (Write XOR Execute) was devised and widely deployed. The core principle of W^X is simple: a memory page can be either writable or executable, but never both simultaneously. This design uses a single bit in the page table entry to instruct the CPU on memory permissions. If an attacker injects code onto the stack, the stack memory, by design, should only be writable (for data) but not executable. Thus, even if shellcode is successfully placed on the stack, attempting to execute it would trigger a protection fault, effectively thwarting the attack. Because of its simplicity and low overhead, W^X became a standard, default mitigation on modern operating systems and hardware, leading to the widespread belief that code injection via executable stacks was a solved problem, no longer a significant threat.

Key Findings

▶ Watch: Executable stack still found in modern popular programs (2:00)

Hengkai Ye's research uncovers a surprising reality: executable stacks persist on modern Linux systems, even within popular applications and open-source projects thought to be secure. The investigation identified several prominent examples, including Electron, VS Code, DB, and ZeroTier One, all exhibiting executable stacks. This contradicts the fundamental assumption that W^X prevents such vulnerabilities by default.

The core discovery is termed the "badass" (bad assembly files) issue. This problem arises when an assembly file, included in a project, fails to include the .note.GNU-stack section definition statement. If this crucial directive is missing, the final compiled binary will link with an executable stack. The talk demonstrates this with a simple C code example: compiling and running the C code alone results in a non-executable stack. However, adding an empty assembly file that lacks the .note.GNU-stack directive to the compilation process instantly makes the stack executable. This highlights the subtlety of the issue, as even a functionally inert assembly file can introduce a critical security flaw.

A particularly striking finding is that this problem is not limited to "normal" developers overlooking security details. Ye's team investigated 21 open-source Reference Monitors (RMs), a class of security applications that often interact with assembly code for purposes like Control Flow Integrity (CFI) or software-based fault isolation. These RMs included 9 binary writers, 8 CFI solutions, and 4 isolation/debloating tools. Astonishingly, 11 of these 21 security applications (5 binary writers, 4 CFI solutions, and 2 isolation methods) were found to suffer from the "badass" issue, leading to executable stacks in the hardened binaries they produced. After the researchers reported these findings, 3 binary writers, 3 CFI solutions, and 1 isolation method fixed the issue. This demonstrates that even security experts, deeply familiar with low-level system security, can miss this subtle yet critical directive.

Furthermore, the research revealed the intricate and often overlooked complexity of W^X enforcement across the entire software compilation and loading pipeline. This complexity, coupled with the subtle nature of the .note.GNU-stack directive, makes it easy for developers to inadvertently reintroduce executable stacks. The talk also brought to light another similar critical section, .note.GNU-property, which, if mishandled, can disable important hardware mitigations like Intel CET (Control-flow Enforcement Technology) and Intel C shadow stack, and even lead to stack overflow issues or writable read-only memory.

Technical Deep Dive

▶ Watch: Main reason: Missing note GNU stack in assembly files (2:30)

The existence of executable stacks in modern systems, despite W^X, stems from a break in the elaborate enforcement chain designed to uphold memory permissions. The W^X principle dictates that memory pages should not be simultaneously writable and executable. On Linux, this is primarily managed through the .note.GNU-stack section.

The enforcement flow of W^X is a multi-stage process involving several components:

  1. Assembler: When an assembly file is processed, the assembler is responsible for parsing directives. If the assembly file contains the .note.GNU-stack directive (e.g., .section .note.GNU-stack,"",@progbits), the assembler will generate a corresponding .note.GNU-stack section in the resulting object file. Crucially, if this directive is missing, no such section is generated. The compiler, by default, always includes this directive when compiling C/C++ code, typically setting the flag to indicate no executable stack is needed.
  1. Linker: During the linking phase, the linker combines multiple object files and libraries into a final executable or shared library. The linker inspects the .note.GNU-stack sections (or their absence) in all input object files. Its behavior is critical:
  • If any object file has its .note.GNU-stack section flagged as executable, the final linked binary will also be marked as requiring an executable stack.
  • More importantly for the "badass" issue: if any object file completely misses the .note.GNU-stack section (due to a bad assembly file), the linker, for compatibility reasons, will default to assuming an executable stack is required. This is a crucial design flaw that allows a single oversight to compromise the entire binary.

The linker then embeds this information into the program's program header (specifically, the PT_GNU_STACK program header entry) within the ELF binary. This header contains flags that indicate the desired stack permissions.

  1. Process Creation (Kernel): When a program is executed, the Linux kernel loads the ELF binary into memory and creates a new process. During this phase, the kernel reads the PT_GNU_STACK program header from the main binary. Based on the flags in this header, the kernel sets up the initial stack memory permissions for the process. If the PT_GNU_STACK header indicates an executable stack (or is absent/misconfigured due to the "badass" issue), the kernel will allocate an executable stack.
  1. Library Loading: Similar checks occur when shared libraries are loaded into a running process. If any shared library loaded by the process suffers from the "badass" issue (i.e., its PT_GNU_STACK header indicates an executable stack or is missing the necessary directive), the kernel will set the process's stack to be executable. This means that even if the main executable is correctly configured, a single vulnerable shared library can compromise the entire process's stack security.

The "badass" issue specifically bypasses the regular enforcement routine because the necessary .note.GNU-stack section is missing in the object file, leading the linker to set an improper flag in the PT_GNU_STACK program header. This subtle omission, rather than an explicit instruction to make the stack executable, is the root cause.

Beyond .note.GNU-stack, the research also identifies .note.GNU-property as another critical section. This section, if mishandled, can have severe consequences:

  • Disabling Hardware Mitigations: It can inadvertently disable Intel's Control-flow Enforcement Technology (CET) and C shadow stack, which are crucial for defending against ROP/JOP attacks and stack-based exploits.
  • Stack Overflow Issues: Incorrect handling can trigger stack overflow vulnerabilities.
  • Writable Read-Only Memory: It may lead to situations where memory intended to be read-only becomes writable, opening doors for further exploitation.

The complexity of these interactions underscores that W^X is not a "fire and forget" mitigation but requires "concerted collaborations" across the entire software development toolchain and operating system components.

Demo / Proof of Concept

▶ Watch: Even security experts' tools suffer from the 'Badass' issue (6:05)

The presentation includes a clear demonstration of the "badass" issue's impact. The speaker outlines a straightforward two-part experiment:

  1. Baseline Case: A simple C code file is compiled and executed. The stack permissions of the resulting program are then checked. As expected on a modern Linux system with W^X enabled by default, the stack is found to be readable and writable, but explicitly not executable. This serves as the control, confirming the default secure behavior.
  1. "Badass" Injection: An empty assembly file is created. This file contains no functional code; its sole characteristic is the absence of the crucial .note.GNU-stack section directive. This empty assembly file is then compiled alongside the original C code into a single program. When this new program is executed, and its stack permissions are checked, the result is dramatically different: the stack is now reported as executable.

This simple yet effective proof of concept visually illustrates how the mere omission of a non-functional directive in an assembly file, even one with no other content, is sufficient to override the system's default W^X protection and render the stack executable. It highlights the "too subtle to notice" nature of the vulnerability, where a developer might include an empty or minimal assembly file for various reasons (e.g., future expansion, placeholder, specific build system requirements) without realizing the profound security implications of missing a single, non-obvious line.

Defensive Implications

▶ Watch: Detailed enforcement analysis of Write XOR Execute mitigation (6:50)

The findings of this research necessitate a re-evaluation of how W^X is enforced and how developers approach assembly code. The "badass" issue reveals a critical gap that requires coordinated action across the software ecosystem:

  1. For Tool Developers (Compilers, Assemblers, Linkers):
  • Default to Non-Executable Stacks: The most robust solution is to change the default behavior of assemblers and linkers. By default, they should assume and enforce a non-executable stack, only enabling executability with an explicit, opt-in compilation or linking option (e.g., -z execstack). This flips the current insecure default, where missing a directive leads to an executable stack, to a secure default where explicit action is needed to opt-out of protection. This aligns with the principle of secure by default.
  • Stricter Warnings/Errors: Assemblers and linkers could issue warnings or even errors if they encounter object files missing the .note.GNU-stack section, forcing developers to explicitly acknowledge and resolve the stack permission status.
  1. For Application Developers (using Assembly):
  • Always Include Directives: Any developer who includes assembly files in their projects must be vigilant. It is imperative to always include the .note.GNU-stack directive in all assembly files, explicitly stating the desired stack permissions. The common practice should be to declare it as non-executable.
  • Security Audits: Projects, especially those with performance-critical or low-level components using assembly, should undergo thorough security audits to ensure proper handling of stack permissions.
  1. For Kernel and Loader Developers:
  • User Confirmation for Executable Stacks: The kernel and dynamic loader (e.g., ld.so) could implement stronger safeguards. If they detect that a program or any of its loaded shared libraries requests an executable stack (or implicitly defaults to one due to a missing directive), they could ask the user for explicit confirmation or log a prominent security warning. This would make the "too subtle to notice" issue much harder to overlook.
  1. Other Operating Systems: The presentation briefly touches upon the state of W^X enforcement on other operating systems, highlighting varying approaches:
  • OpenBSD: Known for its strong security posture, OpenBSD was among the first to implement W^X and has made it mandatory since 2016, suggesting a more robust default.
  • Windows: By default, the stack is not executable. However, developers can enable it through specific assembly directives, similar to the explicit opt-in model proposed for Linux.
  • macOS (Intel CPU): The stack is non-executable by default, but it can be enabled via a linker option.
  • macOS (Apple Silicon): On Apple Silicon, the stack is always non-executable, indicating a hardware-enforced, immutable security policy that completely removes this class of vulnerability. This represents the strongest possible defense.

The research emphasizes that while mitigations exist, they are not sufficient if the underlying enforcement mechanisms are prone to subtle misconfigurations. The complexity of the W^X enforcement chain, coupled with the "shalty" (subtlety and faultiness) of directives like .note.GNU-stack and .note.GNU-property, remains a significant challenge.

Key Takeaways

  • Executable Stacks Persist: Despite widespread W^X defenses, executable stacks still exist in modern, popular Linux applications and security tools due to subtle configuration oversights.
  • The "Badass" Issue: The primary cause is the omission of the .note.GNU-stack section directive in assembly files, which implicitly tells the linker to create an executable stack for compatibility.
  • Security Experts Are Not Immune: Even 11 out of 21 investigated security-focused Reference Monitors (like CFI solutions) were vulnerable to the "badass" issue, highlighting the directive's obscure nature.
  • W^X Enforcement is Complex: Preventing executable stacks requires a "concerted collaboration" across assemblers, compilers, linkers, and the kernel; a break in this chain can reintroduce vulnerabilities.
  • Beyond Stacks: The .note.GNU-property section is another critical, subtle directive whose mishandling can disable hardware mitigations (Intel CET, C shadow stack) and introduce other memory safety issues.
  • Stronger Defaults and Vigilance Needed: Recommendations include making non-executable stacks the default, requiring explicit opt-in for executability, and urging developers to always include stack directives in assembly files.

About the Speaker(s)

Hengkai Ye is a researcher from Pensday. In his presentation at the NDSS Symposium, he demonstrated a deep understanding of low-level system security, assembly language, and the intricate workings of the Linux kernel's memory management and program loading mechanisms. His work focuses on identifying subtle vulnerabilities that undermine fundamental security mitigations, providing insights crucial for improving the robustness of modern software systems.

Reviews

Dr. Zero (Offensive Security Researcher) — STRONG ACCEPT

Solid, original systems security research that surfaces a real and embarrassing blind spot in the W^X enforcement chain — the kind of finding that's obvious in retrospect but clearly required systematic effort to document at scale. The fact that 11 of 21 security-focused reference monitors were bitten by this is the killer stat that earns the talk its seat.

Heather Calloway (CISO) — WEAK

Technically credible research on a real and underappreciated gap in W^X enforcement — executable stacks persisting in production software due to subtle assembly directive omissions. The findings are legitimate, but the talk never climbs out of the toolchain and into the institutional or operational layer where decisions actually get made.

→ Top-rated talks at Network and Distributed System Security (NDSS) Symposium 2025

All talks from Network and Distributed System Security (NDSS) Symposium 2025