Loading learning content...
Throughout this module, we have explored capabilities in depth—their concept, organization, delegation, and revocation. But capabilities exist in the context of a broader access control landscape, where Access Control Lists (ACLs) remain the dominant paradigm in most deployed systems.\n\nDoes this mean ACLs are better? Not necessarily. Each model embodies a fundamentally different philosophy:\n\n- ACLs ask: "Who is allowed to access this object?" (Object-centric)\n- Capabilities ask: "What can this subject access?" (Subject-centric)\n\nNeither question is inherently superior—they optimize for different scenarios, enable different security properties, and have different implementation trade-offs. The choice between them (or the decision to combine them) depends on the specific security requirements, system architecture, and operational constraints.\n\nThis page provides a comprehensive comparison to equip you with the judgment to make this choice wisely.
By the end of this page, you will understand the structural differences between capabilities and ACLs, their respective strengths and weaknesses, which security properties each model enables or hinders, when to prefer one over the other, and how modern systems combine both approaches to leverage their respective advantages.
Both capabilities and ACLs are implementations of the abstract access matrix model. Recall that the access matrix is a 2D table:\n\n- Rows represent subjects (users, processes, domains)\n- Columns represent objects (files, devices, resources)\n- Cells contain the access rights that subject has to that object\n\nThe access matrix is a conceptual model—actually storing it as a 2D array would be impractical for systems with millions of subjects and objects. Instead, systems store either the rows or the columns:
12345678910111213141516171819202122232425262728293031323334353637
# Access Matrix and its representations # Conceptual access matrixaccess_matrix = { # file1 file2 socket device 'alice': {'R,W', 'R', 'R,W', 'R'}, 'bob': {'R', 'R,W', '', ''}, 'server': {'', 'R', 'R,W,S', 'R,W'},} # ACL representation: store by object (column)acls = { 'file1': [('alice', 'R,W'), ('bob', 'R')], 'file2': [('alice', 'R'), ('bob', 'R,W'), ('server', 'R')], 'socket': [('alice', 'R,W'), ('server', 'R,W,S')], 'device': [('alice', 'R'), ('server', 'R,W')],} # Capability list representation: store by subject (row)c_lists = { 'alice': [('file1', 'R,W'), ('file2', 'R'), ('socket', 'R,W'), ('device', 'R')], 'bob': [('file1', 'R'), ('file2', 'R,W')], 'server': [('file2', 'R'), ('socket', 'R,W,S'), ('device', 'R,W')],} # Same information, different organization# ACL: "Who is allowed to access 'socket'?"# -> Quick lookup: acls['socket']# -> Answer: alice (R,W), server (R,W,S) # Capability: "What can 'bob' access?"# -> Quick lookup: c_lists['bob']# -> Answer: file1 (R), file2 (R,W) # Cross-query is expensive:# In ACLs: "What can bob access?" -> Must scan ALL object ACLs# In Capabilities: "Who can access device?" -> Must scan ALL capability listsThe choice of storage is fundamental because it determines which queries are efficient. Systems tend to optimize for their most common query pattern: ACL systems for 'who can access this object' (policy review, auditing); capability systems for 'what can this subject do' (least privilege, sandboxing).
Different access control models enable different security properties. Let's examine key security properties and how each model supports them.\n\n1. Principle of Least Privilege (POLP)\n\nDefinition: Subjects should be granted only the minimal access rights needed for their task, for the shortest time necessary.
ACLs associate rights with identity (user/group). A program running 'as Alice' has all of Alice's rights—even rights irrelevant to its task. Granting fine-grained access requires creating artificial identities for each privilege subset.
Capabilities naturally support POLP. A process receives only the capabilities it needs—no ambient authority from identity. Fine-grained access requires only passing the appropriate (attenuated) capabilities.
2. Confused Deputy Prevention\n\nDefinition: A program acting on behalf of a user should not inadvertently use its own authority when it should use the user's.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546
// The Confused Deputy Problem: ACL vs Capability // SCENARIO: A compiler that maintains a billing log// User asks compiler to read 'input.c' and write 'output.o'// Compiler also writes to '/billing/log' for accounting // === ACL APPROACH ===// Compiler runs with BOTH user's permissions AND billing permissions// Because identity is the basis for access control void acl_compile(const char* input, const char* output) { // What identity do we check? // The compiler is running as 'compiler_user' which has: // - Access to billing log (its own need) // - Access to user's files (inherited through setuid or similar) FILE* in = fopen(input, "r"); // Uses... which identity? FILE* out = fopen(output, "w"); // Uses... which identity? FILE* log = fopen("/billing/log", "a"); // Uses compiler's own access // PROBLEM: Malicious user passes input="/billing/log" // Compiler has write access to billing log! // ACL check succeeds because compiler runs with billing authority // Result: User corrupts billing log via confused deputy} // === CAPABILITY APPROACH ===// Compiler receives ONLY the capabilities user provides for input/output// Plus its own separate capability for billing void cap_compile(Capability input_cap, Capability output_cap, Capability billing_cap) { // Each operation uses a SPECIFIC capability // No confusion possible FILE* in = cap_open(input_cap, CAP_READ); // User's capability FILE* out = cap_open(output_cap, CAP_WRITE); // User's capability FILE* log = cap_open(billing_cap, CAP_APPEND); // Compiler's own // User cannot pass a capability to billing log UNLESS they have one // And if they do, that's their legitimate access, not confusion // The compiler never uses its billing capability for user operations // The user never has the billing capability to misuse // Problem SOLVED by design}3. Revocation\n\nDefinition: The ability to retroactively remove previously granted access.
| Aspect | ACLs | Capabilities |
|---|---|---|
| Basic operation | Edit ACL: remove subject | Find and invalidate all copies |
| Immediate effect | Yes (next access check) | Depends on mechanism |
| Implementation complexity | Simple (single point) | Complex (distributed state) |
| Transitive revocation | N/A (no delegation chains) | Required with delegation |
| Selective revocation | Per-subject natural | Requires tracking |
| Audit trail | Log ACL changes | Log capability operations |
4. Sandboxing and Confinement\n\nDefinition: Restricting a program's access to only specified resources, preventing it from affecting or observing anything else.\n\nCapabilities excel at sandboxing because they explicitly enumerate all access. A sandboxed process receives only specific capabilities and has no ambient authority to escape.\n\nWith ACLs, sandboxing requires careful identity management—running the program as a special restricted user with minimal ACL entries. This is harder to manage and more error-prone.
FreeBSD's Capsicum demonstrates practical capability sandboxing. A process enters 'capability mode' and loses all ambient authority—it can only access resources through file descriptors (capabilities) it already holds. No pathname lookups, no new network connections. Simple, effective, composable.
5. Delegation\n\nDefinition: Transferring access rights from one subject to another.\n\n- ACL delegation: Modifying the ACL requires authority over the object (typically ownership). The object must be updated.\n- Capability delegation: Transfer the capability directly. No object modification needed. Natural and simple.\n\nCapabilities make delegation first-class; ACLs treat it as administrative action.
Beyond security properties, capabilities and ACLs differ in day-to-day operation and system administration.\n\nIdentity and Naming
Object Discovery\n\nHow do subjects find objects they want to access?
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
// Object Discovery: ACL vs Capability approaches // === ACL Systems: Naming Separate from Authorization ===// Objects are named through a global namespace (filesystem, registry, etc.)// Authorization is checked when the name is resolved int acl_open_file(const char* path, int mode) { // 1. Resolve path through global namespace struct inode* inode = resolve_path(path); // 2. Check ACL against current user identity if (!acl_check(inode->acl, current_user(), mode)) return -EACCES; // 3. Grant access return create_file_descriptor(inode, mode);} // Key issue: Can NAME an object you cannot ACCESS// This leaks existence information and enables TOCTOU attacks // === Capability Systems: Naming IS Authorization ===// You only have a reference (capability) to objects you can access// No global namespace to enumerate void* cap_access(Capability cap) { // The capability IS the name IS the authorization // You cannot name something you don't have a capability for // No global namespace to probe // No existence leakage // No TOCTOU (time-of-check-time-of-use) gap return cap_dereference(cap);} // How do you find capabilities? Through designated channels:// 1. Granted by creator/parent// 2. Received via IPC from service// 3. Returned by capability allocation operation// 4. Found through capability directory services // Example: Capability directory (nameserver)Capability find_service(Capability directory_cap, const char* name) { // Request capability from directory // Directory checks if we're authorized (using our directory_cap) // Directory returns capability to named service // Note: We need directory_cap to even ask // Directory is itself capability-protected return directory_lookup(directory_cap, name);}Administrative Actions
| Operation | ACL Approach | Capability Approach |
|---|---|---|
| Grant access | Add entry to object's ACL | Transfer or copy capability |
| Revoke access | Remove entry from object's ACL | Revoke capability (complex) |
| Review access | Read object's ACL | Enumerate subject's C-list |
| Answer 'Who can access X?' | Read X's ACL (easy) | Search all C-lists (hard) |
| Answer 'What can Y access?' | Search all ACLs (hard) | Read Y's C-list (easy) |
| Change all access to X | Modify X's ACL once | Revoke and reissue all caps |
| Bulk user deprovisioning | Update all relevant ACLs | May be easier if user's C-list is authoritative |
Security auditors often ask 'Who can access sensitive resource X?' This question is natural for ACLs (just read the ACL) but hard for capabilities (must examine all subjects' C-lists). This is why many capability systems maintain auxiliary indexes or use hybrid approaches.
The choice between capabilities and ACLs has significant implications for system performance and implementation complexity.\n\nAccess Check Performance
| Aspect | ACL Check | Capability Check |
|---|---|---|
| Type | "Is subject S allowed to do operation O on object X?" | "Does this capability authorize operation O?" |
| Lookup | Find S's entry in X's ACL | Validate capability (already in hand) |
| Complexity | O(ACL size) or O(log n) with sorted ACL | O(1) with proper capability representation |
| Cache behavior | Object's ACL must be loaded | Capability is inline (hardware caps) |
| Authentication | Must verify S's identity first | Possession is authorization |
| Typical overhead | Low to moderate | Very low (hardware) or moderate (software) |
Storage Overhead
123456789101112131415161718192021222324252627282930313233343536
// Storage overhead comparison // Assumptions:// - S subjects, O objects// - Average subject accesses A objects// - Average object is accessed by B subjects// - Total access relationships: S*A = O*B (by definition) // === ACL STORAGE ===// Each object stores list of subject entries// Storage: O * B * (sizeof(subject_id) + sizeof(rights))// Typically: (subject_id: 4-8 bytes, rights: 4 bytes) = 8-12 bytes per entry // Example: 1M objects, average 10 subjects per object// Storage: 1M * 10 * 12 = 120 MB for all ACLs // Plus: need to store object→ACL mapping (object-local, so free) // === CAPABILITY STORAGE ===// Each subject stores list of capability entries// Storage: S * A * (sizeof(object_ref) + sizeof(rights) + metadata)// Typically: (object_ref: 8 bytes, rights: 4 bytes, misc: 4 bytes) = 16 bytes // Example: 10K subjects, average 1000 objects per subject// Storage: 10K * 1000 * 16 = 160 MB for all C-lists // Plus: need to store subject→C-list mapping (process-local) // Key insight: ACLs scale with object count × access density// Capabilities scale with subject count × access density// Choose based on which is smaller in your domain // Special case: Hardware capabilities (CHERI)// Doubling pointer size: 64→128 bits = 100% pointer overhead// But: Replaces separate capability tables entirely// Net effect: ~10-50% memory increase for typical programsImplementation Complexity
Many practical systems combine ACL and capability concepts to leverage the strengths of each. Understanding these hybrid designs reveals how the apparent dichotomy can be bridged.\n\nUnix: ACLs with Capability-like File Descriptors\n\nUnix uses ACLs for authorization but capability-like file descriptors for operations:
123456789101112131415161718192021222324
// Unix: ACL + Capability hybrid // Phase 1: ACL-based authorization (open)int fd = open("/path/to/file", O_RDWR);// - Resolves path through global namespace (allows naming without access)// - Checks file's permissions (ACL) against process UID/GID// - If authorized, creates file descriptor (capability) // Phase 2: Capability-based operation (read/write)read(fd, buffer, size);write(fd, buffer, size);// - No further ACL checks// - File descriptor IS the capability// - Can be passed to child processes (inheritance)// - Can be passed via SCM_RIGHTS (explicit delegation) // Result: ACL controls initial access; capability controls ongoing use// Best of both:// - ACL provides easy revocation (chmod/chown)// - FD provides confused-deputy prevention in programs// - FD transfer enables delegation // Limitation: Still has ambient authority through namespace// Process can attempt open() on any path; sees EACCES only after name resolutionWindows: ACLs Everywhere, Handles as Capabilities\n\nWindows uses ACLs for all securable objects but handle-based access for operations. Similar conceptually to Unix, but with richer ACL semantics (SACLs, explicit deny, inheritance).
Capsicum: Capability Mode with ACL Base\n\nCapsicum lets programs enter 'capability mode' where ACL-based authority is revoked:
123456789101112131415161718192021222324252627282930313233
// Capsicum: ACL + Capability mode #include <sys/capsicum.h> int main() { // Phase 1: Use ACLs to open files (ambient authority) int input_fd = open("/data/input.txt", O_RDONLY); int output_fd = open("/data/output.txt", O_WRONLY | O_CREAT, 0644); // At this point, process has ambient authority via filesystem ACLs // Could open("/etc/passwd", O_RDONLY) if permissions allow // Phase 2: Enter capability mode cap_enter(); // NOW: Ambient authority is REVOKED // open("/etc/passwd", O_RDONLY) -> ENOTCAPABLE // open("/any/path", ...) -> ENOTCAPABLE // socket(AF_INET, ...) -> ENOTCAPABLE // Can ONLY use file descriptors already held // Those FDs ARE capabilities now // Phase 3: Work in capability mode process_data(input_fd, output_fd); // Even if compromised, attacker confined to these two files return 0;} // Key insight: ACLs for bootstrap, Capabilities for sandboxing// Get the easy grant/revoke of ACLs for setup// Get the sandboxing benefits of capabilities for executionseL4: Pure Capabilities with Naming Conventions\n\nseL4 is a pure capability system, but builds conventional naming services on top:
seL4 has no built-in namespace—everything is accessed via capabilities. However, user-level services implement naming conventions: a name server holds capabilities and dispenses them to authorized requesters. This achieves ACL-like semantics (name → permission check → access) built on pure capabilities. The policy is in user space, not the kernel.
| Pattern | ACL Role | Capability Role | Example |
|---|---|---|---|
| ACL for access, cap for use | Controls open/connect | Controls read/write | Unix, Windows |
| ACL bootstrap, cap sandboxing | Initial resource access | Sandboxed execution | Capsicum, Chrome |
| Cap kernel, ACL services | User-space naming services | All kernel access control | seL4, microkernels |
| ACL with cap-like delegation | Base authorization | Object handles for transfer | Macaroons, OAuth |
| Layered capability + ACL | Coarse-grained policy | Fine-grained runtime | Many enterprise systems |
Given the trade-offs, how do you decide which model to use? Here are guidelines based on system characteristics and requirements.\n\nChoose ACLs When:
Choose Capabilities When:
Most sophisticated systems use both models at different layers. ACLs for coarse-grained administrative policy (who belongs to what group, what permissions exist). Capabilities for fine-grained runtime security (what operations this particular request can perform). The question is less 'which one?' and more 'which one at each layer?'
| Criterion | Prefer ACLs | Prefer Capabilities |
|---|---|---|
| Security audit focus | Object-centric ("who can access X?") | Subject-centric ("what can Y do?") |
| Administration style | Centralized policy | Decentralized delegation |
| Revocation needs | Simple, immediate | Can tolerate complexity |
| Trust model | Trust identity infrastructure | Trust capability chains |
| Principal granularity | Users/groups/roles | Individual operations |
| System architecture | Monolithic, enterprise | Microservices, sandboxed |
| Legacy compatibility | Integrate with existing | Clean-slate design OK |
The future of access control appears to involve greater convergence of ACL and capability concepts, particularly through hardware capabilities.\n\nCHERI: Hardware Capabilities for Everyone\n\nCHERI (Capability Hardware Enhanced RISC Instructions) brings hardware capabilities to mainstream processors. When CHERI becomes widespread:\n\n- Memory safety becomes default: Every pointer is a capability with bounds. Buffer overflows become impossible.\n- Fine-grained sandboxing becomes cheap: Compartmentalization has near-zero overhead.\n- ACLs and capabilities naturally layer: ACLs control coarse policy; CHERI capabilities enforce memory safety within each compartment.\n\nARM's Morello processor (CHERI-extended Armv8) is available now for research and development. Future ARM processors may include CHERI as standard.
Microsoft and Google report that ~70% of their security vulnerabilities are memory safety issues (buffer overflows, use-after-free, etc.). CHERI's capability-based memory eliminates these classes entirely. This is why hardware capability adoption is likely regardless of preferences about access control models.
Emerging Patterns\n\n1. Capability-secured APIs: Even ACL-based systems increasingly use capability-like patterns for API access (OAuth tokens, API keys, JWTs).\n\n2. Zero Trust Architectures: Move from perimeter-based (ACL-friendly) to request-based (capability-friendly) security models.\n\n3. WebAssembly: WASM's linear memory model combined with capability-like imports enables sandboxed computation.\n\n4. Container Security: namespaces + seccomp + capability dropping = capability-like confinement.
12345678910111213141516171819202122232425262728293031323334353637383940414243
# Modern patterns combining ACL and capability concepts # Pattern 1: Zero Trust API Access# Traditional (ACL-ish):# - Authenticate identity# - Check role membership# - Allow if policy matches # Capability-influenced:# - Request includes token (capability)# - Token carries scope, audience, expiration (attenuated rights)# - No ambient authority - must present token for each request jwt_token = { "sub": "user@example.com", # Subject (for audit) "scope": "read:files write:docs", # Rights (attenuated capability) "aud": "api.example.com", # Object restriction "exp": 1700000000, # Time limitation "iat": 1699990000 # Issuance tracking}# This IS a capability: unforgeable (signed), designates resource, encodes rights# But issued based on ACL/RBAC policy # Pattern 2: Container Sandboxingdocker_security = { "capabilities": ["NET_BIND_SERVICE"], # Linux capabilities (coarse-grained) "seccomp_profile": "default", # Syscall filtering "read_only_rootfs": True, # Restrict writes "no_new_privileges": True, # No privilege escalation}# Principle of least privilege via capability dropping# But policies defined via ACL-like configuration # Pattern 3: WebAssembly Import Capabilitieswasm_imports = { "fd_read": "cap_read", # File read capability "fd_write": "cap_write", # File write capability "clock_get_time": "allow", # Clock access # No network, no filesystem access (not imported)}# WASM module can ONLY use imported capabilities# Pure capability model for sandboxing# ACL-like policy determines what gets importedThe Long-Term Picture\n\nThe capability vs ACL debate is resolving not through one winning, but through:\n\n1. Hardware capabilities (CHERI) providing memory safety universally\n2. Software capabilities for fine-grained sandboxing (Capsicum, seccomp, WASM)\n3. ACLs/RBAC for enterprise policy management and compliance\n4. Capability-like tokens for distributed systems and APIs\n\nThe principled engineer of the future will use ACLs for administrative convenience and auditability, while using capabilities for runtime security and least privilege. Understanding both deeply—as you now do—is essential for navigating this converging landscape.
This page has provided a comprehensive comparison of capability-based and ACL-based access control—two fundamental approaches to protecting system resources. Let's consolidate the key insights:
Module Complete\n\nCongratulations! You have completed the comprehensive study of capabilities. You now understand:\n\n- The capability concept and its formal properties\n- How capability lists organize subject authority\n- How delegation enables authority transfer with attenuation\n- The challenges and mechanisms of capability revocation\n- How capabilities compare to ACLs and when to use each\n\nThis knowledge equips you to design secure systems, evaluate security architectures, and understand the evolving landscape of operating system protection mechanisms.
You have mastered capability-based access control—from foundational concepts through practical implementation, from delegation patterns through revocation mechanisms, and from theoretical properties through real-world hybrid systems. You are now prepared to design and evaluate protection mechanisms at the highest level.