memfd_create and Anonymous Memory Files

memfd_create(name, flags) creates a brand-new, nameless file that lives entirely in RAM — backed by tmpfs (or hugetlbfs) — and returns an open file descriptor to it. The file has no path in any directory, is never written to disk, and is automatically destroyed when the last open reference is closed (the descriptor, plus any mmap mappings and any copies passed to other processes). Because it is just a file you can ftruncate to a size, mmap to share, and pass to another process over a Unix-domain socket, it has become the modern, clean replacement for the old “open a /dev/shm file, then unlink it immediately” dance for making a shared memory buffer. It first appeared in Linux 3.17 (October 2014), authored by David Herrmann, and its companion feature — file sealing — is what makes it more than a convenience (Herrmann’s announcement; memfd_create(2)).

Mental Model

Think of a memfd as a tmpfs file with no name. The kernel’s tmpfs (the in-RAM filesystem also used for /dev/shm and /tmp on many systems) can hold files; ordinarily a file gets there by being created at a path. memfd_create short-circuits the path entirely: it calls shmem_file_setup() directly to build a fresh tmpfs inode and hands you a file descriptor without ever inserting the file into any directory tree. There is no name to collide with, no mountpoint to find, and no cleanup to forget — when every reference to that inode is gone, the inode and its pages are freed, exactly like any anonymous resource.

The name you pass is purely cosmetic: it shows up only in /proc/<pid>/fd/<fd> as a symlink target of the form /memfd:<name> (deleted), used for debugging and for tools like lsof. Two memfds created with the same name are completely independent files; the name is not a key, not a handle, and not visible to other processes except through your /proc entry.

flowchart LR
  A["Process A:<br/>fd = memfd_create('buf', MFD_CLOEXEC)"] --> B["nameless tmpfs inode<br/>(in RAM, 0 bytes)"]
  B --> C["ftruncate(fd, size)<br/>mmap(fd) -- fill data"]
  C --> D["sendmsg over AF_UNIX<br/>SCM_RIGHTS: pass the fd"]
  D --> E["Process B:<br/>receives a NEW fd to<br/>the SAME inode"]
  E --> F["mmap(fd) -- sees the<br/>identical physical pages"]
  F --> G["all fds closed +<br/>all mappings gone<br/>-> inode freed"]

How a memfd travels between two processes. What it shows: the buffer is created without any filesystem path, populated via mmap, and the file descriptor (not a name) is handed to a second process through a Unix socket’s SCM_RIGHTS ancillary data; the recipient gets its own descriptor referring to the same in-RAM inode and maps the identical physical pages. The insight to take: the memfd’s identity is the open file description itself, so lifetime is reference-counted across processes — nobody has to remember to unlink anything, and the buffer disappears the instant the last holder lets go. The fd-passing step is owned by Passing File Descriptors with SCM_RIGHTS; the buffer-handoff protocol by Passing memfd Buffers Between Processes.

Why memfd Replaced the Old Dances

Before memfd_create, a process that wanted an anonymous, shareable RAM buffer had three awkward options, each with a real defect (Herrmann 2014):

  1. open() a file in /tmp, then unlink() it. This needs a writable filesystem mount, leaves a window between open and unlink where the file is visible (and can be opened by a racing process or left behind if you crash in between), and on a real disk-backed /tmp the data can hit storage.

  2. shm_open() on /dev/shm, then shm_unlink(). This is the POSIX shared-memory idiom (see POSIX Shared Memory and shm_open). It is RAM-backed via tmpfs, but it still requires a global name in a global namespace (/dev/shm/<name>), so two unrelated programs can collide on the same name, and a crash between shm_open and shm_unlink leaks a named object that survives in /dev/shm until someone cleans it up.

  3. open() with O_TMPFILE. This creates an unnamed temporary file directly, avoiding the unlink race — but it still needs a mountpoint that supports O_TMPFILE, and it does not provide sealing.

