A Linux memfd can begin as a writable anonymous file and later acquire restrictions that cannot be removed. The restrictions belong to the inode, so transferring or duplicating a descriptor does not create a less restricted view. Once a seal is added successfully, every descriptor referring to that inode is subject to it.

This makes sealing different from descriptor access modes. A descriptor can carry local flags, while a seal changes the mutation boundary of the shared file object itself.

Sealing must be enabled at creation

memfd_create() returns a descriptor for an anonymous file. With MFD_ALLOW_SEALING, the initial seal set is empty and F_ADD_SEALS can add restrictions later.

Without MFD_ALLOW_SEALING, the file starts with F_SEAL_SEAL. That seal blocks subsequent additions, so the absence of the creation flag is not equivalent to an empty, unrestricted sealing policy.

The seal set can be inspected with F_GET_SEALS. Adding a seal requires a writable descriptor, but enforcement is attached to the inode rather than only to the descriptor used for F_ADD_SEALS.

The seal set only moves toward more restriction

F_ADD_SEALS performs an additive transition:

S_next = S_current | S_requested

There is no operation that removes a seal. Repeating an already present seal is a no-op unless F_SEAL_SEAL has already closed the seal set against further F_ADD_SEALS calls.

This monotonic property matters when a memfd crosses process boundaries. A receiving process can inspect the current set, but it cannot rely on a later transition back to a more mutable state. If F_SEAL_SEAL is absent, a process holding a writable descriptor may still add further restrictions.

Size seals constrain different directions

F_SEAL_SHRINK prevents operations that reduce the file size. Growing the file remains possible.

F_SEAL_GROW prevents operations that increase the file size. Shrinking remains possible.

Applying both fixes the size against growth and shrinkage, but does not by itself make existing bytes immutable. Size stability and content stability are separate properties.

That distinction is important for shared mappings. A peer that cannot truncate the object can no longer create a shorter backing file through ordinary size-changing operations, but bytes inside the retained range may still be writable unless a write-related seal also applies.

F_SEAL_WRITE requires existing shared writers to disappear

F_SEAL_WRITE blocks writes and new shared writable mappings. Linux also rejects an attempt to add this seal with EBUSY while a writable shared mapping already exists.

The ordering constraint is therefore explicit:

populate data
    |
remove shared writable mappings
    |
add F_SEAL_WRITE
    |
share or map read-only

The kernel does not retroactively revoke an active shared writable mapping to make F_SEAL_WRITE succeed. The mapping must first be removed.

This boundary prevents a file from being declared fully write-sealed while an existing mapping still provides a write path.

F_SEAL_FUTURE_WRITE preserves earlier shared mappings

F_SEAL_FUTURE_WRITE has a different boundary. After it is set, write() and new shared writable mmap() operations fail, but shared writable mappings created before the seal can continue modifying the file.

That property supports a producer that retains an established mapping while preventing newly arriving peers from obtaining a new write path. It is not equivalent to immutable content: the pre-existing mapping remains an authorized mutation channel.

The difference between the two write seals is temporal. F_SEAL_WRITE requires the writable shared-mapping set to be empty at seal time; F_SEAL_FUTURE_WRITE draws a boundary between mappings established before and after the transition.

F_SEAL_SEAL freezes policy, not file data

F_SEAL_SEAL prevents future F_ADD_SEALS operations. It does not automatically imply F_SEAL_WRITE, F_SEAL_GROW, or F_SEAL_SHRINK.

A file sealed only with F_SEAL_SEAL can therefore remain writable and resizable according to its other state, while its sealing policy can no longer be strengthened. Ordering matters when constructing a final policy: restrictions intended for the final object must be added before the seal set itself is closed.

A typical immutable-data state can be expressed as:

F_SEAL_WRITE | F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL

The exact set should match the intended mutation boundary rather than treating F_SEAL_SEAL as a shorthand for immutability.

Descriptor transfer preserves the same sealed object

Passing a memfd over a UNIX domain socket with SCM_RIGHTS, inheriting it across fork(), or opening a suitable /proc/PID/fd/FD reference can produce another descriptor for the same underlying file. The seal set remains associated with that file.

This gives sealing a useful IPC property: the producer can establish kernel-enforced restrictions before handing the object to another process. The consumer does not receive a private snapshot; it receives access to the same inode under the restrictions already attached to it.

The boundary is narrow but strong. Seals constrain specified file mutations. They do not authenticate the producer, validate the bytes, assign application meaning to the data, or replace protocol-level checks. Their role is to make selected mutation capabilities disappear monotonically from a shared file object.