Linux Idle-Memory Encryption for Long-Running Services
StayCloser moves explicitly registered, idle application pages out of plaintext memory and into an authenticated ciphertext store managed by a separate process. The protected service uses a small C shared library; the monitor owns page-selection policy, cryptographic keys, page-state validation, and fault-driven restoration.
Authenticated monitor · userfaultfd restoration · AES-256-GCM · Adaptive page leases · Structured
operational telemetry
Warning
StayCloser is currently in experimental beta. You may encounter bugs, incomplete behavior, and compatibility issues. The project is under active development and is expected to change as testing, integrations, and platform support expand.
docs/ARCHITECTURE.md: Trust boundaries, registration, and page lifecycledocs/THREAT_MODEL.md: Security objective, adversaries, controls, and limitsdocs/CONTAINERS.md: Seccomp requirements and container deployment modelsCONTRIBUTING.md: Development workflow and validation requirements
- Protects application-owned, page-aligned private anonymous memory selected by the application.
- Learns a bounded per-page plaintext lease from refault behavior when DAMON is unavailable.
- Write-protects a candidate page before snapshotting so concurrent writers cannot produce a lost update.
- Encrypts each page generation with AES-256-GCM and identity-bound authenticated data.
- Discards the target's plaintext page and independently verifies it is neither present nor swapped.
- Restores authenticated plaintext on a verified in-process
userfaultfdevent. - Emits low-volume lifecycle and aggregate memory telemetry by default.
- Materializes cold pages during graceful client or monitor shutdown.
StayCloser never scans arbitrary writable memory. Applications must opt in a dedicated mapping through the public API.
StayCloser is a two-part system:
- Client library (
libstaycloser.so)- Authenticates the monitor's Unix socket owner and
SO_PEERCREDidentity. - Creates a
userfaultfdwith missing-page, write-protect, and thread-ID features. - Registers an explicit mapping and performs authenticated discard requests on a control thread.
- Authenticates the monitor's Unix socket owner and
- Monitor (
staycloserd)- Verifies client credentials, exact executable path, pidfd, process start time, protocol state, and mapping properties.
- Owns encryption keys, ciphertext, adaptive lease state, pagemap validation, and page restoration.
stateDiagram-v2
[*] --> Present
Present --> Sealing: Plaintext lease expires
Sealing --> Cold: Write protect, encrypt, discard, verify pagemap
Cold --> Restoring: Application access and verified fault thread
Restoring --> Present: Authenticate ciphertext and UFFDIO_COPY
The monitor stores ciphertext, not a second plaintext page. Plaintext remains available while a page is active and exists briefly in locked, non-dumpable monitor scratch memory during seal and restoration.
Read the complete StayCloser threat model.
- Linux with
CONFIG_USERFAULTFD=y userfaultfdmissing-page, write-protect, and thread-ID features- C17 compiler and GNU make
- Linux UAPI headers
- OpenSSL 3 development headers
clang-formatfor source formatting
Container runtimes commonly deny userfaultfd through seccomp even when the host kernel supports it.
See Container Deployment before integrating a containerized service.
make
make checkThe default build is optimized with -O3, link-time optimization, and instruction selection for the
current CPU through -march=native -mtune=native. Build stripped binaries for installation or packaging:
make releaseRelease archives use a portable build instead of inheriting the build runner's CPU features:
make clean
make NATIVE=0 releaseFor an unoptimized build with debug symbols, run make debug.
sudo make installPREFIX defaults to /usr/local. Staged packaging is supported through DESTDIR:
make DESTDIR="$package_root" PREFIX=/usr installAllocate a dedicated, page-aligned anonymous mapping and register it:
#include <staycloser.h>
if (staycloser_init("/run/staycloser/monitor.sock", 0) < 0) {
/* handle staycloser_last_error() */
}
if (staycloser_protect(mapping, mapping_length) < 0) {
/* keep using the unprotected mapping or fail closed */
}mapping and mapping_length must be page-aligned. The current monitor accepts one range per client.
Do not register stacks, allocator arenas, executable or file-backed mappings, signal-path data, locked
memory, or latency-critical runtime structures.
On systems with Yama ptrace restrictions, permit the library to grant the authenticated monitor PID the
narrow relationship required by process_vm_readv:
export STAYCLOSER_ALLOW_PTRACE_MONITOR=1The PID is received through the authenticated challenge. The environment variable does not select or trust an application-supplied PID.
Adaptive lease mode is the currently implemented page-selection backend:
staycloserd \
--socket /run/staycloser/monitor.sock \
--allow-exe /usr/local/bin/orders-api \
--allow-uid 10001 \
--lease-mode \
--lease-min-ms 250 \
--lease-max-ms 30000 \
--lease-refault-window-ms 100 \
--lease-cold-reset-ms 60000 \
--service-label orders-apiWithout --lease-mode, the current release refuses to start because the DAMON adapter is not yet
implemented.
| Option | Required | Default | Purpose |
|---|---|---|---|
--allow-exe PATH |
Yes | Required | Exact executable path accepted by the monitor |
--allow-uid UID |
Yes | Required | Expected kernel UID from SO_PEERCRED |
--socket PATH |
No | /run/staycloser/monitor.sock |
Authenticated Unix socket |
--lease-mode |
Current release | Disabled | Enables the adaptive page-selection backend |
--lease-min-ms N |
No | 250 |
Minimum plaintext lease |
--lease-max-ms N |
No | 30000 |
Hard maximum plaintext lease |
--lease-refault-window-ms N |
No | 100 |
Refault window that increases the next lease |
--lease-cold-reset-ms N |
No | 60000 |
Cold residency that decreases the next lease |
--log-level LEVEL |
No | info |
error, warning, info, or debug |
--metrics-interval-sec N |
No | 60 |
Aggregate summary interval; 0 disables summaries |
--service-label NAME |
No | protected-service |
Stable label added to log records |
Each page begins at the minimum lease. A refault within the configured window doubles the next lease up to the maximum. A page that remains encrypted for at least the cold-reset interval has its next lease halved toward the minimum. Policy state is bounded, per-page, in memory, and not persisted.
The monitor writes structured single-line key/value records to stderr for journald and container log
collectors. The default info level emits lifecycle events and one aggregate memory_summary per client
every 60 seconds. Per-page addresses and seal/restore events are debug-only.
staycloserd ... --log-level info --metrics-interval-sec 60Use warning or error for lower-volume operation. Set the metrics interval to 0 to disable periodic
summaries. Use debug only for bounded diagnostics because it can produce high-volume, address-bearing
records.
Run the normal functional suite:
make checkIt covers authenticated connection failures, executable-map rejection, concurrent writes, multiple page generations, repeated library initialization, adaptive lease growth and decay, and graceful cold-page recovery.
Run the 256-page performance smoke test:
make benchmarkThe benchmark confirms that every page became encrypted before access, verifies restored bytes, and reports aggregate and average fault-restoration latency. Results are workload and host specific and are not enforced as a pass/fail latency threshold.
Run the independent cold-state proof:
make evidence-checkThe evidence build pauses a known page while cold and verifies:
- the target pagemap entry is neither present nor swapped;
- stored ciphertext differs from the known page and contains no known marker;
- AES-GCM decrypts to the exact original bytes;
- a modified authentication tag is rejected; and
- access after release restores the page exactly.
The evidence monitor is compiled separately and exports a temporary test key under a mode-0700
directory in build/. Normal binaries do not contain the export path. Treat retained evidence as
sensitive test data and remove it with make clean when no longer required.
To repeat the proof in a locally cached container image:
make evidence-probes
./tests/container_evidence.sh your-image:tag image-labelThe container harness uses an unconfined seccomp profile only inside a disposable, network-disabled diagnostic container. It is not a production configuration.
StayCloser is available under the MIT License.