Memory Protection¶
Authorised application boundary
Authorised application code is trusted. An authorised app can send out a secret it has copied or encoded. Current content checks do not provide a general guarantee against this disclosure or a malicious authorised app. Memory-read tests and a Ready agent do not prove copied/encoded-secret exfiltration prevention. Enforced NetworkPolicy can restrict destinations, but allowed traffic remains a channel. See Security Model.
cloudtaser delivers fetched secrets through process memory rather than Kubernetes Secrets or disk files. Inherited environment delivery places values in the child's /proc/<pid>/environ; active eBPF enforcement restricts external reads. This page documents each memory protection mechanism, how it works at the kernel level, and what it defends against.
Compliance Summary
cloudtaser combines memory isolation, read-path enforcement and tamper signals. Protection provides tamper signals within the deployed enforcement boundary, not absolute against node root or arbitrary kernel code. These controls provide evidence for a deployment-specific compliance assessment; they do not establish compliance or eliminate provider risk by themselves.
Overview¶
The wrapper fetches secrets after startup, prepares protected mappings and selects a delivery mode before launching the application:
- Eligible glibc-dynamic programs use the LD_PRELOAD interposer by default. Mapped secret keys are removed from the exec environment and served through supported
getenv()calls. - Static binaries, musl environments and explicit inherited-environment mode receive actual child environment entries. Those entries and application-created copies require active read-path enforcement.
- The wrapper's own exec environment contains configuration, not fetched secrets. Memory allocation, locking and dump restrictions are checked separately from delivery compatibility.
See Wrapper runtime compatibility. These layers depend on kernel integrity; they do not make a compromised kernel trustworthy.
memfd_secret(2)¶
Kernel requirement: Linux 5.14+
memfd_secret creates an anonymous file descriptor whose mapped pages have no kernel direct-map alias. It strengthens isolation of that mapping; it does not protect every copy of a secret.
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
// The mapped pages have no kernel direct-map alias
When memfd_secret is called, the kernel:
- Allocates pages from the buddy allocator
- Removes those pages from the kernel's direct map (the linear mapping of all physical memory)
- Maps the pages into processes holding the descriptor
Kernel integrity remains part of the boundary
Removing the direct-map alias restricts ordinary read paths. It does not make the pages unreachable to arbitrary kernel code, and it does not protect ordinary heap or environment copies. Unrestricted node root can load modules/BPF where permitted, use kexec on non-lockdown images, inspect copies through /proc/kcore, or remove bpffs pins after killing the agent. CloudTaser provides tamper signals within its enforcement boundary, not an absolute root or kernel barrier. See the security model.
The Linux memfd_secret manual describes the direct-map and cross-process restrictions without guaranteeing protection against every kernel attack.
What It Defends Against¶
| Attack Vector | Scope |
|---|---|
| Ordinary cross-process memory reads | Secret mappings have additional kernel restrictions; ordinary application copies need active eBPF protection |
| Arbitrary kernel module or privileged kernel-code execution | Outside the isolation guarantee |
| Ordinary heap/environment copies | Outside the secret mapping; protection depends on delivery mode and active read-path enforcement |
| Hypervisor memory inspection | Requires a confidential-compute substrate; memfd_secret alone does not address it |
Configuration¶
env:
- name: CLOUDTASER_REQUIRE_MEMFD_SECRET
value: "true" # Fail startup if memfd_secret unavailable
When CLOUDTASER_REQUIRE_MEMFD_SECRET=true, the wrapper refuses startup if the required secret mapping is unavailable. Kernel version alone does not establish availability; configuration and architecture matter. This is a software isolation requirement, not hardware memory encryption.
mlock(2)¶
Kernel requirement: Any Linux
mlock pins memory pages in physical RAM, preventing the kernel from swapping them to disk.
How It Works¶
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 | The memfd_secret region is not swappable; ordinary application heap/environment copies can be swapped |
| 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¶
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¶
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 | Marked mappings are omitted; ordinary application copies need separate protection |
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¶
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
PR_SET_DUMPABLE(0) is not a boundary against arbitrary kernel code or all privileged access. coredump_filter controls dump contents; it does not change the dumpable attribute. See PR_SET_DUMPABLE.
Wrapper environ is structurally clean (environ_scrubbed)¶
Kernel requirement: Any Linux
The wrapper's own /proc/self/environ does not contain secret values. This is a structural property of the fork+exec architecture, not a runtime mutation.
How It Works¶
/proc/PID/environ on Linux is a frozen snapshot of the bytes between mm->env_start and mm->env_end set at execve() time. The kernel does not update this region when a process subsequently calls os.Setenv / setenv / putenv — those calls modify the C library's environ array in heap memory, not the kernel's record of the original envp[].
The wrapper exploits this:
- The wrapper's container is started by the operator webhook with a configuration-only env (
CLOUDTASER_*,VAULT_ADDR, etc.). No secrets inenvp[]at exec time. - After the wrapper starts, it fetches secrets from vault into Go heap memory, the protected
memfd_secretregion, or both. This Go-heap data does not flow back into the kernel's frozen/proc/self/environ. - Strict interposer delivery removes mapped secret keys from the child's exec environment. In inherited-environment mode,
BuildChildEnvsupplies actual values to the child, so its initial/proc/PID/environcontains them. Neither mode adds fetched secrets to the wrapper's original exec environment.
The environ_scrubbed protection score check therefore confirms a structural property: at no point does the wrapper's own /proc/self/environ byte-range contain secret keys.
Child environment and application copies
Inherited-environment mode leaves secret values in the child's initial environment. The wrapper's clean environment does not remove them. Active target-anchored BPF LSM enforcement protects external procfs reads; it does not erase values or prevent the authorized application from copying or transmitting them. Strict interposer delivery avoids the initial environment entries, but applications may still make ordinary copies.
What It Defends Against¶
| Attack Vector | Blocked? | Why |
|---|---|---|
Reading wrapper's /proc/1/environ for secrets |
Yes | Wrapper exec'd without secrets in envp[]; secrets enter only after exec, in Go heap |
Reading child's /proc/<child>/environ |
Delegated to eBPF | Active target-anchored BPF LSM policy restricts external procfs reads |
Container runtime env inspection (docker inspect) |
Yes | Container spec contains only CLOUDTASER_* configuration, not secrets |
| 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 in the container spec (the env: field in the pod YAML, or envFrom against a Kubernetes Secret). Those values land in the kernel's envp[] at exec time and are then readable in /proc/PID/environ for the entire lifetime of the process — by anything with ptrace_may_access-equivalent permissions on the namespace. cloudtaser fetches secrets from EU vault directly into wrapper memory after the wrapper has already exec'd, so the wrapper's frozen envp[] snapshot never contained them. The child's snapshot contains them in inherited-environment mode; strict interposer mode avoids those initial entries. Active eBPF enforcement remains required in both modes.
LD_PRELOAD Interposer¶
Compatibility: Eligible glibc-dynamic programs calling libc getenv() / secure_getenv().
The wrapper enables the interposer by default for eligible dynamic programs. It removes mapped secret keys from the exec environment and supplies supported reads from the shared secret mapping. Static and musl programs use inherited environment delivery instead.
Copies and runtime APIs¶
Returning a pointer into the mapping avoids an initial environment entry for that read. The application can still copy the value to ordinary memory, log it, or pass it to a child. The interposer does not guarantee that only one copy exists.
Languages that snapshot environ do not necessarily call libc getenv() for later reads. In particular, enabling CGO does not make Go's os.Getenv use the interposer. Snapshot mode and inherited environment mode have different exposure and rotation properties; consult the runtime compatibility table before choosing one.
Fallback Behavior on Older Kernels¶
When memfd_secret is unavailable and fallback is permitted, the wrapper uses an anonymous mapping with the available locking and dump restrictions. This loses the secret mapping's direct-map isolation. Availability depends on the running kernel and configuration, not just its version.
Root and kernel limitations apply on every kernel
A newer kernel with memfd_secret does not make arbitrary kernel execution safe. Keep node privileges restricted and verify the active enforcement profile. Tier 2 retains primary BPF LSM read-path protection but has detect-only secondary vectors; see platform compatibility.
Strict Mode¶
Set CLOUDTASER_REQUIRE_MEMFD_SECRET=true to require successful secret-memory allocation:
This requires the mapping; it does not establish a data-sovereignty guarantee or replace a deployment-specific threat assessment.
Summary Table¶
| Mechanism | Defends Against | Kernel | Can Root Bypass? | eBPF Backup? |
|---|---|---|---|---|
| memfd_secret | Ordinary access to the secret mapping | 5.14+ with support enabled | Arbitrary kernel code is outside the guarantee | Ordinary copies still need enforcement |
| mlock | Swap to disk | Any | No (but irrelevant -- root reads RAM directly) | N/A |
| MADV_DONTDUMP | Dumping the marked region | 3.4+ | Arbitrary kernel code is outside the guarantee | Does not cover ordinary copies |
| PR_SET_DUMPABLE(0) | ptrace, /proc access, core dumps | Any | Yes (kernel module) | With active BPF LSM: blocks covered ptrace access and detects module-loading attempts |
Wrapper environ clean (environ_scrubbed) |
Structural read of wrapper's own /proc/self/environ |
Any | N/A -- secrets never enter wrapper envp[] at exec time |
Active target-anchored BPF LSM policy restricts external reads |
| LD_PRELOAD | Initial secret environment entries in strict mode | Eligible glibc-dynamic programs | Application-created copies remain possible | Active enforcement remains required |
eBPF Enforcement | Protection Score
The default CLOUDTASER_REQUIRE_MEMFD_SECRET=true refuses fallback.
Standalone code with that requirement disabled can use memfd_create, then an
immediately unlinked file in /dev/shm or the system temporary directory. The
operator locks the requirement on injected workloads. This exception does not
qualify a production path. Memory-backed volumes hold support files; they do
not extend the protected-region guarantee to runtime heap or environment copies.
Cgroup anchor registration is inferred from startup wiring, not a measured refusal guarantee: the wrapper can log and continue without the anchor when cgroup v2 or ID resolution is unavailable. Verify the runtime state on the selected node.