Skip to content

Memory Protection

CloudTaser stores secrets exclusively in process memory, never on disk, never in etcd, never in environment variables that persist in /proc. This page documents each memory protection mechanism, how it works at the kernel level, and what it defends against.


Overview

The wrapper binary applies protections in a specific order during startup:

1. Allocate memfd_secret region (or fall back to mmap + mlock)
2. Fetch secrets from EU vault over mTLS
3. Write secrets into protected memory region
4. Apply mlock, MADV_DONTDUMP, PR_SET_DUMPABLE(0)
5. Scrub environment variables from /proc/PID/environ
6. Set up LD_PRELOAD interposer (if dynamically linked app)
7. Exec the application process

Each mechanism addresses a different attack vector. Together, they form a layered defense that a root attacker must defeat simultaneously.


memfd_secret(2)

Kernel requirement: Linux 5.14+

memfd_secret is the strongest memory protection available in the Linux kernel. It creates an anonymous file descriptor backed by memory pages that are physically removed from the kernel's direct memory map.

How It Works

int fd = memfd_secret(0);
ftruncate(fd, secret_size);
void *addr = mmap(NULL, secret_size, PROT_READ | PROT_WRITE,
                   MAP_SHARED, fd, 0);
// Write secrets to addr
// Pages are now invisible to the kernel

When memfd_secret is called, the kernel:

  1. Allocates pages from the buddy allocator
  2. Removes those pages from the kernel's direct map (the linear mapping of all physical memory)
  3. Sets up page table entries only in the owning process's address space

What this means in practice

No kernel code path can read memfd_secret pages. Not /dev/mem. Not /proc/PID/mem. Not kernel modules. Not eBPF programs. Not kprobes. The pages simply do not exist in any kernel-accessible mapping. The only entity that can read them is the owning user-space process -- and the hypervisor, which has direct physical memory access.

What It Defends Against

Attack Vector Blocked? Why
Root reading /proc/PID/mem Yes Pages not in kernel direct map
Kernel module scanning memory Yes Pages not in kernel direct map
/dev/mem access Yes Pages not in kernel direct map
eBPF program reading memory Yes Pages not in kernel direct map
Hypervisor memory inspection No Physical memory access bypasses page tables

Configuration

env:
  - name: CLOUDTASER_REQUIRE_MEMFD_SECRET
    value: "true"   # Fail startup if memfd_secret unavailable

When CLOUDTASER_REQUIRE_MEMFD_SECRET=true, the wrapper will refuse to start on kernels older than 5.14. This ensures that secrets are never stored without hardware-level memory hiding.


mlock(2)

Kernel requirement: Any Linux

mlock pins memory pages in physical RAM, preventing the kernel from swapping them to disk.

How It Works

mlock(secret_addr, secret_size);

The kernel marks the pages as non-evictable. They will remain in RAM until explicitly unlocked or the process exits.

What It Defends Against

Attack Vector Blocked? Why
Swap file forensics Yes Pages never written to swap partition
Disk imaging after shutdown Yes (for swap) No swap footprint to recover
OOM killer eviction to swap Yes Pages are pinned regardless of memory pressure

mlock does not hide memory from root

A root attacker can still read mlocked pages via /proc/PID/mem or ptrace. mlock only prevents disk exposure. It is a complement to memfd_secret, not a substitute.

Configuration

env:
  - name: CLOUDTASER_REQUIRE_MLOCK
    value: "true"   # Fail startup if mlock fails

CAP_IPC_LOCK

The wrapper container requires CAP_IPC_LOCK to call mlock. CloudTaser's Helm chart sets this capability by default. Without it, mlock will fail and the wrapper will fall back to unprotected memory (unless CLOUDTASER_REQUIRE_MLOCK=true).


MADV_DONTDUMP

Kernel requirement: Linux 3.4+

MADV_DONTDUMP tells the kernel to exclude specific memory regions from core dumps.

How It Works

madvise(secret_addr, secret_size, MADV_DONTDUMP);

When the process crashes and the kernel generates a core dump, pages marked with MADV_DONTDUMP are omitted. The core file contains a hole where the secret region was.

What It Defends Against

Attack Vector Blocked? Why
Core dump analysis Yes Secret pages excluded from dump
Crash dump collection services Yes Secrets not in the dump file
Cloud provider crash analysis pipelines Yes Provider never sees secrets in crash reports

Defense in depth with PR_SET_DUMPABLE

MADV_DONTDUMP protects the specific memory region. PR_SET_DUMPABLE(0) prevents core dumps entirely. CloudTaser applies both.


PR_SET_DUMPABLE(0)

Kernel requirement: Any Linux

PR_SET_DUMPABLE(0) sets the process as non-dumpable, which has several security effects beyond just preventing core dumps.

How It Works

prctl(PR_SET_DUMPABLE, 0);

Effects

Effect Description
No core dumps Kernel will not generate core dump files for this process
Restricted /proc access /proc/PID/{mem,maps,environ,syscall,stack} become unreadable by non-root
ptrace restriction Prevents ptrace ATTACH from non-parent processes (defense in depth with eBPF)
No SIGABRT dumps Abort signals do not produce dump files

