os-memlock

Crates.ioos-memlock
lib.rsos-memlock
version0.2.0
created_at2025-10-02 17:28:07.60378+00
updated_at2025-10-03 13:53:31.188739+00
descriptionUnsafe thin wrappers around OS memory locking syscalls (mlock/munlock/madvise)
homepagehttps://github.com/thatnewyorker/os-memlock
repositoryhttps://github.com/thatnewyorker/os-memlock
max_upload_size
id1864716
size77,545
Gerard (thatnewyorker)

documentation

https://docs.rs/os-memlock

README

os-memlock

Docs

Quick links:

  • Detailed guide: docs/overview.md
  • API docs (docs.rs): https://docs.rs/os-memlock
  • Examples:
    • examples/simple.rs
    • examples/locked_vec.rs

Docs

The detailed guide covers:

  • Safety model and caller obligations: docs/overview.md#safety-model-and-caller-obligations
  • Platform support and behavior: docs/overview.md#cross-platform-behavior
  • Usage patterns and RAII wrappers: docs/overview.md#usage-patterns
  • Error model and diagnostics: docs/overview.md#error-model
  • Testing and CI: docs/overview.md#testing-and-ci
  • Security considerations and threat model: docs/overview.md#security-considerations
  • Integration checklist: docs/overview.md#integration-checklist

Small, focused crate providing thin, unsafe wrappers around OS memory-locking syscalls:

  • mlock / munlock (prevent swapping)
  • madvise_dontdump (best-effort exclusion from core dumps: Linux MADV_DONTDUMP, FreeBSD MADV_NOCORE)

This crate isolates the minimal unsafe FFI surface so higher-level modules can remain #![forbid(unsafe_code)]. The public functions are intentionally unsafe to make pointer-safety obligations explicit to callers.


Purpose

  • Provide a tiny, audit-friendly layer over platform syscalls used to lock memory pages and apply dump-exclusion hints.
  • Keep all unsafe and FFI details in a single, well-documented crate so the rest of the codebase can use a safe abstraction that validates inputs before calling into this crate when appropriate.
  • Expose a stable, minimal API that is easy to reason about and to wrap in safer helpers.

Crate API (surface)

The crate re-exports the platform-specific implementations at the crate root:

  • unsafe fn mlock(addr: *const std::os::raw::c_void, len: usize) -> std::io::Result<()>

    • Lock the pages containing the memory region so they are not swapped out.
    • On unsupported platforms, returns Err(io::ErrorKind::Unsupported).
  • unsafe fn munlock(addr: *const std::os::raw::c_void, len: usize) -> std::io::Result<()>

    • Unlock the pages, reversing mlock.
    • On unsupported platforms, returns Err(io::ErrorKind::Unsupported).
  • unsafe fn madvise_dontdump(addr: *mut std::os::raw::c_void, len: usize) -> std::io::Result<()>

    • Best-effort hint to exclude a mapping from core dumps (Linux: MADV_DONTDUMP, FreeBSD: MADV_NOCORE).
    • On unsupported platforms, returns Err(io::ErrorKind::Unsupported).

Notes on signatures:

  • The functions intentionally use raw pointers and usize lengths to mirror the OS call semantics and to avoid hiding important safety obligations behind false safety.
  • Zero-length regions are treated as a no-op and return Ok(()) for ergonomic callers.

Safety contract

All functions are unsafe. Callers must uphold the following preconditions for each call:

  1. The (addr, len) pair must denote a valid memory region that the caller owns for the duration of the call and for as long as the OS considers the lock to be held.

    • The range must be mapped into the process address space and addressable (initialized) memory. Passing invalid pointers is undefined behavior at the OS/FFI boundary.
  2. The memory region must not be concurrently deallocated, unmapped, or remapped while the system call is in-flight. Concurrent unmapping or reallocation may cause the OS call to operate on a different mapping and can lead to undefined behavior or kernel errors.

  3. Callers must ensure alignment and fractional-page concerns are addressed if required by their higher-level policy; the OS operates at page granularity, but mlock is defined on an arbitrary address and length.

  4. When using mlock to protect secrets, callers must consider:

    • Handling and limiting locked memory lifetime.
    • Zeroizing secrets before munlock/drop, where appropriate.
    • Observability: mlock failures may be transient or platform-dependent — be prepared to treat Err(Unsupported) and other error kinds as operational signals.
  5. For madvise_dontdump:

    • This is advisory and best-effort; the kernel may ignore or reject the hint.
    • Use it as a privacy/operational enhancement, not a strict security boundary.

Platform support & behavior

  • Unix (Linux, *BSD, macOS):

    • mlock and munlock call through to libc::mlock and libc::munlock.
    • madvise_dontdump:
      • On Linux: wraps madvise(..., MADV_DONTDUMP).
      • On FreeBSD: wraps madvise(..., MADV_NOCORE).
      • On macOS and other Unix targets: returns Err(io::ErrorKind::Unsupported).
  • Non-Unix platforms:

    • All functions return Err(io::ErrorKind::Unsupported).
    • The function signatures exist to preserve a consistent cross-platform API; callers should handle Unsupported gracefully.

