$RodHat_
Syscall reference · Memory

mlock(2)

Lock pages into RAM so they can't be swapped out; mlockall() does the whole address space.

Since
POSIX.1-1996 (SVr4)check your kernel →
glibc
unknown (mlock2(): glibc 2.27)
System
Linux
Man page
mlock(2)

Synopsis

#include <sys/mman.h>

int mlock(const void addr[size], size_t size);
int mlock2(const void addr[size], size_t size, unsigned int flags);
int munlock(const void addr[size], size_t size);

int mlockall(int flags);
int munlockall(void);

Rod's take

Your private key leaks three ways: swap, core dumps and fork, and mlock only fixes the first. Lock the pages, and if mlockall comes back ENOMEM, die. Logging a warning and carrying on means the key hits swap during some 3am memory crunch and nobody ever notices. Raise RLIMIT_MEMLOCK or hold CAP_IPC_LOCK. Then add MADV_DONTDUMP for crash dumps, MADV_WIPEONFORK so children see zeros, and explicit_bzero when you're done, because the compiler will happily delete your memset. None of it stops root reading your memory. It stops the boring leaks.

Flags and arguments that matter

NameWhat it does
MCL_CURRENTmlockall(): lock every page currently mapped into the address space.
MCL_FUTUREmlockall(): lock pages mapped in the future too; later mmap/brk can fail if over the limit.
MCL_ONFAULTLinux 4.4With MCL_CURRENT/MCL_FUTURE: lock pages as they're faulted in instead of populating them up front.
MLOCK_ONFAULTmlock2(), Linux 4.4mlock2(): lock resident pages now, the rest of the range when they're first faulted in.
RLIMIT_MEMLOCKCaps how much an unprivileged process can lock (since Linux 2.6.9). CAP_IPC_LOCK bypasses it.

Errors you will actually hit

errnoMeaning
ENOMEMWould exceed the RLIMIT_MEMLOCK soft limit without CAP_IPC_LOCK, or the range isn't fully mapped.
EPERMCaller is not privileged but needs CAP_IPC_LOCK for the requested operation.
EAGAINSome or all of the specified range could not be locked.
EINVALaddr+size overflowed, unknown flags, or MCL_ONFAULT without MCL_CURRENT/MCL_FUTURE.

Example

unsigned char *alloc_secret(size_t len) {
    unsigned char *buf;
    if (posix_memalign((void **)&buf, getpagesize(), len) != 0)
        return NULL;
    if (mlock(buf, len) != 0) { free(buf); return NULL; }
    madvise(buf, len, MADV_DONTDUMP);
    madvise(buf, len, MADV_WIPEONFORK);
    return buf;
}

void free_secret(unsigned char *buf, size_t len) {
    explicit_bzero(buf, len);
    munlock(buf, len);
    free(buf);
}

Trimmed from the full walkthrough in mlock, MADV_DONTDUMP, and where your secrets actually leak.

BSD equivalent

FreeBSD: mlock(2)/mlockall(2) (mlock since 4.4BSD); madvise(2) MADV_NOCORE for core-dump exclusion. OpenBSD: mlock(2)/mlockall(2) (4.4BSD).

The long version, from the console

Sources

  1. mlock(2), man7.org
  2. madvise(2), man7.org
  3. mlock(2), FreeBSD
  4. madvise(2), FreeBSD
  5. mlock(2), OpenBSD

Version numbers are read off the cited man pages, not memory. Spot one that's wrong? Tell Rod.