JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

The Access Matrix: Capabilities and ACLs

Least privilege is a goal; now we need the bookkeeping that makes it real. This guide builds the access matrix — the master table of who-may-do-what — then slices it two ways: down the columns into ACLs the file keeps, or along the rows into capabilities the subject carries. Once you see the matrix, file permissions, rings, and sandboxes all click into place.

From a principle to a ledger

In the first guide of this rung you met the principle of least privilege: every program, every user, should hold exactly the powers it needs to do its job and not one drop more. That is a wonderful aim, but an aim is not a mechanism. To actually enforce it, the OS needs a precise, checkable record of who is allowed to do what to which thing. Without such a ledger, 'least privilege' is just a wish. This guide is about that ledger — its cleanest mathematical form, and the two very different ways real systems store it.

Let us fix the three words the whole field hangs on. A subject is an active doer — a user, or more usefully a running protection domain (you met this idea in guide one as the set of powers a process holds at a given moment). An object is a passive thing being acted on — a file, a chunk of memory, a network socket, a printer. And a right (or access mode) is a verb that a subject may exercise on an object — read, write, execute, delete. Protection, boiled all the way down, is just answering one question over and over: does this subject hold this right on this object, right now?

The whole policy as one giant table

Here is the beautiful idea. Imagine a giant grid. Give every subject its own row and every object its own column. In the cell where subject S's row crosses object O's column, write down exactly the rights S holds on O. That grid is the access matrix, and it is the complete, exact statement of your system's protection policy — nothing more is hidden, nothing more is needed. To check 'may S write O?' you do the most boring thing imaginable: look in cell (S, O) and see whether 'write' is listed. The whole of protection is reading one cell of a table.

                |  file: essay  |  file: payroll  |  printer  |  domain: D2
  --------------+---------------+-----------------+-----------+-------------
  domain D1     |  read, write  |       --        |   print   |   switch
  --------------+---------------+-----------------+-----------+-------------
  domain D2     |     read      |  read, write    |    --     |     --
  --------------+---------------+-----------------+-----------+-------------
  domain D3     |      --       |     read        |   print   |     --

  to answer "may D1 print?"  ->  look in cell (D1, printer)  ->  yes
A tiny access matrix. Rows are subjects (here protection domains), columns are objects — and notice a domain can itself be an object, so the right 'switch' lets D1 move into domain D2. Every protection question is one cell lookup.

Notice the sly twist in that diagram: a protection domain can appear as a column too, making it an object. The right 'switch' in cell (D1, D2) means 'while running in D1 you are allowed to move into D2.' That is how a program legitimately changes its powers mid-flight — and it is exactly the dangerous hinge an attacker tries to bend, which guide three explores as privilege escalation. The matrix does not just list powers; it also governs how powers may be handed around.

Why nobody stores the matrix whole

The matrix is a gorgeous way to think, but a terrible way to store. Picture a real machine: thousands of users and processes, millions of files. A literal grid would have millions of columns by thousands of rows — billions of cells — and the overwhelming majority would be empty, because most subjects have no rights at all on most objects. Storing a vast, mostly-blank table would waste enormous space and make every change slow. The matrix is sparse, so we never write it out in full. Instead, real systems keep only the non-empty cells — and the clever part is which way you slice the table to do that.

There are exactly two natural ways to chop a table into useful strips. You can cut it by column — gather, for each object, the list of subjects that may touch it and how — or you can cut it by row — gather, for each subject, the list of objects it may touch and how. Same matrix, same information, two completely different data structures with completely different personalities. The column slices are called access-control lists; the row slices are called capability lists. The rest of this guide is the story of those two, because every protection system you will ever meet is, underneath, one or the other (or a blend).

Slicing by column: access-control lists

Take one column — one object — and store the whole column with that object. The result is an access-control list (ACL): attached to each file, a list saying 'Ana may read and write; the accounting group may read; everyone else, nothing.' This is the world you already live in. The nine permission bits you met in the file rung — written rwxr-xr-x, set with chmod 755 — are a tiny, fixed-shape ACL squeezed into a few bits: three subjects (owner, group, other), three rights each (read, write, execute). A full ACL just drops the 'three subjects only' limit and lets you name individual users and groups.

ACLs shine when your question is object-centered: 'who can touch this file?' The answer is right there, stapled to the file — easy to read, easy to audit, easy to revoke (cross a name off the list and that subject's access is gone instantly). That last point matters: revocation is ACLs' great strength. The flip side is the opposite question — 'everything Ana can touch across the whole system?' — which an ACL world answers terribly, because you would have to open every object on the machine and scan its list for Ana's name. ACLs make per-object questions cheap and per-subject questions expensive.

Slicing by row: capabilities

Now slice the other way. Take one row — one subject — and store the whole row with that subject. Each subject carries a list of the objects it may touch and the rights it holds on each. One entry in that list — 'this object, these rights' — is a capability. The mental picture is a key ring: a subject does not consult a list bolted to the door; instead it walks up holding a key, and the door opens if the key fits. A capability is an unforgeable token that both names an object and grants specific rights on it, all in one bundle. To act, you present the capability; the system honors it without looking anyone up.

The trick that makes capabilities safe is unforgeability — you must not be able to scribble your own key. Systems achieve this in a few ways: keep the capability list inside the kernel where user code cannot edit it (a process holds only an index into it, much like a file descriptor is a number standing in for a kernel-protected entry); tag the memory in hardware so a capability cannot be manufactured by ordinary arithmetic; or wrap each capability in a cryptographic signature the kernel can verify but a forger cannot reproduce. In every case the point is the same: holding the token is the permission, so checks are blazing fast and naturally local.

Capabilities answer per-subject questions cheaply ('what can this domain do?' — just read its list) and make delegation elegant: to share access, you hand someone a copy of the key, no central edit required. But they invert ACLs' strength: revocation is hard. Once copies of a key are floating around, taking access back means hunting down every copy — like trying to recall a physical key you have already lent out. This is the deep, honest trade-off: ACLs make revocation easy and enumeration-by-subject hard; capabilities make delegation and per-subject reasoning easy but revocation hard. Neither is simply 'better' — they are duals, the two ways to read the same matrix.

Rings: enforcing the matrix in hardware

All of this is only as strong as the wall that stops a subject from simply rewriting its own row. That wall is built into the CPU. Recall from the very first rung the two-level split — kernel mode versus user mode, a single mode bit deciding whether privileged instructions are allowed. The general form of that idea is a stack of nested protection rings, numbered from the most trusted inward to the least trusted outward: ring 0 is the kernel, with full power; the outer rings are progressively caged. A subject in an outer ring physically cannot execute the privileged instructions or touch the protected memory of an inner ring — the hardware refuses.

Rings are how 'the OS, not the file, enforces protection' becomes a hardware fact rather than a polite agreement. Crossing inward — a user program asking the kernel for something — cannot be done casually; it must go through a single guarded gate, the system call, which is the only sanctioned doorway from an outer ring to ring 0. This is the principle of least privilege wearing a hardware uniform: most code runs in an outer ring with limited power, and only the small, carefully reviewed kernel runs in ring 0. The smaller you can keep that fully-trusted core, the smaller your trusted computing base — the set of components that, if subverted, break all your protection — a theme the rest of this rung returns to again and again.