memfd_create removes the mountpoint requirement, the global namespace, and the cleanup race in one stroke: “memfd_create does not require a local mount-point… there are no name-clashes and no global registry” (Herrmann 2014). The buffer is anonymous from birth, and its lifetime is automatic. But the decisive advantage is that a memfd created with MFD_ALLOW_SEALING can be sealed so a recipient can trust it — something none of the older approaches can offer. That trust model is the whole subject of File Sealing with F_ADD_SEALS.

Mechanical Walk-through

The system call is int memfd_create(const char *name, unsigned int flags), declared in <sys/mman.h> under _GNU_SOURCE. Its kernel implementation lives in mm/memfd.c as SYSCALL_DEFINE2(memfd_create, ...) (v6.12 mm/memfd.c). Walking the 6.12 source, the syscall does the following:

  1. Validate flags. If MFD_HUGETLB is not set, any bit outside MFD_ALL_FLAGS (which is MFD_CLOEXEC | MFD_ALLOW_SEALING | MFD_HUGETLB | MFD_NOEXEC_SEAL | MFD_EXEC) yields -EINVAL. If MFD_HUGETLB is set, the huge-page-size encoding bits (MFD_HUGE_MASK << MFD_HUGE_SHIFT) are additionally allowed. It is also -EINVAL to set both MFD_EXEC and MFD_NOEXEC_SEAL at once, since they are opposites.

  2. Apply the noexec sysctl policy. check_sysctl_memfd_noexec(&flags) consults the per-PID-namespace vm.memfd_noexec setting (covered below) and may auto-add MFD_NOEXEC_SEAL or MFD_EXEC, or reject the call with -EACCES.

  3. Copy and bound the name. strnlen_user() reads the user string; it must be at most MFD_NAME_MAX_LEN bytes, which is NAME_MAX (255) minus the length of the "memfd:" prefix — i.e. a 249-byte limit (v6.12 mm/memfd.c). The kernel prepends "memfd:" to form the displayed name. A name longer than the limit is -EINVAL; a bad pointer is -EFAULT.

  4. Allocate the fd. get_unused_fd_flags(MFD_CLOEXEC ? O_CLOEXEC : 0) reserves a descriptor with the close-on-exec flag if requested.

  5. Create the backing file. For MFD_HUGETLB, it calls hugetlb_file_setup(...) to back the file with huge pages from hugetlbfs; otherwise it calls shmem_file_setup(name, 0, VM_NORESERVE) to make a zero-length tmpfs-backed file. VM_NORESERVE means no swap space is pre-reserved — pages are accounted as they are actually used. The file’s mode gets FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE and O_LARGEFILE, so it supports seeking and positional I/O and large sizes.

  6. Set default seals. If MFD_NOEXEC_SEAL was set, the kernel clears the file’s execute bits (inode->i_mode &= ~0111) and stamps the inode’s seal set with F_SEAL_EXEC, while clearing the default F_SEAL_SEAL. If instead MFD_ALLOW_SEALING was set, it clears F_SEAL_SEAL so seals can be added later. If neither flag is set, the file keeps the implicit F_SEAL_SEAL that tmpfs/hugetlbfs inodes carry by default, which is why an ordinary memfd silently rejects all sealing attempts — see File Sealing with F_ADD_SEALS.

  7. Install and return. fd_install(fd, file) publishes the descriptor, name is freed, and the fd is returned.

The returned descriptor starts at size 0; you must ftruncate(fd, n) (or write to it) before mapping n bytes, or your mmap will succeed but touching beyond the file’s end faults with SIGBUS. After sizing, you mmap(NULL, n, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0) to get a shared view backed by the file’s pages; a write through that mapping is immediately visible through any other mapping of the same inode in any process, which is the entire point.

The v6.18 LTS source is functionally identical to v6.12 for these paths; the only differences are a comment typo fix and an internal is_write_sealed() helper plus refactored huge-page allocation accounting (verified by diffing v6.12 against v6.18 mm/memfd.c). The flag bit values are unchanged between the two LTS lines.

