Unix owner, group, and other mode bits answer many common access questions, but they cannot express every useful policy.
Suppose a report is owned by the payment service and belongs to the payments group. An auditor needs read-only access, but adding the auditor to payments would also expose unrelated files. Creating a new group for every combination of users and files quickly becomes difficult to manage.
Privilege has a similar problem. A web service may need one operation traditionally restricted to the system administrator, such as binding a low-numbered port, but it should not receive authority to modify every file and control every process.
Operating systems address these problems with richer access-control models and more precise forms of delegated authority.
This chapter separates three related ideas:
An access-control system deals with three elements:
A useful abstract model is an access-control matrix. Each row represents a subject, each column represents an object, and each cell contains the operations that subject may perform on that object.
| Subject | Database credential | Payment log | Network port 80 |
|---|---|---|---|
| API process | none | append | bind |
| Payment worker | read | append | none |
| Log collector | none | read | none |
The complete matrix would be enormous on a real system. Processes and objects are created and destroyed continuously, and most cells would contain no rights.
Operating systems therefore do not usually store one literal global table. They represent parts of the matrix in forms suited to different policies.
An access-control list, or ACL, stores a column-like view with an object:
A capability stores a row-like piece of authority with a subject:
This matrix is a conceptual tool, not a complete implementation. It makes the central authorization question explicit:
Which subject may perform which operation on which object?
The same subject-object relationship can be governed in different ways. Three widely used models are discretionary access control, mandatory access control, and role-based access control.
They are not mutually exclusive. A production Linux system can apply more than one model to the same operation.
In discretionary access control, or DAC, an object's owner can make policy decisions about that object within the rules of the system.
Traditional Unix ownership and mode bits are a DAC mechanism. The owner of a file can normally change its permission bits, grant access to a group, or make the file accessible to everyone.
The word discretionary refers to this ability to delegate at the owner's discretion. It does not mean that every process can ignore the rules.
DAC is flexible and easy to understand for ordinary sharing. It also has an important limitation: once an authorized subject reads information, DAC does not control how that subject redistributes it.
For example, if the payment service can read a credential, it can copy those bytes into a file it owns and grant another identity access to the copy. The original file's permissions do not follow the information after it is copied.
DAC policy can also become overly broad through owner mistakes, permissive groups, or files accidentally made readable to everyone.
In mandatory access control, or MAC, a system-wide policy constrains subjects and objects. Ordinary object owners cannot freely override that policy.
A MAC system may attach security labels to processes and files, then apply centrally defined rules to label combinations. For example, a process labeled as a public web service may be forbidden from reading objects labeled as payment secrets even if ordinary mode bits would allow the read.
Linux supports security modules that add such checks. SELinux commonly uses labels and type-enforcement rules. AppArmor commonly confines programs with profiles based largely on executable identities and path-oriented rules.
The important property is not the particular policy language. It is that the additional policy can deny an operation even when the file owner would permit it through DAC.
MAC is useful for containing compromised services and enforcing organization-wide separation. Its policies require careful design because the system must describe legitimate interactions precisely.
In role-based access control, or RBAC, permissions are assigned to roles, and identities are assigned or allowed to activate those roles.
Instead of granting database maintenance rights independently to each administrator, a system can define a database-operator role and assign that role to selected people:
users mapping to roles, and roles mapping to permissions
Roles can model job functions and can be reviewed more easily than thousands of direct grants. Full RBAC systems may support role hierarchies, mutually exclusive roles, and rules about when a role may be activated.
Unix groups can approximate simple roles, but a group is not a complete RBAC system. Group membership alone does not provide dynamic role activation, role hierarchy, or separation-of-duty constraints.
On Linux, an operation can pass through several independent checks.
For a file open, the path may include:
The checks are stacked rather than alternative. Passing the DAC check does not end the decision when a system-wide policy is also configured.
Passing one layer does not guarantee that the complete operation will succeed. A file's mode may allow a read while a mandatory policy denies it. A read-only filesystem can reject a write even when the file's DAC permissions contain a write bit.
Administrative privilege also does not necessarily bypass every layer. A privilege may override a particular DAC restriction while an independent mandatory policy still denies the operation.
This layered design supports defense in depth, but it can complicate diagnosis. A mode string such as -rw-r--r-- describes one part of the decision, not every rule that might apply.
The basic Unix mode has one owner entry, one group entry, and one entry for everyone else. A POSIX access ACL can add entries for specifically named users and groups.
Suppose a file initially has:
The log-agent user needs read access, but should not join the broader payments group. An administrator can add a named ACL entry:
Inspect the result with:
A representative ACL is:
The entries mean:
ls -l commonly shows a + after the mode when an extended ACL exists:
The plus sign means that the nine displayed permission characters are not the complete access policy.
The ACL mask limits the effective permissions of named users, named groups, and the file's owning group entry.
It does not limit the owning user's user:: entry or the other:: entry.
Consider:
The ACL entry for auditor contains r-x, but the mask contains only r--. The auditor's effective permissions are therefore:
getfacl can make the reduction visible:
The ACL mask corresponds to the group-class bits displayed by ls -l. Changing group bits with chmod on a file that has an extended ACL can therefore change the ACL mask and restrict several named entries at once.
This is a frequent source of confusion. An administrator sees user:auditor:rwx in the ACL but the operation fails because the mask removes one or more of those permissions.
Loading simulation...
The ACL algorithm still selects an applicable class rather than treating every entry as an independent fallback.
A simplified evaluation is:
user::.mask::.other::.If a named user entry matches but lacks the requested permission, the kernel does not fall through to other::. Similarly, if the group class matches but its combined effective permissions do not allow the operation, a more permissive other:: entry does not rescue the request.
For example:
The log-agent user receives read-only access. It does not receive the other::rw- permissions because its named entry matched first.
ACLs provide finer granularity, but they preserve the idea that access decisions should be deterministic and inspectable.
A directory can have a default ACL in addition to the access ACL that controls the directory itself.
The default ACL acts as an inheritance template for new children. It is useful for shared directories whose files should consistently grant access to a service or team.
Inspect both forms with:
Output can contain entries such as:
The entries without the default: prefix control the directory itself. The default entries influence ACLs created on new files and subdirectories.
Inheritance is still constrained by the mode the creating program requests. A default ACL should not be understood as blindly forcing every inherited permission onto every new object.
When a default ACL exists, the inherited ACL and the program's requested mode determine the new access ACL; the umask is not applied as a separate additional step.
Existing children are not retroactively changed when a directory's default ACL changes. A deployment should inspect both the directory template and representative files created under it.
An ACL starts with an object and asks who may use it:
A capability starts with a holder and says what authority it possesses:
These are often described as opposite views of the access-control matrix:
The difference affects delegation and revocation.
An ACL can be changed centrally on the object. Future access checks consult the updated list, making it natural to revoke a named subject's future access.
A capability can be delegated by transferring it to another subject, often without changing a central object list. This supports precise sharing, but revocation can require indirection, tracking, or invalidating every outstanding capability.
Neither representation is universally better. They optimize different ways of expressing and distributing authority.
In a capability-based security model, a capability is an unforgeable reference that identifies an object and carries authority to perform specific operations on it.
Holding the capability is sufficient to exercise the represented authority. A subject cannot create a valid capability merely by guessing an object name or writing down a number; the trusted system controls capability creation and transfer.
Imagine a storage service returning a handle with read-only authority to one report:
A read-only report capability identifies report 731, permits reading it, and permits neither writing it nor reaching any other report.
The caller does not need ambient permission to search the entire storage namespace. It can act only through the authority it received.
This encourages the principle of least authority: give a component the exact references and operations it needs rather than access to a global namespace from which it can discover many objects.
Capabilities also make delegation explicit. Passing a read-only capability to another component delegates read authority to that component. A design must therefore control both use and transfer.
A Unix file descriptor is not a complete implementation of a pure capability system, but it has capability-like properties.
After the kernel opens a file, the descriptor refers to a particular open file description with a particular access mode. The process cannot turn an arbitrary integer into a descriptor for another process's secret file. It must receive the descriptor from the kernel, inherit it, or have another process deliberately pass it.
open("/srv/report", O_RDONLY) causes the kernel to check the pathname policy once, returning file descriptor 7, which refers to the opened object and carries read access.
The process can use descriptor 7 without resolving the pathname again. It can also pass the descriptor to another process over a suitable local communication channel, explicitly delegating access to the already opened object.
The descriptor is only capability-like because a normal Unix process also has ambient access through pathnames, process IDs, and other global namespaces. A pure capability environment removes or sharply limits such ambient authority.
Still, the model is practical: a privileged launcher can open one protected resource and pass only that descriptor to an unprivileged worker. The worker does not need permission to search the resource's pathname or open other files in the same directory.
The term Linux capability refers to a different mechanism from an object capability.
Traditional Unix treated UID 0 as a large bundle of exceptional authority. Linux divides much of that authority into named privilege bits. A thread can hold some bits without holding all of them.
Examples include:
CAP_CHOWN, which permits bypassing restrictions on changing file ownershipCAP_DAC_OVERRIDE, which permits bypassing many discretionary file access checksCAP_KILL, which permits bypassing certain permission checks when sending signalsCAP_NET_BIND_SERVICE, which permits binding Internet-domain sockets to privileged portsCAP_NET_ADMIN, which covers many network-administration operationsCAP_SETUID, which permits particular changes to process user IDsCAP_SYS_ADMIN, which covers a very broad collection of system-administration operationsA Linux capability does not normally identify one specific object. CAP_CHOWN is not an unforgeable reference to one file; it is authority to pass a category of kernel privilege checks, subject to the capability's scope and other security layers.
This gives a crucial terminology distinction:
Object capabilities identify particular objects and operations. Linux capabilities divide traditional superuser privilege into named categories.
Both aim to make authority more precise, but they do so at different granularities.
Suppose a server needs to listen on TCP port 80. On conventional Linux configurations, binding a port below 1024 requires privilege.
Running the entire server with UID 0 gives it far more authority than binding one port requires. If the server is compromised, the attacker may gain access to unrelated files, processes, and system configuration.
Granting only CAP_NET_BIND_SERVICE changes the failure boundary:
Capabilities implement least privilege more effectively than an all-or-nothing root identity.
They are not automatically narrow enough. CAP_DAC_OVERRIDE bypasses a large class of file restrictions, while CAP_SYS_ADMIN is so broad that granting it often defeats the goal of precise privilege reduction. Capability selection must be based on the actual kernel operations a service requires.
The safest capability is still one the process does not need. If a process needs a capability only during initialization, it can perform the operation and then drop the capability before handling untrusted work.
Linux capability sets are technically properties of a thread. The kernel evaluates the calling thread's credentials when it reaches a capability check.
Most applications keep the credentials of all threads aligned, so documentation and operational tools often speak informally about a process's capabilities. The distinction matters when a multithreaded program changes credentials: one thread's state must not be assumed to represent every other thread automatically.
Capabilities are inherited when a process creates a child because the child begins with copied credentials. Executing a new program recalculates capability sets according to the current thread, the executable file, and system restrictions.
This means that capability behavior across execve() is intentional, not simply “keep everything” or “drop everything.”
Linux tracks several capability sets because a thread may possess authority without using it continuously, pass selected authority through program execution, or place a ceiling on descendants.
The permitted set contains capabilities the thread may make effective. A thread cannot ordinarily add a capability to its effective set if that capability is absent from its permitted set.
Dropping a bit from the permitted set reduces what the current program can reactivate. Executing another file can still affect the new permitted set under tightly defined rules, bounded by the executable's metadata and the thread's other limits.
The effective set contains the capabilities currently active for kernel checks.
When an operation requires CAP_NET_BIND_SERVICE, the kernel tests whether that bit is effective in the relevant scope. A bit that is merely permitted but not effective does not satisfy the check until the program activates it.
Separating permitted from effective lets a program keep a capability inactive during code paths that do not require it.
The inheritable set identifies capabilities that may participate in the capability calculation when the thread executes a file whose capability metadata permits that inheritance.
The name can be misleading: simply placing a bit in the inheritable set does not guarantee that every child executable receives it. File metadata and the execution rules also matter.
The capability bounding set places a ceiling on capabilities that can be gained through file capability metadata during execution.
A thread can drop capabilities from its bounding set, and its descendants inherit the smaller ceiling. Once a capability has been removed from the bounding set for that process tree, an executable file cannot use its file capability metadata to restore the bit there.
This makes the bounding set useful for service launchers: even if a child executes an unexpectedly privileged binary, the child cannot gain capabilities outside the configured ceiling.
The ambient set allows selected capabilities to survive execution of ordinary, nonprivileged programs.
An ambient capability must also be present in both the permitted and inheritable sets. When an ordinary program is executed, ambient bits are added to its new permitted and effective sets. Executing a setuid program or a file with privilege metadata clears the ambient set.
Ambient capabilities are useful when a service launcher needs to grant a capability to an ordinary executable without modifying that executable's file metadata.
The five sets serve different questions:
Loading simulation...
Linux filesystems can store capability metadata in the security.capability extended attribute of an executable file. These are called file capabilities.
File capability metadata contains permitted and inheritable masks plus an effective flag. During execve(), the kernel combines this metadata with the calling thread's capability state and restrictions to calculate the new thread capability sets.
The exact calculation has several cases, but the common goal is straightforward:
An executable file carrying selected capability metadata gives the new program that capability across execve(), without granting every root privilege.
For example:
Here:
p places CAP_NET_BIND_SERVICE in the file's permitted mask.e sets the file's effective flag, causing the capability granted as permitted during execution to become effective as well.Inspect file capabilities with:
Expected output is similar to:
Remove the file capability with:
File capabilities require filesystem and kernel support. A mount configured not to honor privileged executable metadata can suppress their effect.
They also attach to the executable file, not to its pathname as an abstract name. Replacing the binary during deployment can remove the extended attribute. Copy and packaging tools must preserve the metadata intentionally when required.
The executable and its containing deployment path must be protected from modification by the unprivileged service. Granting privilege to code that the service can rewrite would undermine the trust boundary.
Consider a compiled API server installed at:
It runs as the non-root user payments but must bind TCP port 80.
First, verify that the executable is not writable by the service:
A reasonable ownership pattern is:
Grant only the required file capability:
Start the server as payments. The exact service command depends on how the application is deployed, but the resulting process should have UID payments, not UID 0.
Inspect its identity:
Inspect its capabilities with getpcaps, provided by the Linux capability utilities:
Linux also exposes hexadecimal capability masks:
The fields include:
The numeric masks are bitsets, not ordinary decimal capability numbers. On systems with the capsh utility, a mask can be decoded with:
In these examples, 12345 stands for the service's actual process ID. /proc/<pid>/status reports the main thread's capability sets. When a multithreaded program may have different thread credentials, inspect /proc/<pid>/task/<tid>/status for the relevant thread.
The verification goal is precise:
Successfully binding the port does not prove that the service lacks other privilege. Inspecting the complete sets is part of the test.
Modifying executable metadata is not the only deployment model. A privileged service manager can prepare a process's capabilities before launching the service.
For example, a systemd service can use:
CapabilityBoundingSet removes capabilities outside the specified ceiling. AmbientCapabilities carries the selected capability into the ordinary executable. NoNewPrivileges prevents the service and its descendants from gaining additional privilege through later program execution.
This is an alternative to attaching security.capability metadata to the binary. It centralizes the policy in the service definition and avoids losing a file capability when the executable is replaced.
The service manager must be trusted to establish the credentials in the correct order. The application should still drop CAP_NET_BIND_SERVICE after binding if it never needs to open another privileged port.
After changing the service definition, verify the running process rather than assuming the configuration was applied. A stale process may still have its previous credentials.
no_new_privsLinux provides a per-thread attribute commonly called no new privileges. Once set, it is inherited by children and cannot be unset.
Its central promise is that executing a new program will not grant the caller privileges it did not already possess. Setuid transitions and file capabilities cannot be used by that execution to gain additional authority.
This matters when choosing a deployment design. An unprivileged launcher that sets no_new_privs and then expects a file capability to grant CAP_NET_BIND_SERVICE has created conflicting requirements: the privilege-gaining execution is intentionally suppressed.
A privileged service manager can instead establish the selected capability first, place it in the appropriate sets, set a strict bounding set, enable no-new-privileges, and then execute the service.
The attribute does not remove authority the process already has. It prevents a particular path of future privilege gain, so it should be combined with dropping capabilities that are no longer required.
A capability that remains effective can be misused by any later code executing in that thread.
For a network server, a safer lifecycle is:
CAP_NET_BIND_SERVICE.The capability is held only for step 2. By the time untrusted input arrives, the process can no longer use it.
Dropping only the effective bit may not be enough if the permitted set still lets the program reactivate it. A permanent drop should remove the bit from every set through which the program could regain or propagate it, consistent with the service's execution needs.
In a multithreaded program, credential changes require special care because capabilities are technically per-thread. Establish and reduce privilege before creating worker threads when possible, or use APIs that deliberately coordinate credential changes.
Privilege reduction should be verified from outside the application using /proc/<pid>/status or a capability-inspection tool. A log message saying “dropped privileges” is not evidence that every relevant set changed successfully.
Removing every Linux capability does not make a process harmless.
The process retains all ordinary authority granted by its UID, groups, open file descriptors, file modes, ACLs, and other access-control rules. A capability-free service that owns a directory can still modify everything its ordinary permissions allow.
Conversely, granting one capability does not automatically bypass unrelated restrictions. CAP_NET_BIND_SERVICE lets a process pass the privileged-port check; it does not let the process read a root-owned credential file or administer every network setting.
Some capabilities are much broader than their names first suggest. CAP_SYS_ADMIN is used by many unrelated kernel operations and is often described as an overloaded administrative capability. Granting it should be treated as close to granting a large portion of root authority.
Capabilities also interact with other access-control layers. A thread may pass a Linux capability check and still be denied by a mandatory security policy.
The correct mental model is:
Linux evaluates capabilities relative to a user namespace. A thread can have a capability inside one user namespace without holding equivalent authority in its parent namespace.
This creates scoped administration. A process may be treated as privileged for resources governed by its namespace while remaining unprivileged for host resources governed by an ancestor namespace.
The capability name alone therefore does not describe its entire reach. A useful inspection asks both:
Not every kernel resource is isolated or governed by the same namespace. Possessing a capability in a child namespace does not imply unrestricted authority over the whole machine.
Return to the payment API that must bind port 80, read one configuration file, and append one log.
Its policy can use several independent mechanisms:
Each mechanism solves a different problem.
The UID and ordinary file modes establish the default authority of the process. The ACL handles a specific sharing relationship without placing the service in an overly broad group. The Linux capability grants one privileged kernel operation without granting full administrative identity. The mandatory policy can add containment even if DAC is misconfigured.
If the API is compromised after startup, the damage depends on the complete active authority:
Security comes from the intersection of these controls, not from attaching one impressive-sounding mechanism to the process.
When an operation is denied, start from the subject, object, operation, and active policy layers.
Inspect the process:
Inspect ordinary file metadata and ACLs:
Inspect executable capability metadata:
Then ask which check the operation actually requires. A service that cannot bind port 80 needs a different investigation from one that cannot read its configuration.
For an unexpected permission grant, use the same method in reverse. Determine whether access came from ordinary mode bits, a named ACL entry, an existing open descriptor, a Linux capability that bypassed a DAC restriction, or another policy layer.
Do not respond to a capability error by granting CAP_SYS_ADMIN, and do not respond to an ACL error by making the file accessible to everyone. Broad grants hide the original mistake and enlarge the consequences of compromise.
Access control maps subjects and operations to protected objects. DAC lets owners influence policy, MAC imposes system-wide constraints, and RBAC assigns permissions through organizational roles.
POSIX ACLs extend owner, group, and other mode bits with named users and groups. The ACL mask limits the effective permissions of the entire group class, and default directory ACLs provide controlled inheritance.
Object capabilities are unforgeable references to particular objects and operations. Unix file descriptors have useful capability-like behavior because they carry established access to an opened object.
Linux capabilities are different: they split traditional root authority into named privilege bits. Per-thread permitted, effective, inheritable, bounding, and ambient sets control which bits are active and how they behave across execution.
File capabilities or a service manager can give a non-root service one required privilege such as CAP_NET_BIND_SERVICE. The executable, launch policy, runtime sets, and privilege-dropping behavior must all be verified.
Capabilities support least privilege, but they do not replace ordinary identity permissions or form a complete sandbox. A secure service combines narrowly scoped authority with independent access-control layers.
5 quizzes