A memfd_create() descriptor names an anonymous file whose storage lives in memory-backed filesystem infrastructure. By itself, descriptor handoff does not freeze that object: a process retaining suitable access can still write bytes, truncate the file, or extend it. Linux file seals add kernel-enforced restrictions that can make selected mutations fail after the producer declares the object complete.
This changes shared-memory handoff from a convention into a state transition enforced at the file object.
Seals attach to the file, not one descriptor
A memfd can be created with MFD_ALLOW_SEALING:
int fd = memfd_create("payload", MFD_CLOEXEC | MFD_ALLOW_SEALING);Without MFD_ALLOW_SEALING, the initial seal set includes F_SEAL_SEAL, preventing later additions. With sealing enabled, fcntl() can add restrictions:
int seals = F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_WRITE | F_SEAL_SEAL;
if (fcntl(fd, F_ADD_SEALS, seals) == -1) {
perror("F_ADD_SEALS");
}F_GET_SEALS reports the current set. Seals are properties of the underlying inode, so duplicating the descriptor or passing it through a UNIX domain socket does not create an unsealed copy.
F_SEAL_SEAL closes the seal set itself: after it is installed, no additional seals can be added. Existing seals cannot be removed.
Size seals and write seals protect different mutations
F_SEAL_SHRINK rejects operations that reduce the file size. F_SEAL_GROW rejects operations that increase it. They are independent from byte modification inside the existing extent.
F_SEAL_WRITE blocks writes and also prevents writable shared mappings from being created. Adding it can fail with EBUSY while writable shared mappings already exist. The producer therefore has to account for mapping lifetime before attempting the transition.
A common immutable-payload sequence is:
create memfd
|
v
set size and populate bytes
|
v
remove writable shared mappings
|
v
add GROW + SHRINK + WRITE seals
|
v
optionally add SEAL
|
v
pass descriptor to consumerThe ordering is material. A seal does not retroactively make an incompatible writable mapping disappear.
F_SEAL_FUTURE_WRITE permits existing writable mappings to remain
Linux also provides F_SEAL_FUTURE_WRITE. It prevents future writes through write(2) and prevents new writable shared mappings, while writable shared mappings that already existed when the seal was added may continue modifying the file.
That distinction supports a different handoff boundary. A producer can retain an established writable mapping while preventing recipients from obtaining a new writable path through the descriptor. It is not equivalent to an immutable object while the old mapping remains active.
F_SEAL_WRITE is the stronger transition when no writable shared mapping must survive.
Descriptor permissions are not the same boundary
Passing a descriptor as read-only is not always a sufficient model for object integrity. Descriptor access mode describes operations available through that particular open file description, while seals constrain the underlying file against classes of mutation.
This matters when multiple descriptors or mappings already exist. A consumer checking seals can inspect an object-level state rather than relying only on claims about the descriptor used for transport.
For example:
int seals = fcntl(fd, F_GET_SEALS);
if (seals == -1) {
perror("F_GET_SEALS");
return -1;
}
int required = F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_WRITE;
if ((seals & required) != required) {
fprintf(stderr, "payload is still mutable\n");
return -1;
}The check can be part of an IPC contract: the receiver accepts the object only after the required restrictions are visible on the file itself.
Sealing does not authenticate the payload
Seals constrain mutation; they do not establish origin, authorization, or semantic validity. A malicious producer can seal incorrect bytes just as easily as correct bytes. A receiver that needs authenticity still requires an appropriate trust mechanism, such as a verified signature, authenticated channel, or trusted peer identity.
Seals also do not replace bounds checks. The receiver still has to validate file size, offsets, internal lengths, and data formats before mapping or parsing untrusted content.
The useful security property is narrower: once the required seals are active, later operations covered by those seals cannot change the accepted object state through ordinary file mutation paths.
Sealed memfd objects fit zero-copy handoff boundaries
A memfd can be populated once, mapped without copying by another process, and transferred with SCM_RIGHTS. Sealing lets the producer constrain the object before that transfer, while the consumer can verify the constraints with F_GET_SEALS.
That pattern separates transport from mutability. The descriptor transports access to the same kernel object; the seal set records which later mutations the kernel must reject. For shared buffers, generated artifacts, and IPC payloads, that distinction provides a precise boundary between construction and consumption.