CVE-2017-1000375

NetBSD Stack Clash: When the Floor Becomes the Ceiling

Amit Schendel
Amit Schendel
Senior Security Researcher

Jan 7, 2026·6 min read

Executive Summary (TL;DR)

NetBSD mapped the dynamic linker directly below the stack. Attackers could allocate large buffers to 'jump' over the stack guard page and write directly into the linker's memory. This allows for reliable Local Privilege Escalation (LPE), effectively bypassing ASLR and stack protections.

A fundamental memory management flaw in NetBSD allowed the stack to collide with the dynamic linker (ld.so), bypassing guard pages and enabling arbitrary code execution. Part of the broader 'Stack Clash' research by Qualys.

The Hook: A Game of Memory Tetris

In the world of operating system design, memory layout is everything. Ideally, you want your stack (where local variables and function calls live) and your heap (dynamic memory) to stay far, far away from each other. They are the volatile teenagers of memory segments—let them touch, and you get chaos.

Enter CVE-2017-1000375, a star player in the 'Stack Clash' saga of 2017. While Linux and OpenBSD were busy patching their own variations of this nightmare, NetBSD was found holding a particularly smoking gun. The operating system had decided, in its infinite wisdom, to map ld.so—the runtime link-editor responsible for loading shared libraries—directly below the stack.

This is the architectural equivalent of building a munitions depot in the basement of a fireworks factory. ld.so is a high-value target. It contains function pointers, relocation tables, and logic that runs with the privileges of the executing process. If you can write to it, you own the process. And thanks to this vulnerability, writing to it was as easy as asking the stack to grow a little too fast.

The Flaw: The Guard Dog That Slept

To understand why this is a vulnerability, we have to talk about Guard Pages. In a sane operating system, the kernel places a small region of unmapped memory (usually 4KB, or one page) at the bottom of the stack. This is the electric fence. If the stack grows naturally and hits this fence, the CPU throws a page fault, the kernel sees it, and either expands the stack legally or kills the process for misbehaving.

However, the 'Stack Clash' attack relies on a simple physics problem: Quantum Tunneling. Okay, not really, but close. If a program allocates a stack buffer that is larger than the guard page (say, 64KB), the Stack Pointer (SP) subtracts that amount instantly. The SP 'hops' over the guard page entirely, landing in whatever memory lies beyond.

In NetBSD's case, the land beyond the fence wasn't an empty void; it was the ld.so data segment. Because ld.so is a valid, writable memory region, the CPU sees the write instructions, checks the permissions, says 'Looks good to me!', and allows the program to corrupt the linker's internal state. No page fault. No crash. Just silent, deadly corruption.

The Layout: Visualizing the Crash

Let's visualize the memory layout to see exactly how egregious this setup was. In a standard exploitation scenario, we are fighting against Address Space Layout Randomization (ASLR), which shuffles these regions around. But here, the relative distance between the stack and the linker was often deterministic or easily manipulatable.

The red arrow represents the exploit. By using a large alloca() or a recursive function with large stack frames, the attacker subtracts enough from the Stack Pointer to bypass the Guard Page entirely. The write operations that follow execute directly inside ld.so.

The Exploit: Smashing the Linker

Qualys provided a beautiful, minimalist Proof of Concept (PoC) that demonstrates the mechanics without the weaponized payload. The core logic exploits the lack of stack probing. If the compiler doesn't generate code to touch every page as the stack grows (using -fstack-check), the CPU never validates the intermediate pages.

Here is the logic derived from the PoC:

static void smash_no_jump(const size_t smash_size) {
    char buf[1024];
    // 1. Fill the buffer to ensure we are actually using the memory
    memset(buf, 'A', sizeof(buf));
    
    // 2. Recursively call to grow the stack closer to the target
    if (smash_size > sizeof(buf))
        smash_no_jump(smash_size - sizeof(buf));
}

A real-world weaponized exploit follows this chain:

  1. Preparation: The attacker forks a process or uses execve to set up a clean memory layout where the distance between the stack and ld.so is known.
  2. The Approach: Use standard recursion (like qsort or the loop above) to bring the Stack Pointer right to the edge of the Guard Page.
  3. The Leap: Call a function that allocates a massive buffer (e.g., 64KB). The instruction sub rsp, 0x10000 executes. The Stack Pointer is now pointing inside ld.so.
  4. The Overwrite: The function continues to execute, writing data to its local variables (which are now mapped over ld.so structures). The attacker overwrites a function pointer in the Global Offset Table (GOT) or a destructor entry.
  5. Execution: When the program calls exit() or resolves a dynamic symbol, the linker wakes up, reads the corrupted pointer, and jumps to the attacker's shellcode.

The Impact: Why This Matters

You might look at the CVSS score of 9.8 and think 'Remote Code Execution?' Not exactly. NVD lists it as AV:N, but this is primarily a Local Privilege Escalation (LPE) vector. However, the criticality is justified by the reliability and the ubiquity of the stack.

This vulnerability allowed any local user with shell access to become root effectively 100% of the time. Unlike complex heap feng-shui exploits that rely on precise race conditions or heap grooming, Stack Clash is deterministic. If you can do math, you can own the box.

Furthermore, this highlighted a massive failure in OS design principles. We spent years building ASLR, NX (No-Execute), and Canaries, only to leave the back door open because we assumed the stack would never jump over a 4KB fence. It was a wake-up call that mitigation assumes normal behavior, and hackers rarely behave normally.

The Fix: Building Higher Fences

The remediation for Stack Clash was a multi-layered approach, acknowledging that a single fix wasn't enough.

  1. Bigger Guard Pages: NetBSD and other kernels increased the default guard page size (often to 1MB or dynamic sizing) to make it much harder for a single allocation to jump over it.
  2. Stack Probing: The real fix comes from the compiler. Recompiling userland with -fstack-check (on GCC) forces the program to touch every 4KB page when allocating large buffers. This ensures you literally cannot skip the guard page; you will hit it and crash before you reach the juicy data.
  3. Layout Randomization: Kernels became more aggressive about placing random gaps between the stack and shared libraries. If you don't know where ld.so is relative to the stack, you're jumping blind.

For developers today: valid stack growth is not guaranteed. If you are writing low-level code or OS kernels, never assume adjacent memory is safe just because you put a PROT_NONE page in between.

Official Patches

Technical Appendix

CVSS Score
9.8/ 10
CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
EPSS Probability
38.41%
Top 3% most exploited

Affected Systems

NetBSD 7.1NetBSD (All versions prior to patch)

Affected Versions Detail

Product
Affected Versions
Fixed Version
NetBSD
NetBSD
<= 7.1Subject to patch availability (Post-2017 updates)
AttributeDetail
Attack VectorLocal (Stack manipulation)
CVSS v3.09.8 (Critical)
Bug ClassStack Clash / Memory Corruption
Target Componentld.so (Dynamic Linker)
Exploit ReliabilityHigh (Deterministic memory layout)
EPSS Score38.41%
CWE-787
Out-of-bounds Write

Out-of-bounds Write

Vulnerability Timeline

Qualys publishes Stack Clash research
2017-06-19
CVE-2017-1000375 assigned
2017-06-19
PoC published on Exploit-DB
2017-06-28

Subscribe to updates

Get the latest CVE analysis reports delivered to your inbox.