hardening
hardening
¶
Process self-hardening — shrink what a leaked address space can reveal.
A process that holds secret material (a vault DB session key, an SSH private key) wants four cheap kernel-level guarantees the moment it starts, before it opens anything sensitive:
- No ptrace / no debugger attach —
prctl(PR_SET_DUMPABLE, 0)clears the dumpable flag, so another process in the same user (a compromised sibling) cannotptracethe address space and cannot read/proc/<pid>/mem. It also stops the kernel writing a core dump for this process. - No core dumps —
setrlimit(RLIMIT_CORE, 0)belt-and-braces the dumpable clear: even a SIGSEGV can't spill the heap (keys included) to a file on disk. - No swap-out —
mlockall(MCL_CURRENT | MCL_FUTURE)pins the pages into RAM so secret bytes never land in the swap file where they outlive the process. This one is best-effort: it needsCAP_IPC_LOCKor a generousRLIMIT_MEMLOCKand legitimately fails in a locked-down rootless container — a failure is reported, never raised. - No privilege gain on exec —
prctl(PR_SET_NO_NEW_PRIVS, 1)bars this process and every descendant from ever gaining privilege through a setuid/setgid bit or file capabilities onexec. A child that is compromised while shelling out (the gate service runsgit) cannot re-execa setuid helper to climb out. It also unlocks installing an unprivilegedseccompfilter, which the kernel permits only once no-new-privs is set. Unlike the dumpable clear, it never impedes a debugger attaching, so it holds even in debug mode.
harden_self applies all four to
the current process and returns a
HardeningReport of what took —
the floor every isolated child process (terok-sandbox's split supervisor
children) applies at start-up.
__all__ = ['HardeningReport', 'harden_self']
module-attribute
¶
HardeningReport(no_dump, no_core, memory_locked, no_new_privs)
dataclass
¶
What harden_self managed to apply.
Each field is True only when the corresponding guarantee is in
force for the current process. no_dump, no_core, and
no_new_privs are expected to succeed; memory_locked routinely
comes back False in a rootless container without CAP_IPC_LOCK
and that is not an error — the caller decides whether to log it.
harden_self(*, allow_debugger=False)
¶
Apply the process-hardening floor to the current process.
Idempotent and side-effecting: clears the dumpable flag, zeroes the
core-dump limit, and locks memory — each independently, so a failure
of one (typically mlockall for lack of privilege) still lets the
others take. Never raises; the returned
HardeningReport says what
held.
Call this as early as possible in a process that will hold secret material — before opening the credential store or binding a socket — so the sensitive bytes are only ever mapped under the guarantees.
allow_debugger leaves the dumpable flag set so a debugger, py-spy,
or strace can attach — the escape hatch for running a task in debug
mode. It trades away only the no-ptrace guarantee (no_dump reports
False); the core-dump, swap-out, and no-new-privileges guarantees
still apply (the last never impedes a debugger attaching).