File Descriptors as Capabilities
The file descriptor as a capability
In UNIX, a file descriptor is a small integer returned by open(). Internally, it is an index into the process’s file-descriptor table, which points to a kernel file object. Once obtained, the descriptor can be used with read(), write(), lseek(), and close() without further permission checks.
This is a capability system in practice: the file descriptor is an unforgeable token (the process cannot manufacture arbitrary descriptors; it can only inherit them or receive them via IPC) that grants specific rights to a specific object.
Inheritance and passing
File descriptors survive fork() (the child gets a copy of the table, pointing to the same kernel file objects). They survive exec() unless marked FD_CLOEXEC. This is the mechanism by which the shell sets up pipes and redirections.
Descriptors can also be passed between unrelated processes via UNIX-domain sockets using ancillary data (SCM_RIGHTS):
// Sender attaches an fd to a message
struct msghdr msg = { ... };
char buf[CMSG_SPACE(sizeof(int))];
msg.msg_control = buf;
msg.msg_controllen = sizeof(buf);
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
*(int *)CMSG_DATA(cmsg) = fd_to_send;
sendmsg(sockfd, &msg, 0);
The receiving process gets a new file descriptor (possibly with a different integer value) that refers to the same kernel file object. This is capability passing at the OS level.
Capability vs ACL for files
UNIX’s hybrid approach is instructive:
- At
open()time, the kernel performs an ACL check (permission bits + UID/GID). - After
open(), the file descriptor acts as a capability — no further checks. - This combines the administrative convenience of ACLs (the file owner controls access by setting
chmodbits) with the efficiency of capabilities (repeatedread()/write()don’t recheck permissions).
The trade-off, as noted in the 2019 paper, is that if permissions change between open() and a later read(), the process retains access it should arguably have lost. Moving the check to every read()/write() would be safer but more expensive, and would add the “descriptor becomes invalid mid-I/O” error case.
Capability-based file systems
A pure capability file system would dispense with ACLs entirely. Each user would hold a set of capabilities (e.g., hashes of file contents + operation codes). Presenting a capability to the system proves the right to read or write. This is more flexible for delegation (Alice can pass her read capability to Bob without involving the file owner) but harder to revoke (once passed, revoking requires tracking and invalidating every derivative capability).
Summary
- File descriptors in UNIX are a practical capability system: unforgeable, inheritable, and passable across IPC.
- ACL checks at
open()time, capability semantics thereafter. SCM_RIGHTSallows capabilities (descriptors) to be sent between unrelated processes.- Pure capability file systems offer flexible delegation but complicate revocation.