The Flags, One by One

The flag bit values are defined in include/uapi/linux/memfd.h (v6.12):

  • MFD_CLOEXEC (0x0001) — sets O_CLOEXEC (close-on-exec) on the returned descriptor, so it is not inherited across an execve(2). As with all fds, you almost always want this unless you are deliberately handing the descriptor to a child program; leaking an unsealed, writable memfd across exec is a footgun.

  • MFD_ALLOW_SEALING (0x0002) — permits later fcntl(fd, F_ADD_SEALS, ...) calls by clearing the default F_SEAL_SEAL. Without this flag, the file is born sealed-against-sealing and no seals can ever be added. This is the gate to the entire sealing feature; see File Sealing with F_ADD_SEALS.

  • MFD_HUGETLB (0x0004, since Linux 4.14) — backs the file with explicit huge pages from hugetlbfs rather than ordinary tmpfs pages. Useful for very large shared buffers where reducing TLB (translation lookaside buffer) pressure matters, e.g. databases or virtual-machine memory. The huge-page size is selected by OR-ing in one of the MFD_HUGE_* encodings (MFD_HUGE_2MB, MFD_HUGE_1GB, and others) shifted into the high bits; the size must be one the hardware and kernel actually support. Note that in older kernels MFD_HUGETLB was mutually exclusive with MFD_ALLOW_SEALING; sealing support for hugetlbfs memfds was added later (the 6.12 F_ALL_SEALS path covers both shmem_file and is_file_hugepages inodes via memfd_file_seals_ptr).

Uncertain

Verify: the exact kernel release in which MFD_HUGETLB memfds became sealable (i.e. when the old MFD_HUGETLB+MFD_ALLOW_SEALING -EINVAL restriction was lifted). Reason: the v6.12 source clearly supports seals on hugetlbfs inodes via HUGETLBFS_I(...)->seals, but I did not pin the introducing version against a primary changelog. To resolve: bisect the memfd_file_seals_ptr() / hugetlbfs-seal commit history or check the fcntl/memfd_create man-page CHANGES. uncertain

  • MFD_NOEXEC_SEAL (0x0008, since Linux 6.3) — creates the memfd without the execute bit (mode 0666 instead of 0777) and seals it with F_SEAL_EXEC so it can never be chmod-ed back to executable (LWN 917910; v6.12 mm/memfd.c). This is the secure default for buffers that should hold data, not code.

  • MFD_EXEC (0x0010, since Linux 6.3) — explicitly requests the historical behaviour: a memfd created executable (mode 0777), suitable for legitimately loading code (some language runtimes and runc use this). It exists so that, after 6.3, programs can declare their intent rather than relying on the silent default, and so the kernel can warn or refuse when neither flag is given.

The reason MFD_NOEXEC_SEAL / MFD_EXEC were introduced in 6.3 is a real attack class: historically every memfd was created executable, and the syscall offered no way to change that. On a hardened, “write-xor-execute” (W^X) system such as ChromeOS — where all legitimate executables come from a verified-boot read-only root filesystem — an attacker who can write to a memfd and then execve it gains an arbitrary-code-execution / noexec-bypass primitive: a “confused-deputy” path around the policy that all code must come from verified storage (LWN 917910). Making memfds non-executable-and-sealed by policy closes that door.

The vm.memfd_noexec sysctl

To roll this out without breaking existing software, the noexec behaviour is governed by a per-PID-namespace sysctl, vm.memfd_noexec, with three levels (kernel.org mfd_noexec docs):

  • 0 (MEMFD_NOEXEC_SCOPE_EXEC) — a memfd_create call that passes neither flag behaves as if MFD_EXEC was set (the old executable default).
  • 1 (MEMFD_NOEXEC_SCOPE_NOEXEC_SEAL) — such a call behaves as if MFD_NOEXEC_SEAL was set (non-executable, sealed).
  • 2 (MEMFD_NOEXEC_SCOPE_NOEXEC_ENFORCED) — such a call is rejected with -EACCES, forcing every caller to be explicit and forbidding executable memfds entirely.

