Memory Bleed / CWE-908

Node.js Buffer.allocUnsafe() Memory Leak: How Un-Zeroed RAM Exposes Keys & Cookies

How allocating uninitialized memory slabs with Buffer.allocUnsafe() in Node.js services transmits leftover HTTP headers, passwords, and private keys across concurrent network streams.

💡 Plain English Explainer (ELI5)

When your computer deletes a file or finishes an HTTP request, it doesn't erase the letters from its physical memory; it just marks the space as 'free'. `Buffer.alloc()` fills that space with zeros before giving it to you. `Buffer.allocUnsafe()` gives you the memory raw and dirty. If your code doesn't fill every single byte, it broadcasts the old leftover data—like another user's session cookie or password—out over the internet.

Core Concepts & Key Terms

Uninitialized Memory
RAM that has been allocated by the operating system or runtime engine but not wiped with zeros, retaining fragments of prior process data.
Node.js 8KB Slab Allocator
The internal V8 memory optimization where Node pre-allocates an 8KB `ArrayBuffer` slab to slice small buffers quickly.
`Buffer.allocUnsafe()`
A high-performance Node.js method that allocates memory without zero-filling the underlying buffer pool.
ESLint `no-buffer-constructor`
A static analysis rule that flags dangerous `new Buffer()` invocations which implicitly triggered uninitialized allocations.

Step-by-Step Attack Flow

Step 1

1. High-Concurrency Server Traffic

A Node.js web server processes diverse workloads: User A submits private API tokens and database passwords.

Step 2

2. Memory Reallocated to Slab

User A's request finishes. The 8KB memory slab is returned to the pool with User A's data remaining intact in memory.

Step 3

3. Unsafe Allocation with Partial Write

A proxy or image handler allocates buf = Buffer.allocUnsafe(1024) for User B, but only writes 200 bytes of actual response data into it.

Step 4

4. Data Bleed Over Network

The server serializes the full 1024-byte buffer into the HTTP response. User B inspects the trailing 824 bytes and discovers User A's raw credentials.

Source Code: Flaw vs. Secure Implementation

VULNERABLE PATTERN
// VULNERABLE: Using Buffer.allocUnsafe() with Partial Content
const http = require("http");

http.createServer((req, res) => {
  // CRITICAL SECURITY FLAW:
  // allocUnsafe skips zero-initialization to save negligible CPU cycles.
  // The allocated buffer contains un-scrubbed RAM fragments from other requests!
  const buffer = Buffer.allocUnsafe(512);
  
  const greeting = "Hello, authenticated user!";
  // Only writes 27 bytes into a 512-byte buffer!
  buffer.write(greeting); 
  
  // The remaining 485 bytes contain raw RAM fragments:
  // session cookies, authorization headers, or database rows!
  res.writeHead(200, { "Content-Type": "application/octet-stream" });
  res.end(buffer);
}).listen(3000);
HARDENED DEFENSE
// SECURE: Strict Zero-Initialized Buffers & Exact Length Slicing
const http = require("http");

http.createServer((req, res) => {
  const greeting = "Hello, authenticated user!";
  
  // 1. BEST PRACTICE: Allocate exactly the required size, automatically zero-filled
  const safeBuffer = Buffer.from(greeting, "utf-8");
  
  // 2. If dynamic pre-allocation is required, ALWAYS use zero-initialized Buffer.alloc()
  const preAllocated = Buffer.alloc(512); // Wipes memory with 0x00 bytes
  preAllocated.write(greeting);

  // 3. Slice the buffer to the EXACT length written before sending over network
  const finalOutput = preAllocated.subarray(0, Buffer.byteLength(greeting));

  res.writeHead(200, { "Content-Type": "text/plain" });
  res.end(finalOutput);
}).listen(3000);

Engineering Hardening Checklist

← Browse Full Security Directory Explore Vulnerability Playbooks →