Capability-Based Security
Overview
Capability-based security is a security model in which the right to access a resource is represented by an unforgeable, transferable token called a capability. A process can access a resource if and only if it holds a capability for that resource — there is no implicit ambient authority derived from identity, username, or process credentials. To call a function, open a file, or send a message to another process, a subject must explicitly possess and present a capability token for that specific operation on that specific object.
This is a fundamentally different mental model from the access control paradigm that dominates mainstream operating systems. In Unix, a process running as user "alice" has ambient authority: it can open any file that alice can open, connect to any port alice can connect to, and access any kernel service alice has permission to use. This ambient authority makes it easy to write programs (no explicit bookkeeping of permissions) but makes it equally easy for those programs to be tricked into using permissions in ways the user did not intend. Capability security makes authority explicit, granular, and transferable — which eliminates entire categories of privilege escalation and confused deputy attacks.
The concept was formalized by Dennis and Van Horn in 1966 and has seen continuous research and practical implementation from Hydra (1974) through KeyKOS, EROS, Coyotos, seL4, and Google's Fuchsia/Zircon. After decades as an academic curiosity, capability security is now in production operating systems and increasingly in hardware (CHERI).
Prerequisites
- Understanding of Unix permissions and access control lists
- Familiarity with process model (fork, exec, file descriptors)
- Basic understanding of OS privilege levels (kernel/user, ring 0/ring 3)
- Awareness of common vulnerability classes (privilege escalation, TOCTOU)
- Understanding of the confused deputy problem helps but will be explained here
Historical Context
Dennis and Van Horn (1966)
Jack Dennis and Earl Van Horn described the capability concept in their 1966 paper "Programming Semantics for Multiprogrammed Computations." They observed that in a multiprogramming system, a program needs both the specification of what to do (the code) and the authority to do it (access to resources). They proposed that this authority should be represented as explicit tokens — capabilities — rather than derived from programmer identity or runtime privilege.
The insight was profound: if authority is explicit and unforgeable, then software composition is safe. You can give a subroutine exactly the capabilities it needs to do its job, and it cannot exceed that authority no matter what bugs it contains or what malicious instructions it executes.
The Hydra and KeyKOS Lineage
The Hydra system (Carnegie-Mellon, 1974) was the first OS built around capabilities. KeyKOS (Key Logic, 1980s) was a commercial capability OS that ran production workloads. Its successor EROS (Extremely Reliable Operating System, Jonathan Shapiro, 1990s-2000s) proved that capability systems could be fast, and Coyotos refined it further. These systems established that capability-based kernels could compete on performance with conventional systems.
seL4, developed at NICTA Sydney (2009), brought capabilities to a formally verified microkernel. Fuchsia, Google's experimental OS (2016-), built the Zircon kernel around object handles as capabilities, potentially the most serious industrial investment in capability OS design since KeyKOS.
Capabilities vs ACLs
The Fundamental Difference
ACLs (Access Control Lists) attach a list of authorized subjects to each protected resource. The question "can Alice read file X?" is answered by looking at file X's ACL and checking if Alice's identity appears with read permission.
Capabilities invert this: they attach a list of resources (with permissions) to each subject. The question is answered by checking whether Alice's process holds a capability token granting read access to file X.
ACL Model
===========
Resource (file X):
ACL: [alice: read/write, bob: read, charlie: none]
Question: Can Alice read file X?
Process: Look up file X's ACL → find alice → check permission → allow
Problem: ANY process running as alice can read file X
Capability Model
=================
Alice's process:
Capability table:
cap[0] → file X (read/write)
cap[1] → file Y (read)
cap[2] → network socket (connect to port 443)
cap[3] → subprocess A (send messages)
Question: Can Alice's process read file X?
Process: Does the process hold a capability for file X with read? → Yes → allow
Key: A subprocess of Alice's process that doesn't receive cap[0]
cannot read file X even though it runs as alice.
Why ACLs Create Security Problems
The ACL model grants authority based on identity. When Alice runs a compiler, the compiler runs as Alice and has all of Alice's access rights — to every file Alice can read or write, to every network destination Alice can connect to. If the compiler has a bug or is malicious, it can exploit Alice's authority to do anything Alice can do.
Capability model: Alice gives the compiler only the capabilities it needs — read access to the source files, write access to the output directory, perhaps execute access to the linker. Even if the compiler is compromised, it cannot read Alice's SSH keys, send email, or write to Alice's home directory.
The Confused Deputy Problem
The confused deputy is the classic attack that capabilities prevent. It was described by Norm Hardy in 1988 using a specific example:
A compiler program runs with two capabilities: access to the user's files (passed by the user), and access to a billing file (given by the OS because the compiler must charge for CPU time). A user invokes the compiler with a specific output filename: the name of the billing file. The compiler, dutifully following instructions, writes output to that filename — which happens to be the billing file. The compiler was deceived (by the user) into using its billing authority to corrupt the billing file.
Confused Deputy Attack
========================
Without capabilities (ambient authority):
User ──────► invokes compiler with output = "/etc/billing"
│
Compiler runs with user identity + billing write access (ambient)
│
Compiler writes "output" to /etc/billing ← billing file overwritten!
The compiler was "deputized" to serve two masters and was confused
With capabilities (explicit authority):
User ──────► invokes compiler
User passes capability: cap_output = write("/tmp/user/out.o")
OS grants compiler: cap_billing = write("/etc/billing")
│
Compiler needs to write output: uses cap_output → /tmp/user/out.o ✓
User says "write to /etc/billing":
cap_output is for /tmp/user/out.o, not /etc/billing → DENIED
cap_billing is not user-provided → user cannot direct its use
Attack fails because compiler cannot be tricked into using
billing authority for user-directed writes.
The confused deputy problem is the root cause of many real attacks: CSRF (web server acts as deputy for attacker using user's session), symlink attacks (process acts as deputy using privileged file access), and many privilege escalation vulnerabilities in Unix systems.
Principle of Least Authority (POLA)
POLA states that a program should hold only the capabilities necessary to perform its intended function at any given time. This is the operating principle of good capability system design.
In practice: - A PDF viewer needs read access to one file and display access to the screen. It should not have network access, write access to user files, or access to the address book. - A web browser's renderer process needs display access, network sockets it was given, and storage for the specific sites being rendered. It should not have access to local files. - A build system worker process needs read access to source files and write access to the build output directory. It should not have internet access.
POLA is trivially violated in Unix (a PDF viewer runs with full user authority) and is trivially enforced in a capability system (give the process exactly what it needs).
seL4 Capability Model
seL4's capability system is total: every interaction with the kernel goes through a capability. There is no "kernel call by name" — only capability invocations. The capability types in seL4:
seL4 Capability Types
=======================
Capability Type What it grants access to
----------------- -------------------------------------------------------
CNode Capability storage node (contains other capabilities)
TCB Thread Control Block (create/configure/destroy thread)
Endpoint IPC endpoint (send/receive messages between threads)
Notification Asynchronous signaling (signal/wait)
VSpace (PageDir) Virtual address space root (configure page mappings)
Frame Physical memory frame (map into address space)
Untyped Raw untyped memory (retype into other kernel objects)
IRQHandler Interrupt handler (receive hardware interrupts)
IRQControl Capability to create IRQHandler capabilities
ASIDControl Address Space ID management
ASIDPool ASID allocation pool
SchedContext Scheduling context (CPU time allocation)
SchedControl Capability to create/configure SchedContexts
Key: "Untyped" capability is how seL4 bootstraps the system.
The kernel starts with all memory as untyped; the root task
retyping untyped memory into TCBs, endpoints, frames, etc.
This gives the root task complete control over all kernel objects.
Capability Delegation
Capabilities can be delegated (given to child processes) but only with equal or reduced authority. A process holding cap[file_x: read/write] can give a child cap[file_x: read-only] (reduced) but cannot give cap[file_x: read/write/execute] (increased — the parent doesn't have that).
seL4 Capability Derivation Tree
=================================
Root task holds: untyped memory cap (full authority)
│
├── Retyped: TCB for process A
│ └── Given: endpoint cap (send/receive)
│ └── Process A delegates: endpoint cap (send-only) to B
│
└── Retyped: Frame cap for shared memory region
├── Mapped into process A: read/write
└── Mapped into process B: read-only (reduced grant)
Revocation: If root task revokes the frame cap,
both mappings are automatically revoked —
no dangling capabilities can exist.
Zircon/Fuchsia Capabilities
Google's Zircon kernel (used in Fuchsia OS) implements capabilities as "handles" to kernel objects. Every kernel resource — processes, threads, virtual memory objects (VMOs), channels, sockets, ports — is accessed through a handle. Handles are integers (not pointers) stored in a per-process handle table maintained by the kernel. A process can only access kernel objects for which it holds handles.
Zircon Handle System
======================
Process A's handle table:
handle[0] → VMO (Virtual Memory Object: 4MB) rights: read/write/map
handle[1] → Channel (IPC endpoint with Process B) rights: read/write
handle[2] → Process B rights: signal/wait
handle[3] → Event rights: signal/wait/transfer
Handles can be:
- Sent over a Channel (delegated to another process)
- Duplicated (with equal or lesser rights)
- Closed (capability revoked from this process)
System call pattern:
zx_channel_write(channel_handle, 0, bytes, byte_count,
handles_to_transfer, handle_count);
// handles_to_transfer become invalid in sender after write!
// The receiving process gets new handles with the same rights
Fuchsia's component framework (Component Manager) uses capability routing at the component level: components declare what capabilities they need and what they provide; the framework routes them at startup. No ambient Unix-style filesystem access — components access files only through explicitly routed directory capabilities.
CHERI: Hardware-Enforced Capability Pointers
CHERI (Capability Hardware Enhanced RISC Instructions) extends a conventional ISA with hardware-enforced capability pointers. Every pointer in memory is replaced by a "fat pointer" that carries:
- The base address (normal pointer value)
- Bounds (lower and upper address limits the pointer can access)
- Permissions (read/write/execute)
- A valid bit (set; cleared when the object is freed — prevents use-after-free)
- A sealed bit (prevents further modification)
CHERI Capability (128-bit "fat pointer")
==========================================
127 64 63 0
[perms][base][top][addr][tag]
perms: read, write, execute, seal, unseal, ...
base: lowest accessible address
top: one past highest accessible address (bounds)
addr: current pointer value (must be in [base, top))
tag: 1-bit "valid" flag, stored out-of-band in memory tagging hardware
Normal load: dereference a pointer
1. Check tag bit = 1 (valid, not freed)
2. Check addr in [base, top) (in-bounds)
3. Check permission (read/write/execute)
→ Any check fails: CHERI exception (hardware fault)
→ All pass: proceed with memory access
Example attack prevention:
char buf[64]; // base=buf, top=buf+64
buf[200] = 0; // addr = buf+200 > top → exception (buffer overflow blocked)
free(ptr); // tag bit cleared by allocator
*ptr = val; // tag = 0 → exception (use-after-free blocked)
CHERI was developed at the University of Cambridge and SRI International. The ARM Morello development board (Arm's first silicon CHERI implementation) shipped in 2022. CHERI has been demonstrated on MIPS and is in progress for RISC-V. Microsoft Research has funded a CHERI-based research OS (CheriBSD, based on FreeBSD).
Object-Capability Languages
The "ocap" (object-capability) programming model applies capability principles to language-level objects:
-
E language (Mark Miller, 1998): Pure object-capability language where all authority flows through object references. An object can only affect what it can reach via its held references. If you don't give an object a reference to the filesystem, it has no filesystem access — provably, by language semantics.
-
Erlang processes: Each Erlang process has a PID (process identifier). To send a message to a process, you must hold its PID. PIDs are not globally guessable — they are capabilities. The actor model naturally implements capability-style isolation.
-
JavaScript realms and SES (Secure ECMAScript): The Agoric/Mark Miller work on "Secure ECMAScript" adds capability discipline to JavaScript by removing ambient global authority (no
Date.now(), noMath.random(), nofetch()in capability-clean code) and routing all authority through explicit object arguments.
Capabilities vs MAC/DAC Comparison
Security Model Comparison
===========================
Property DAC (Unix) MAC (SELinux) Capabilities
-------------------- ----------- ------------- ---------------
Authority basis User identity Policy labels Explicit tokens
Authority holder OS (ambient) Policy engine Subject process
Confused deputy Vulnerable Partially safe Prevented
Principle of Hard to enforce Enforced by Naturally enforced
Least Authority (app must drop) policy author (hold only what
you're given)
Composability Poor Poor Excellent
Privilege delegation su/sudo (crude) setcon (limited) Capability grant
Revocation Kill process Policy change Revoke cap
or remove file (complex) (immediate)
Auditability Login-based Label-based Token-based
Implementation cost Low High (policy) Medium (design)
Programmer familiarity High Medium Low (learning curve)
Production adoption Universal Widespread Growing (Fuchsia,
seL4 deployments)
Debugging Notes
seL4 capability debugging (seL4 debug builds):
seL4_DebugCapSlot: print capability at a given CNode slot
seL4_DebugDumpCNode: dump all capabilities in a CNode
seL4 kernel tracks: capability type, rights, object pointer
Fuchsia (Zircon) handle inspection:
zxdb: Fuchsia debugger can inspect handle tables
/proc/self/handles: not exposed (by design — no /proc)
zx_object_get_info(handle, ZX_INFO_HANDLE_BASIC, ...): query handle type/rights
Common errors:
ZX_ERR_ACCESS_DENIED: process doesn't hold required right on handle
ZX_ERR_BAD_HANDLE: invalid handle value (already closed or never valid)
seL4 fault: "cap fault in send phase" → invoked a null/invalid capability
seL4 fault: "cap rights insufficient" → invoked with wrong rights
CHERI debugging (CheriBSD):
CHERI exceptions show as SIGPROT
addr2line with CHERI debug info shows which bounds check failed
cheribsdtest: test suite for CHERI spatial/temporal safety properties
Security Implications
- No confused deputy: The defining security property. A component cannot be tricked into misusing authority it holds on behalf of an attacker-controlled input.
- Compartmentalization by default: The capability model forces developers to think about authority at component boundaries. Security compartmentalization that requires explicit effort in Unix (seccomp, namespaces, MAC policy) is the natural default.
- Revocation: Capabilities can be precisely revoked. When a user logs out or a session ends, all capabilities issued in that session can be revoked atomically, with no chance of dangling authority.
- Supply chain isolation: In a capability OS, a third-party library can be given exactly the authority it needs. A compromised npm package cannot exfiltrate SSH keys because it was never given a capability for the SSH key directory.
Performance Implications
- seL4 capability IPC is approximately 500 cycles on ARM (the measured implementation on which proofs were done). This is comparable to Linux system call overhead.
- Fuchsia's channel-based IPC has higher overhead than bare Linux sockets because every IPC involves capability validation — benchmarks show 2-3x overhead vs Linux pipe for small messages.
- CHERI fat pointers double pointer size (128-bit vs 64-bit), increasing cache pressure and memory usage. CHERI researchers measured 10-20% overhead on typical programs, with zero overhead possible for code that doesn't manipulate pointers.
- Capability lookup in kernel object tables is O(1) (indexed handle table) — not a performance bottleneck.
Failure Modes
- Capability proliferation: If a poorly designed system gives out capabilities too liberally (every component gets all the capabilities "just in case"), the system degrades to ambient authority. Discipline in capability assignment is necessary.
- Confused delegation: A component may correctly hold a capability but improperly delegate it to an untrusted subcomponent. This is a programming error, not a system flaw, but it undermines POLA.
- Revocation lag: Some capability systems (early designs) lacked efficient global revocation; revoking a capability required finding and invalidating every copy. seL4's derivation tree enables efficient revocation.
- Interoperability with Unix: Capability OSes in practice must interface with legacy Unix software that expects ambient authority. Fuchsia's POSIX compatibility layer is a significant engineering challenge.
- Audit difficulty: Ambient authority systems are easy to audit (who does alice have access to?). In a capability system, auditing requires tracing which capabilities have been delegated to which components at runtime.
Modern Usage (2025)
- seL4: Used in DARPA HACMS military aviation systems (verified separation of drone network stacks), automotive AUTOSAR safety partitions, ESA (European Space Agency) spacecraft software, and drone safety systems (SMACCM)
- Fuchsia/Zircon: Google ships Fuchsia on Nest Hub devices; the OS is under active development for broader deployment
- CHERI/Morello: Microsoft CHERI and the UKRI Digital Security by Design program have funded silicon CHERI prototypes; CheriBSD is available for research use
- Android capabilities (partial): Android's Binder IPC mechanism has capability-like properties; each Binder token is unforgeable and represents a specific permission
- Web capabilities API: The W3C Permissions API and site isolation in Chrome/Firefox draw from capability thinking — each origin gets specific capabilities (camera, microphone, location) rather than ambient access
Future Directions
- CHERI mainstream hardware: As RISC-V CHERI (Cheriot) is proposed for embedded MCUs and ARM Morello demonstrates viability, CHERI may reach production embedded and eventually server systems within 5-10 years.
- Fuchsia expansion: Google has stated intent to expand Fuchsia beyond smart display devices; if deployed at Android scale, capability OS principles would reach billions of devices.
- Composable OS: Research into merging capability security with library OS architectures (Unikraft + seL4-style capabilities) for cloud workloads.
- WebAssembly capabilities: The WASI (WebAssembly System Interface) preview 2 is built entirely on capability concepts — every system call takes an explicit file descriptor or other capability handle. This brings capability security to server-side WASM sandboxes.
- Supply chain security: NPM, PyPI, and similar package ecosystems are exploring capability-based isolation for packages — each package gets only declared capabilities rather than ambient host access.
Exercises
- Implement the confused deputy attack in a Unix environment: write a setuid program that reads a filename from user input and writes to it. Demonstrate the attack. Then redesign the program using file descriptor passing (a Unix approximation of capabilities) to prevent the attack.
- Read the seL4 reference manual's chapter on capabilities. Draw a diagram showing how a minimal seL4 user-space system would set up: one thread, one IPC endpoint, one mapped memory page. Show the CNode structure containing all required capabilities.
- Study the Fuchsia component framework documentation. Find two production Fuchsia components and their capability declarations. Identify what capabilities each requests, from whom, and why each is necessary (POLA analysis).
- Write a threat model for a PDF viewer in a capability system. List every capability the PDF viewer needs to: open a PDF, render it, print it, extract text, and follow an external link. Then identify which of these should be granted by default vs requiring explicit user approval.
- Research CHERI's bounds checking implementation. Explain how the capability's
baseandtopfields are set whenmalloc()allocates a buffer. What prevents a program from forging a wider capability to get out-of-bounds access?
References
- Dennis, J.B. & Van Horn, E.C. (1966). Programming Semantics for Multiprogrammed Computations. CACM 9(3).
- Hardy, N. (1988). The Confused Deputy (or why capabilities might have been invented). ACM SIGOPS OSR.
- Levy, H.M. (1984). Capability-Based Computer Systems. Digital Press. (classic text, freely available)
- Miller, M.S. (2006). Robust Composition: Towards a Unified Approach to Access Control and Concurrency Control. PhD thesis, Johns Hopkins.
- Klein, G. et al. (2009). seL4: Formal Verification of an OS Kernel. SOSP.
- seL4 Reference Manual: https://sel4.systems/Info/Docs/seL4-manual-latest.pdf
- Fuchsia OS documentation: https://fuchsia.dev/fuchsia-src/concepts/components/v2/capabilities
- Watson, R.N.M. et al. (2015). CHERI: A Hybrid Capability-System Architecture for Scalable Software Compartmentalization. IEEE S&P.
- ARM Morello Programme: https://www.arm.com/architecture/cpu/morello
- WASI Preview 2 capability model: https://github.com/WebAssembly/WASI/blob/main/preview2/