The setting is hierarchical and one-way restrictive: a child PID namespace inherits the value at creation and the kernel searches from the current namespace up to the root, applying the most restrictive setting found; a child can tighten but not relax it (kernel.org docs). This is what check_sysctl_memfd_noexec() in mm/memfd.c enforces.

Worked Example

The following program creates a sealed shared buffer end to end. Each line is annotated.

#define _GNU_SOURCE
#include <sys/mman.h>      /* memfd_create, mmap */
#include <unistd.h>        /* ftruncate, close */
#include <fcntl.h>         /* fcntl, F_ADD_SEALS */
#include <string.h>
#include <stdio.h>
 
int main(void) {
    /* Create a nameless RAM file. MFD_CLOEXEC: don't leak it across exec.
       MFD_ALLOW_SEALING: we intend to seal it later. */
    int fd = memfd_create("shared-buf", MFD_CLOEXEC | MFD_ALLOW_SEALING);
    if (fd < 0) { perror("memfd_create"); return 1; }
 
    /* The file starts at size 0; size it to one page (4096 bytes). */
    if (ftruncate(fd, 4096) < 0) { perror("ftruncate"); return 1; }
 
    /* Map it shared+writable and fill it with data. */
    char *p = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (p == MAP_FAILED) { perror("mmap"); return 1; }
    strcpy(p, "hello from a memfd");
 
    /* CRITICAL: drop the writable mapping before sealing writes.
       F_SEAL_WRITE fails with EBUSY while a writable shared mapping lives. */
    munmap(p, 4096);
 
    /* Freeze the buffer: no growing, no shrinking, no writing, and no
       further seals. A recipient that maps this fd can now trust it. */
    if (fcntl(fd, F_ADD_SEALS,
              F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_WRITE | F_SEAL_SEAL) < 0) {
        perror("F_ADD_SEALS"); return 1;
    }
 
    /* fd is now a read-only, fixed-size, trustable buffer.
       Pass it to another process over an AF_UNIX socket (SCM_RIGHTS),
       or hand it to a child. When the last reference closes, the
       inode and its single page are freed automatically. */
    close(fd);
    return 0;
}

The crucial subtlety is the munmap before the F_SEAL_WRITE: as the kernel’s memfd_add_seals shows, adding F_SEAL_WRITE calls mapping_deny_writable(), which fails if any writable shared mapping currently exists, and the seal attempt returns -EBUSY (v6.12 mm/memfd.c). The full seal semantics are the subject of File Sealing with F_ADD_SEALS.

You can inspect the file from the shell while the program runs: ls -l /proc/<pid>/fd/<fd> shows a symlink to /memfd:shared-buf (deleted), and cat /proc/<pid>/maps shows the mapping backed by that anonymous inode.

