Skip to content

Users, groups and permissions

Linux is a multi-user system: many people and many services share one machine, and each should be able to touch only what it needs. The mechanism that enforces this is old, small and everywhere. Almost every "Permission denied" you will meet, every chmod you type and every sudo rule you write is a consequence of a few ideas: the kernel identifies users and groups by number, every process carries such numbers, and every file records which numbers may do what with it.

This page explains those ideas and how they fit together, from the account databases in /etc to the kernel's access check, and then the two things layered on top: sudo, which grants root's power selectively, and mandatory access control, which can restrict even root.

The kernel knows numbers, not names

To the kernel, a user is a user ID (UID) and a group is a group ID (GID): plain integers. Files store their owner and group as numbers, and processes carry numbers. The name alice exists only in user space, as a translation that programs such as ls, ps and login look up.

That lookup goes through the C library's Name Service Switch, configured in /etc/nsswitch.conf. On a stand-alone server, it reads local files; in an organisation, the same lookups can be answered by LDAP, Active Directory (through SSSD or Winbind) or systemd's userdb, without the kernel noticing any difference. This is why a file copied to another machine can suddenly "belong" to someone else: the number travelled, and the other machine maps it to a different name.

The local account files

Three files make up the traditional local user database:

File Contents Who may read it
/etc/passwd One line per account: name, a placeholder x for the password, UID, primary GID, a comment (the "GECOS" field, usually the full name), home directory and login shell Everyone
/etc/group One line per group: name, a placeholder, GID, and a comma-separated list of supplementary members Everyone
/etc/shadow Password hashes and password-ageing data for each account Only root; some distributions, such as Debian and Ubuntu, also let a shadow group read it

A line of /etc/passwd looks like this:

alice:x:1001:1001:Alice Example:/home/alice:/bin/bash

The split between passwd and shadow is historical. Early Unix kept password hashes in /etc/passwd, which every program must be able to read to turn UIDs into names. As computers became fast enough to guess passwords from their hashes, the hashes moved to a separate file that ordinary users can't read, leaving x behind. /etc/gshadow plays the same role for group passwords, which are rarely used.

Number ranges

Distributions divide the UID space by convention, with the boundaries set in /etc/login.defs:

  • 0 is root.
  • 1 to 999 are system accounts for services such as sshd, www-data or postgres. They usually have no password and no login shell; they exist so that a service can run with its own identity and own its own files.
  • 1000 and up are accounts for people.
  • 65534 is nobody (nfsnobody on some older systems), an identity that owns nothing, used when a process or a network filesystem needs to act as "no one in particular".

The kernel attaches no meaning to these ranges, except for UID 0.

Primary and supplementary groups

Each account has exactly one primary group, named by the GID field in /etc/passwd, and may belong to any number of supplementary groups, listed in /etc/group. The difference matters in two places:

  • New files get the creating process's primary group (its effective GID) as their group, unless the directory has the setgid bit, in which case they inherit the directory's group.
  • Access checks consider the primary group and all supplementary groups equally.

Most distributions create a user private group for each new account: a group with the same name as the user and only that user in it (USERGROUPS_ENAB yes in /etc/login.defs). This sounds redundant, but it solves a real problem. If every user's primary group were a shared users group, a umask that leaves group write enabled would expose everyone's files to everyone. With private groups, the group class of a user's own files contains only that user, so a permissive umask such as 0002 is harmless there, while in a shared directory with the setgid bit the same umask makes new files writable by the whole project group. That is why Debian and Ubuntu give ordinary users a umask of 0002.

Group membership is read when a session starts. At login, a process asks the name service for the user's groups and asks the kernel to attach them to the new process; its children inherit them from then on. Editing /etc/group changes nothing for processes that are already running, which is why adding someone to a group takes effect only at their next login.

What a process carries

Every process has a set of credentials, inherited from its parent. The most important are:

  • the real UID and GID: who started the process;
  • the effective UID and GID: whose rights the process is using now;
  • the saved set-user-ID and set-group-ID: a remembered identity that a program can switch back to;
  • the list of supplementary GIDs;
  • on Linux, a filesystem UID and GID, which normally follow the effective IDs and are what file access checks actually use;
  • a set of capabilities (described below).