Root can override PR_SET_DUMPABLE

A root attacker can re-enable dumpable via /proc/PID/coredump_filter or by loading a kernel module. This is why CloudTaser's eBPF agent monitors /proc writes and detects kernel module loading.


Environment Scrubbing

Kernel requirement: Any Linux

After secrets are loaded into protected memory, the wrapper overwrites the environment variable values in the process's memory space and scrubs /proc/PID/environ.

How It Works

  1. Secrets are fetched from the vault and written to memfd_secret (or fallback) memory
  2. The original environment strings are overwritten with zeros in place
  3. /proc/PID/environ no longer contains secret values

What It Defends Against

Attack Vector Blocked? Why
Reading /proc/PID/environ Yes Values overwritten with zeros
Container runtime env inspection (docker inspect) Yes Original env values scrubbed
Kubernetes API env inspection N/A Secrets never set via K8s env vars

Why this matters

Many secret injection tools set secrets as environment variables and leave them readable in /proc/PID/environ for the lifetime of the process. Any process on the same node with sufficient permissions can read them. CloudTaser scrubs these values immediately after loading secrets into protected memory.


LD_PRELOAD Interposer

Kernel requirement: Any Linux (dynamically linked applications only)

The LD_PRELOAD interposer solves a subtle but critical problem: even when secrets are stored in memfd_secret, the application runtime may copy them to the heap when the application calls getenv().

The Problem

Without interposer:
  1. Secret in memfd_secret region         [PROTECTED]
  2. App calls getenv("DB_PASSWORD")
  3. C library copies value to heap         [UNPROTECTED - on regular heap]
  4. App uses heap copy
  5. Two copies exist: memfd (safe) + heap (exposed to root)

The Solution

With interposer:
  1. Secret in memfd_secret region         [PROTECTED]
  2. App calls getenv("DB_PASSWORD")
  3. Interposer intercepts, returns pointer into memfd region  [PROTECTED]
  4. App uses memfd pointer directly
  5. Only one copy exists: memfd (safe)

How It Works

The wrapper sets LD_PRELOAD to load a shared library that interposes on getenv(). When the application calls getenv() for a secret key, the interposer:

  1. Looks up the key in its memfd_secret-backed table
  2. Returns a pointer directly into the memfd_secret region
  3. The application uses this pointer without knowing it points to protected memory

Coverage

Application Type Interposer Works? Alternative
Dynamically linked C/C++ Yes --
Python, Ruby, Node.js Yes (C runtime underneath) --
Java (JNI) Yes --
PostgreSQL, MySQL, Redis Yes --
Go (CGO_ENABLED=1) Yes --
Go (CGO_ENABLED=0, static) No CloudTaser SDK for direct memfd access
Rust (musl static) No CloudTaser SDK

Approximately 90% of Kubernetes workloads are dynamically linked

The interposer covers the vast majority of production workloads without any application code changes. For the remaining statically linked binaries, the CloudTaser SDK provides equivalent protection through direct memfd file descriptor access.


Fallback Behavior on Older Kernels

When memfd_secret is unavailable (Linux < 5.14), the wrapper falls back to a degraded but still defended posture:

Mechanism 5.14+ Pre-5.14
memfd_secret Active Unavailable -- falls back to anonymous mmap
mlock Active Active
MADV_DONTDUMP Active Active
PR_SET_DUMPABLE(0) Active Active
Environment scrubbing Active Active
LD_PRELOAD interposer Active Active
eBPF enforcement Active Active

Pre-5.14 kernels are vulnerable to root kernel module attacks

Without memfd_secret, a root attacker who loads a custom kernel module can scan process memory and find secrets. The eBPF agent detects module loading (global privilege escalation detection) but cannot block it from non-monitored PIDs without breaking system services.

Recommendation: Use Linux 5.14+ for production deployments. The protection score reflects memfd_secret availability with the highest single-mechanism point value (15 points).

Strict Mode

Set CLOUDTASER_REQUIRE_MEMFD_SECRET=true to prevent the wrapper from starting on older kernels:

env:
  - name: CLOUDTASER_REQUIRE_MEMFD_SECRET
    value: "true"

This is the recommended setting for production workloads where data sovereignty guarantees are contractually required.


Summary Table

Mechanism Defends Against Kernel Can Root Bypass? eBPF Backup?
memfd_secret All software memory access 5.14+ No N/A
mlock Swap to disk Any No (but irrelevant -- root reads RAM directly) N/A
MADV_DONTDUMP Core dump exposure 3.4+ Yes (coredump_filter write) Yes -- blocks /proc writes
PR_SET_DUMPABLE(0) ptrace, /proc access, core dumps Any Yes (kernel module) Yes -- blocks ptrace, detects modules
Environ scrubbing /proc/PID/environ reads Any No (values are gone) Yes -- blocks environ reads anyway
LD_PRELOAD Heap copies of secrets Any N/A (prevents copies, not access) N/A

:octicons-arrow-right-24: eBPF Enforcement | :octicons-arrow-right-24: Protection Score