Failure Modes and Common Misunderstandings

  • mmap succeeds but access faults with SIGBUS. A fresh memfd is 0 bytes. If you mmap 4096 bytes of a 0-byte memfd and then dereference the pointer, you read past end-of-file and get SIGBUS, not SIGSEGV. The fix is to ftruncate to the intended size first (or write to extend it). This is the single most common beginner mistake.

  • fcntl(F_ADD_SEALS) returns EINVAL on a memfd you “definitely” created sealable. Almost always you forgot MFD_ALLOW_SEALING (or, on 6.3+, the file got F_SEAL_EXEC-sealed via MFD_NOEXEC_SEAL and you are now blocked by F_SEAL_SEAL because… no — MFD_NOEXEC_SEAL clears F_SEAL_SEAL). Without MFD_ALLOW_SEALING, the inode keeps its default F_SEAL_SEAL and every seal addition fails. memfd_file_seals_ptr() returns the inode’s seal word only for tmpfs/hugetlbfs inodes; on any other fd, sealing operations return -EINVAL.

  • F_SEAL_WRITE returns EBUSY. A writable, shared mapping of the file still exists somewhere — in your process or any process holding the fd. You must munmap all writable shared maps first. There is also a subtle case: outstanding kernel pins from get_user_pages() (direct I/O, AIO, GUP by drivers) hold extra page references; memfd_wait_for_pins() waits up to roughly 150 ms for them to drain and returns -EBUSY if they do not (v6.12 mm/memfd.c).

  • memfd_create returns EINVAL on an old kernel. The syscall is only present from 3.17, and individual flags from later releases (MFD_HUGETLB 4.14; MFD_NOEXEC_SEAL/MFD_EXEC 6.3). Passing a flag the running kernel does not know yields -EINVAL. There is no libc fallback; you must detect support at runtime.

  • memfd_create returns EACCES unexpectedly on 6.3+. Some namespace up the tree set vm.memfd_noexec=2, so you must pass MFD_NOEXEC_SEAL (or MFD_EXEC if you truly need executable, which value 2 forbids). This bit several container runtimes when distributions shipped 6.5+ kernels with stricter defaults; programs that called memfd_create with no exec flag started seeing kernel warnings or EACCES.

  • Memory accounting and vm.max_map_count. A memfd’s pages count against the creating process’s memory and, because they are tmpfs, against swap if swapped. Very large memfds can hit RLIMIT_AS on mmap or the system’s memory limits; there is no on-disk overflow.

Alternatives and When to Choose Them

  • POSIX Shared Memory and shm_open (shm_open + mmap). Choose this when you need a named rendezvous: an unrelated process that knows the agreed name /foo can open the same object without anyone passing a descriptor. memfd is anonymous, so it requires an fd-passing channel (a Unix socket or inheritance) to reach another process. The trade-off is that named POSIX shm has the cleanup/leak and name-collision problems memfd was designed to avoid.

  • Anonymous Shared Memory (mmap(MAP_SHARED|MAP_ANONYMOUS)). Choose this when only related processes need the buffer: an anonymous shared mapping is inherited across fork but has no file descriptor, so it cannot be passed to an unrelated process and cannot be sealed. memfd gives you a descriptor and sealing at the cost of one syscall.

  • System V shared memory (shmget/shmat). Legacy; kernel-persistent objects keyed by an integer that survive process death and leak unless ipcrm-ed. Avoid in new code; memfd is strictly cleaner.

  • open(O_TMPFILE) / /dev/shm open-then-unlink. The historical approaches memfd replaces; choose them only on pre-3.17 kernels.

The decisive reason to choose memfd over all of these for a modern shared-buffer protocol is sealing: only a sealed memfd lets a recipient cryptographically-style trust that the buffer will not change size or content underneath it. That is why it underpins Wayland graphics buffers, sandboxed media decoders, and dma-buf heap handoffs — covered in File Sealing with F_ADD_SEALS and Passing memfd Buffers Between Processes.

Production Notes

memfd is everywhere in modern Linux userspace. Wayland compositors and clients exchange wl_shm pool buffers as sealed memfds so the compositor can map an untrusted client’s buffer without defensive SIGBUS handling (Herrmann 2014). Android migrated its ashmem shared-memory driver onto memfd + sealing, which is what drove the addition of F_SEAL_FUTURE_WRITE in Linux 5.1 (Fernandes patch). systemd and various daemons use memfd for passing logs and credentials. Container runtimes (runc, crun) copy the runtime binary into a memfd before re-exec to defend against a compromised container overwriting the host binary — ironically one of the legitimate uses of MFD_EXEC, and a reason value 2 of vm.memfd_noexec can break naive runtimes. The flip side — memfd as an attacker tool for fileless code execution (write a payload to a memfd, execve it, leave nothing on disk) — is precisely why MFD_NOEXEC_SEAL and the noexec sysctl were added; security tooling now watches for memfd: entries in /proc/<pid>/maps as an indicator of in-memory malware.

See Also