Examples (usage guidance)

  • Minimal unsafe call (illustrative — not a full safety wrapper):

Use mlock to lock a buffer you control. Wrap calls in unsafe and uphold the safety contract:

unsafe { os_memlock::mlock(buf.as_ptr() as *const _, buf.len())?; }

Later, before drop/unmapping: unsafe { os_memlock::munlock(buf.as_ptr() as *const _, buf.len())?; }

Call madvise_dontdump on Linux/FreeBSD to reduce chance of core dump exposure: unsafe { os_memlock::madvise_dontdump(buf.as_mut_ptr() as *mut _, buf.len())?; }

  • Higher-level recommended pattern:
    • Prefer a safe wrapper in your application that:
      • Accepts owned buffers (e.g., a wrapper type),
      • Ensures the buffer lives for the duration of the lock,
      • Calls mlock at allocation or when the secret is installed,
      • Zeroizes the content before munlock and ensures munlock is called (via Drop).
    • See src/mem/locked.rs in this repository for an example of a safe LockedVec style wrapper.

Error handling and diagnostics

  • io::ErrorKind::Unsupported signals platform/build-time unavailability; do not treat it as a panic-worthy error unless your feature policy requires it.
  • Other OS errors (e.g., resource limits) will be returned as io::Error with kernel errno translated into std::io::Error. These must be handled by the caller or propagated with context.

Testing notes

  • Unit tests in the repository provide behavior verification in environments where syscalls are available. Tests exercise both success paths and fallback behavior.
  • Where platform syscalls are unavailable or require elevated privileges, tests should mock or stub the syscall provider rather than invoking real FFI.

macOS process-wide core-dump helper

macOS does not expose a per-region dump-exclusion advice via madvise (there is no MADV_DONTDUMP/MADV_NOCORE on Darwin). To offer a practical alternative, this crate provides opt-in, process-wide helpers:

  • disable_core_dumps_for_process():

    • Platform: macOS-only behavior; on other platforms this function returns io::ErrorKind::Unsupported.
    • Effect: Sets the process RLIMIT_CORE soft limit to 0 to disable generation of core dumps for the process.
    • Safety: Exposed as a safe function because it has no pointer/lifetime obligations; it returns io::Result<()> on failure/success.
    • Scope: Process-wide and inherited by child processes. This is not a per-buffer or per-region setting.
    • Privileges: Lowering the soft limit to 0 is typically permitted; raising limits back may require additional privileges or be disallowed by system policy.
    • Operational notes: In sandboxed or restricted environments, changing resource limits may fail. Handle errors and decide whether to degrade gracefully or fail closed, per your policy.
  • disable_core_dumps_with_guard() -> CoreDumpsDisabledGuard:

    • Platform: macOS-only; on other platforms this function returns io::ErrorKind::Unsupported.
    • Effect: Sets the process RLIMIT_CORE soft limit to 0 and returns a guard. When the guard is dropped, the previous limits are restored for the current process.
    • Scope: Process-wide while active. Child processes forked while disabled inherit the lowered limit and are not automatically “restored” by dropping the guard in the parent.
    • Safety: Safe API returning io::Result<CoreDumpsDisabledGuard>.
    • Privileges & operational notes: Same as above; restoration may fail under restrictive policies (restoration errors are best-effort and should be logged by callers if needed).

Recommended usage:

  • For temporary disabling (e.g., during sensitive operations), prefer disable_core_dumps_with_guard() to ensure restoration even on panic or early returns.
  • For a whole-process policy, call disable_core_dumps_for_process() early in startup.
  • Combine with mlock/munlock to reduce the risk of secrets being paged to disk.
  • Log or surface metrics if the helper is unsupported or fails, so you can detect drift from your intended security posture.

Windows process-wide error-dialog helpers

Windows does not provide a per-region dump-exclusion API analogous to MADV_DONTDUMP. To improve operational behavior (avoiding certain error UI), this crate provides opt-in, process-wide helpers:

  • set_windows_error_mode(new_mode: u32) -> io::Result<u32>:

    • Platform: Windows-only; on other platforms returns io::ErrorKind::Unsupported.
    • Effect: Calls SetErrorMode with the provided flags and returns the previous mode.
    • Scope: Process-wide; inherited by child processes created after the change.
    • Notes: This does not control crash dump contents and is not a security feature. It primarily suppresses certain error dialogs.
  • suppress_windows_error_dialogs_for_process() -> io::Result<u32>:

    • Platform: Windows-only; on other platforms returns io::ErrorKind::Unsupported.
    • Effect: Applies a common combination of flags (SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX) via SetErrorMode and returns the previous mode.
    • Scope: Process-wide; inherited by child processes created after the change.
    • Notes: This is best-effort UX/operational control and is not equivalent to per-region dump exclusion.

Recommended usage:

  • Call early in process startup if you want to suppress Windows error dialogs globally.
  • Save and restore the previous mode when temporarily changing settings to limit blast radius.
  • Treat these helpers as operational/UX tweaks, not security controls; combine with mlock/munlock for memory handling as needed.

License

This crate is dual-licensed under Apache-2.0 OR MIT; see Cargo.toml for details.


Commit count: 0

cargo fmt