A memfd_create() file can begin as writable shared state and later become progressively more constrained. File seals make that transition monotonic: successful seals are properties of the inode, affect every descriptor referring to it, and cannot be removed.
That property is useful when one process prepares bytes and then transfers a descriptor to another process. The receiver can inspect kernel-enforced restrictions instead of relying only on a protocol promise that the producer has stopped changing the object.
Sealing must be enabled at creation
memfd_create() creates an anonymous file and returns an O_RDWR file descriptor. Sealing is opt-in through MFD_ALLOW_SEALING.
int fd = memfd_create("payload", MFD_CLOEXEC | MFD_ALLOW_SEALING);
if (fd == -1)
handle_error();With MFD_ALLOW_SEALING, the initial seal set is empty. Without it, the file starts with F_SEAL_SEAL, which prevents later additions to the seal set.
The name passed to memfd_create() is diagnostic rather than a filesystem pathname. The object remains file-like: it can be sized, read, written, mapped, inherited across fork(), or transferred as a file descriptor.
Seals constrain separate mutation dimensions
F_ADD_SEALS adds a bit mask of restrictions through fcntl(). The major seals do not all prohibit the same operation.
F_SEAL_SHRINK blocks reductions in file size. F_SEAL_GROW blocks increases. F_SEAL_WRITE blocks content modification through operations such as write() and also prevents creation of new shared writable mappings. Size changes remain a separate dimension, so a fixed immutable payload commonly combines write, grow, and shrink seals.
int seals = F_SEAL_WRITE | F_SEAL_GROW | F_SEAL_SHRINK;
if (fcntl(fd, F_ADD_SEALS, seals) == -1)
handle_error();After a seal is installed successfully, operations prohibited by that seal fail with EPERM. Existing descriptors do not bypass the restriction because seals belong to the inode rather than to one descriptor.
Writable mappings affect the transition point
F_SEAL_WRITE cannot be added while a writable shared mapping of the file exists. In that state, F_ADD_SEALS fails with EBUSY.
A producer that populates a memfd through MAP_SHARED | PROT_WRITE therefore has a concrete transition boundary: finish mutation, remove writable shared mappings, then install F_SEAL_WRITE.
F_SEAL_FUTURE_WRITE has a different boundary. It blocks later write() calls and new writable mappings while allowing shared writable mappings created before the seal to continue modifying the file. That distinction supports handoff designs in which an existing producer mapping remains active but newly arriving peers cannot acquire equivalent write access.
F_SEAL_SEAL closes the policy itself
The seal set can itself be sealed. Once F_SEAL_SEAL is present, later F_ADD_SEALS calls fail with EPERM.
if (fcntl(fd, F_ADD_SEALS,
F_SEAL_WRITE | F_SEAL_GROW |
F_SEAL_SHRINK | F_SEAL_SEAL) == -1)
handle_error();This creates two monotonic layers. Individual seals reduce permitted file mutations, and F_SEAL_SEAL prevents further changes to that restriction set. It does not remove existing restrictions or make the object more permissive.
A receiver can query the current mask with F_GET_SEALS and reject a descriptor whose restrictions are weaker than the protocol requires.
int seals = fcntl(fd, F_GET_SEALS);
if (seals == -1)
handle_error();
int required = F_SEAL_WRITE | F_SEAL_GROW | F_SEAL_SHRINK;
if ((seals & required) != required)
reject_payload();Descriptor transfer preserves the same sealed object
Passing a memfd through a UNIX domain socket with SCM_RIGHTS gives the receiving process a descriptor for the same underlying file. The seal set therefore crosses that process boundary as inode state.
This differs from copying bytes into a new local buffer. A copy creates separate storage whose integrity depends on the copy point. A sealed memfd keeps one shared object while restricting which later mutations the kernel will accept.
Seals also do not provide authenticity, confidentiality, or authorization for descriptor transfer. They constrain operations on the file after the relevant seals exist. A protocol still needs its own rules for selecting peers, validating content, and deciding which seal mask is sufficient.
The guarantee is narrower than general immutability
File sealing is a Linux-specific mechanism with explicit operation-level semantics. F_SEAL_WRITE alone does not freeze file size. F_SEAL_GROW alone does not block writes inside the existing extent. F_SEAL_FUTURE_WRITE deliberately preserves mutation through older writable shared mappings.
The useful invariant comes from the exact seal combination and the point at which it is installed. Once those restrictions are present, the kernel enforces a one-direction transition from a more mutable memfd toward a more constrained shared object.