fcntl: add the file-sealing ABI, and refuse seals honestly

Adds F_ADD_SEALS (1033), F_GET_SEALS (1034) and the six F_SEAL_* flags at
their Linux ABI values, so cbindgen emits them into fcntl.h. Consumers that
reference them now compile; kwin's utils/ramfile.cpp was failing on
F_SEAL_SHRINK/GROW/SEAL/WRITE and F_ADD_SEALS/F_GET_SEALS.

F_ADD_SEALS takes an argument, so it is added to the va_list match in
fcntl() -- without that it would have read whatever happened to be next.

Sys::fcntl (redox) now rejects both commands explicitly with EINVAL. They
previously fell through to redox_rt::sys::fcntl, where command 1033 has no
defined meaning; refusing deliberately is safer than relying on the kernel
to reject a command it does not know.

EINVAL is deliberate and is not a placeholder. Seals are a property of the
underlying object, enforced against every holder of the descriptor across
dup() and across processes. Redox has no such object: there is no
memfd_create, and no scheme services a seal mask. Linux returns EINVAL for
F_ADD_SEALS on any fd whose backing object cannot seal, so callers that
already cope with unsealable backing stores work unchanged -- kwin notes
"This can fail for QTemporaryFile based on the underlying file system" and
degrades to an unsealed mapping.

These must not be made to return success without enforcement. A caller that
seals a buffer before sharing it with a less-trusted process depends on the
seal holding; reporting success without enforcing turns an honest failure
into a silent one.

Implementing them for real means giving Redox a sealable object. On a
microkernel that belongs in a userspace scheme rather than the kernel: a
`memfd:` scheme owning the memory and checking a per-object monotonic seal
mask in its write/ftruncate/fmap handlers, with F_ADD_SEALS/F_GET_SEALS
routed to it from Sys::fcntl. memfd_create would be built on the same
object. Both are left unimplemented rather than faked.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-03 22:33:29 +03:00
parent 5a441d5a1c
commit de47c901c2
2 changed files with 53 additions and 1 deletions
+41 -1
View File
@@ -51,6 +51,46 @@ pub const F_OFD_SETLKW: c_int = 38;
/// Duplicate file descriptor with the close-on-exec flag `FD_CLOEXEC` set.
pub const F_DUPFD_CLOEXEC: c_int = 1030;
// File sealing (Linux ABI values). Seals are a property of the underlying
// memory object, enforced against every holder of the descriptor across dup()
// and across processes. Redox has no such object yet: there is no
// memfd_create, and no scheme services a seal mask, so `fcntl` reports these
// as unsupported (EINVAL) rather than accepting them.
//
// That refusal is the correct, Linux-compatible behaviour -- Linux likewise
// returns EINVAL for F_ADD_SEALS on any fd whose backing object cannot seal.
// Callers are expected to cope; kwin's utils/ramfile.cpp, for instance, notes
// "This can fail for QTemporaryFile based on the underlying file system" and
// degrades to an unsealed mapping.
//
// DO NOT make these succeed without enforcement. A caller that seals a buffer
// it then shares with a less-trusted process is relying on the seal to hold;
// reporting success without enforcing it converts an honest failure into a
// silent one.
//
// Implementing them for real means giving Redox a sealable object. On a
// microkernel that belongs in a userspace scheme, not the kernel: a `memfd:`
// scheme owning the memory and checking a per-object monotonic seal mask in
// its write/ftruncate/fmap handlers, with F_ADD_SEALS/F_GET_SEALS routed to it
// from Sys::fcntl. See local/docs/ for the workstream note.
/// Add seals to the underlying memory object. Unsupported on Redox: EINVAL.
pub const F_ADD_SEALS: c_int = 1033;
/// Query the seals set on the underlying memory object. Unsupported: EINVAL.
pub const F_GET_SEALS: c_int = 1034;
/// Prevent any further seals from being set.
pub const F_SEAL_SEAL: c_int = 0x0001;
/// Prevent the object from being shrunk.
pub const F_SEAL_SHRINK: c_int = 0x0002;
/// Prevent the object from being grown.
pub const F_SEAL_GROW: c_int = 0x0004;
/// Prevent writes to the object.
pub const F_SEAL_WRITE: c_int = 0x0008;
/// Prevent future writes while leaving existing mappings writable.
pub const F_SEAL_FUTURE_WRITE: c_int = 0x0010;
/// Prevent the object from being mapped executable.
pub const F_SEAL_EXEC: c_int = 0x0020;
// Used for `l_type` to describe the type of lock {
/// Shared or read lock.
pub const F_RDLCK: c_int = 0;
@@ -116,7 +156,7 @@ pub unsafe extern "C" fn fcntl(fildes: c_int, cmd: c_int, mut __valist: ...) ->
// c_ulonglong
let arg = match cmd {
F_DUPFD | F_SETFD | F_SETFL | F_GETLK | F_SETLK | F_SETLKW | F_OFD_GETLK | F_OFD_SETLK
| F_OFD_SETLKW | F_DUPFD_CLOEXEC => unsafe { __valist.next_arg::<c_ulonglong>() },
| F_OFD_SETLKW | F_DUPFD_CLOEXEC | F_ADD_SEALS => unsafe { __valist.next_arg::<c_ulonglong>() },
_ => 0,
};
+12
View File
@@ -569,6 +569,18 @@ unsafe { &mut *(args as *mut flock) };
return Ok(0);
}
fcntl::F_ADD_SEALS | fcntl::F_GET_SEALS => {
// File sealing needs an object that enforces the seal mask against
// every holder of the fd, in every process. Redox has none yet, so
// refuse explicitly instead of letting an unrecognised command
// reach the kernel, where its meaning is undefined.
//
// EINVAL is what Linux returns for a non-sealable object, so callers
// that already handle unsealable backing stores work unchanged.
// Do not turn this into Ok(0) -- see the note in header/fcntl/mod.rs.
return Err(Errno(EINVAL));
}
_ => {}
}