A broker can allocate a memory-backed object, populate it, and pass its file descriptor to another process over a UNIX domain socket. The receiver may treat the bytes as immutable configuration, compiled code, or a serialized artifact. That assumption is unsafe if the sender or another holder can still alter the same inode after validation. Linux memfd_create() and file seals provide a kernel-enforced way to narrow that mutation surface without assigning the object a persistent filesystem pathname.

The security property comes from seals, not from anonymity. A memfd is a file with normal descriptor and mapping semantics; without the relevant seals, writable holders can change its contents. Sealing converts selected mutation operations into permanent kernel restrictions shared by every descriptor that refers to the inode.

Sealing is an inode property, not a descriptor promise

memfd_create() returns a descriptor opened for reading and writing. Passing MFD_ALLOW_SEALING starts the new file with an empty seal set. Without that flag, the initial set contains F_SEAL_SEAL, which blocks adding further seals and therefore prevents a later transition into a more restrictive state.

Seals are added with fcntl(fd, F_ADD_SEALS, mask) and inspected with F_GET_SEALS. They belong to the inode rather than to one descriptor. A process cannot evade an applied seal by duplicating the descriptor, inheriting it across fork(), or receiving another descriptor for the same object. Seals can be added but not removed.

This makes the transition suitable for producer-consumer handoff. A producer can construct an object while it is mutable, add the required seals, verify the resulting seal mask, and only then expose the descriptor to a less trusted component. Descriptor transfer itself does not create immutability; the seal state does.

Write sealing and size sealing cover different operations

F_SEAL_WRITE blocks ordinary writes and new shared writable mappings. It also blocks hole punching that would modify file contents. The seal cannot be added while a writable shared mapping already exists; F_ADD_SEALS fails with EBUSY in that condition. The producer therefore has to remove such mappings before completing a strict write seal.

Size has separate controls. F_SEAL_SHRINK prevents reducing the file, while F_SEAL_GROW prevents increasing it. A consumer that expects both stable bytes and stable length needs the applicable size seals as well as a write seal. Treating F_SEAL_WRITE as a complete object-shape guarantee leaves size-changing operations outside the intended boundary.

F_SEAL_SEAL has another role: it freezes the seal set itself. It is commonly applied after the desired restrictions are present. Applying it too early locks the object into its current seal state and prevents later additions.

A compact handoff can therefore use a sequence such as:

int fd = memfd_create("policy", MFD_CLOEXEC | MFD_ALLOW_SEALING);
/* populate fd, then remove shared writable mappings */

int seals = F_SEAL_WRITE |
            F_SEAL_GROW |
            F_SEAL_SHRINK |
            F_SEAL_SEAL;

if (fcntl(fd, F_ADD_SEALS, seals) == -1)
    /* abort handoff */;

The call result matters. A design that continues after a failed sealing operation has only an application convention, not the intended kernel restriction.

F_SEAL_FUTURE_WRITE deliberately leaves existing mappings alive

F_SEAL_FUTURE_WRITE serves a different sharing model. It blocks write() and prevents creation of new shared writable mappings, but shared writable mappings created before the seal remain able to modify the file. This permits one process to retain a mutable mapping while later recipients are constrained to read-only access paths.

That property is useful for live shared buffers, but it is weaker than immutable handoff. A receiver that validates bytes and then assumes they cannot change must not equate F_SEAL_FUTURE_WRITE with F_SEAL_WRITE while an earlier writable mapping can survive.

The distinction is temporal. F_SEAL_WRITE requires conflicting writable shared mappings to be gone before the seal is accepted. F_SEAL_FUTURE_WRITE can coexist with those prior mappings and constrains only later write avenues. Security review therefore has to account for mappings created before the seal transition, not just descriptors held afterward.

Descriptor lifetime and descriptor inheritance remain separate boundaries

A sealed memfd still follows ordinary descriptor lifetime rules. MFD_CLOEXEC sets FD_CLOEXEC on the returned descriptor so it is closed during a successful execve(). Without close-on-exec, a descriptor can cross an execution boundary and become available to a program that was not meant to receive it.

File sealing does not replace descriptor-capability discipline. A read-only consumer may still be able to duplicate or transfer its descriptor. Access to the bytes remains access to the bytes; the seals restrict mutation, not disclosure or onward delegation.

The memfd name also supplies no authorization boundary. It appears in /proc descriptor links for diagnostic purposes, but it does not act as a filesystem pathname whose secrecy controls access. The relevant authority is possession or acquisition of a descriptor, subject to the surrounding process and /proc access controls.

Executable state has its own seal semantics

Modern Linux also defines F_SEAL_EXEC. It prevents later changes to execute mode bits and implicitly adds the grow, shrink, write, and future-write seals. The implied write restrictions keep an executable memfd from remaining freely mutable after that transition.

Linux also provides creation flags and a pid-namespace policy for non-executable memfds. MFD_NOEXEC_SEAL creates a non-executable memfd with F_SEAL_EXEC set, while MFD_EXEC requests executable state. The vm.memfd_noexec policy can change or restrict behavior for calls that omit an explicit executable-state flag.

These controls are kernel-version and deployment-policy dependent. Code that relies on them needs an explicit minimum kernel contract and should treat namespace policy as part of the execution environment rather than as a property inferred from the memfd name.

Seals constrain mutation; they do not authenticate provenance

A fully write-sealed memfd can provide a stable byte sequence after the sealing transition, but it does not identify who produced those bytes or whether they were approved. If an attacker controls the producer before sealing, the kernel can faithfully preserve attacker-chosen content.

This separates two security questions. File seals answer whether specified mutation operations remain possible after a transition. Provenance and approval require another mechanism, such as a trusted producer, a verified digest, a signature policy, or an authenticated control channel that binds the descriptor to expected content.

The same separation applies to parsing. Stable bytes remove a class of time-of-check/time-of-use mutation races between cooperating components, but malformed immutable data can still trigger parser defects. Sealing strengthens the object-stability boundary; it does not validate the object format.

The strongest handoff is a verified state transition

A robust memfd handoff is defined by observable kernel state: the producer finishes mutation, removes incompatible writable mappings, adds the required seals, checks success, and transfers the descriptor only after the transition. The receiver can use F_GET_SEALS when its policy depends on a particular seal set rather than trusting metadata supplied by the sender.

That model keeps the guarantee narrow and testable. F_SEAL_WRITE closes later write paths only after prior shared writable mappings are gone; size seals stabilize length; F_SEAL_SEAL prevents later seal additions; executable-state controls address a separate permission dimension. Combined deliberately, these mechanisms turn a mutable shared file into an object whose remaining mutation surface is defined by the kernel rather than by process convention.