Usually the real and effective IDs are the same. They differ when a program file has the setuid or setgid bit: executing it sets the effective UID to the file's owner, or the effective GID to the file's group, while the real IDs still record who ran it. This is how passwd lets you change your own password: the program is owned by root and setuid, so it runs with root's effective UID and can write /etc/shadow, but it checks the real UID to make sure you change only your own entry. Every setuid-root program is therefore a piece of trusted code, and the fewer there are, the smaller the system's attack surface.

How the kernel decides

When a process opens, creates, deletes or executes a file, the kernel works through a fixed sequence of checks.

flowchart TB
    a["Process asks for access<br/>to a path"] --> b{"Search (x) permission<br/>on every directory<br/>along the path?"}
    b -- no --> s{"Does the process hold<br/>a capability that<br/>overrides search checks?"}
    s -- no --> deny["Permission denied"]
    s -- yes --> c
    b -- yes --> c{"Which class applies?<br/>owner, else group,<br/>else other"}
    c --> d{"Do that class's bits<br/>(or the ACL entries)<br/>allow it?"}
    d -- no --> e{"Does the process hold<br/>a capability that<br/>overrides the check?"}
    e -- no --> deny
    e -- yes --> f
    d -- yes --> f{"Do security modules<br/>(SELinux, AppArmor)<br/>allow it?"}
    f -- no --> deny
    f -- yes --> allow["Access granted"]

Three details of this sequence surprise people:

  • The path counts, not only the file. Every directory on the way needs the search (x) permission. A file with mode 644 inside a directory with mode 700 is unreachable for anyone but the directory's owner (and root, whose capabilities override the search check).
  • Exactly one class applies, and the first match wins. If you own a file, only the owner bits are checked, even when the group or other bits would allow more. A file with mode 0077 can be read by everyone except its owner. The classes are a selection, not a sum.
  • Deleting a file is a change to its directory. Removing or renaming an entry needs write permission on the directory; the file's own mode doesn't matter. The sticky bit on shared directories such as /tmp exists to close that gap, so that users can't delete one another's files there.

Access control lists (ACLs) extend the middle step with entries for named users and groups beyond the single owner and group. The order stays the same: the owner entry if the process owns the file, then any matching named-user entry, then all matching group entries, then other, with the ACL's mask capping everything except the owner and other. ACLs make the model more expressive, at the cost of permissions no longer being visible at a glance in ls -l, which only hints at them with a +.

Why root is special

Root is special for a single reason: when a process's effective UID is 0, the kernel grants it a full set of capabilities, and capabilities are what let a process skip the checks above.

Historically, Unix had exactly two kinds of process, privileged and unprivileged. Linux splits privilege into about forty capabilities, each covering one kind of power. A few examples show the idea:

Capability What it allows
CAP_DAC_OVERRIDE Ignore read, write and search permissions on files
CAP_CHOWN Change the owner of any file
CAP_FOWNER Act as the owner of any file, for example to change its mode
CAP_SETUID Change the process's UIDs to anything, which is how login and sudo become another user
CAP_NET_BIND_SERVICE Listen on ports below 1024
CAP_SYS_ADMIN A large collection of administrative operations, such as mounting filesystems

This split is what allows a web server to bind port 443 without being fully root, or a systemd service to run with AmbientCapabilities=CAP_NET_BIND_SERVICE and nothing more. For an ordinary root shell, though, the effect is the same as it always was: root can read and write any file, change any owner, and become any user. Even root can't execute a regular file that has no execute bit at all, and, as described below, root can be confined by mandatory access control.

Because root can do anything, two practices follow. First, nobody should work as root routinely, because every typing mistake and every compromised program then runs with unlimited power. Second, there should be a record of who used root's power and for what, which a shared root password can't provide. Those two needs are what sudo addresses.

Where sudo fits

sudo is an ordinary program installed setuid-root. When you run it, the kernel gives it root's effective UID before it does anything else; sudo then decides, according to its own policy, whether to use that power on your behalf. The kernel isn't involved in that decision. The "sudo rights" a user has are entries in a policy file, not a property of the account.

The policy lives in /etc/sudoers and the drop-in files in /etc/sudoers.d/. Each rule says which users or groups may run which commands, as which target users, on which hosts, and whether they must authenticate. A typical sequence is:

  1. sudo matches the invoking user and the requested command against the rules.
  2. It asks for the invoking user's own password, not root's, unless it already did so recently in this terminal or the rule says NOPASSWD.
  3. It logs the command (to syslog or the journal), sets up a clean environment, switches all its UIDs and GIDs to the target user (root by default), and executes the command.

Compared with su, which asks for the target account's password and opens a shell, this changes the model of administration in useful ways. Root needs no password that people share; each administrator proves their own identity; rights can be limited to specific commands; and every use is attributed to a person. A rule that allows any command is effectively root access, of course, and so is one that allows a program which can start a shell or write arbitrary files.

Distributions grant full rights to an administrators' group rather than to individual users. Debian and Ubuntu use a group called sudo. The RHEL family, Arch and Gentoo use wheel. The name starts with "big wheel", English slang for an important person, long before computers. The TENEX operating system borrowed it for its "wheel" privilege, which gave unrestricted access, and BSD Unix took the idea from TENEX as a wheel group whose members alone could use su. Arch and Gentoo ship the wheel rule commented out, leaving the decision to the administrator, while the RHEL family enables it.

Ubuntu 26.04 replaces the original sudo by default with sudo-rs, a reimplementation in Rust that reads the same sudoers format, supports a deliberately smaller set of features, and aims to reduce the risk of memory-safety bugs in a program that runs as root. Other approaches to the same problem exist: doas, from OpenBSD, with a much simpler configuration; polkit, which authorises specific actions requested over D-Bus rather than whole commands; and systemd's run0, which asks the service manager to start the command instead of relying on a setuid program.

DAC and MAC

Everything described so far is discretionary access control (DAC). "Discretionary" means that a file's owner decides who may access it: owners can chmod and setfacl their own files however they like, and root can override everything. DAC is simple and flexible, but it has two weaknesses. A program running as a user can do anything that user can do, so a compromised web browser can read the user's SSH keys. And a compromised process running as root has no limits at all.

Mandatory access control (MAC) adds a second, system-wide policy that owners can't change, and that applies to root as well. In Linux, MAC is implemented by Linux Security Modules (LSMs), which the kernel consults after the DAC check has passed. An operation must be allowed by both; MAC can only take permissions away.

SELinux AppArmor
Default on AlmaLinux, Rocky Linux and RHEL (enforcing) Debian and Ubuntu
Model Every process and file has a security context (a label); the policy states which process types may do what to which file types Each confined program has a profile listing the paths and capabilities it may use
Strengths Very fine-grained and complete; labels follow files when they move Easier to read and write; profiles are per program
Typical symptom "Permission denied" although the mode allows the access; a denial in the audit log The same, with an apparmor="DENIED" kernel message

Arch and Gentoo enable neither by default, although both can run either, and Gentoo offers SELinux profiles. On SELinux systems, ls -l shows a . after the mode string to signal that a file has a security context, and id shows the process's context.

The two kinds of control answer different questions. DAC asks "does this user own, or has the owner granted access to, this file?" MAC asks "is this kind of program supposed to touch this kind of object at all?" Together, they mean a compromised service is limited first by the account it runs as, and then by a policy written for that service, which neither its account nor root can relax from inside.

Trade-offs

Simplicity versus expressiveness. Nine permission bits and one owner and group per file are easy to reason about and fast to check, and they cover most needs. ACLs, capabilities and MAC each add expressiveness, and each makes the effective permissions harder to see from ls -l alone.

One all-powerful account versus divided privilege. UID 0 remains the simplest way to administer a system, and almost all administration still happens through it. Capabilities, sudo command rules and MAC policies divide that power more finely, at the cost of policies that need writing, testing and maintaining.

Group rights versus individual rules. Granting administration through the sudo or wheel group keeps the policy in one place and makes membership easy to audit with ordinary account tools. Per-user rules in /etc/sudoers.d/ allow narrower grants, but spread the policy across many files.

Further reading