Skip to content

Lesson 3 of 9 · 6 min read

The Linux Privilege Model: Permissions, setuid, Capabilities and MAC

Free lessonReviewed By the AISecurityInterviews editorial team
Reading position
In this lesson

TL;DR: Four layers decide what a Linux process may do. Discretionary permissions: owner, group and other bits (plus ACLs) set by each file's owner. Identity changes: setuid and setgid executables run as their owner, so each is privileged code reading attacker input. Capabilities: root split into separate powers; a non-root process can hold dangerous ones, and CAP_SYS_ADMIN is close to root. Mandatory access control: SELinux or AppArmor policy that even a compromised owner or root in a confined domain cannot change. Least privilege for a service means working all four.

Before you read

Pick an answer before you read further. The lesson below explains it, and guessing first, right or wrong, helps the explanation stay with you.

  1. 1A directory is mode 777 with no sticky bit. You own a file inside it with mode 600. Can another user delete your file?

Layer 1: discretionary permissions

Each file has an owner, a group and nine bits. The kernel picks one set: owner bits if the process's effective user ID owns the file, otherwise group bits if any of its groups match, otherwise the other bits. The sets do not combine, so a file can deny its owner while allowing strangers.

On directories the bits mean different things: read lists names, write creates and removes entries, execute allows traversal. Root bypasses these checks, and the owner can always change them, which is why they are called discretionary: whoever controls the owner's account controls the rules.

Layer 2: setuid and setgid

A setuid executable runs with an effective user ID equal to the file's owner. That is how passwd lets you change your own password in a file only root can write. The kernel tracks three IDs per process: real (who started it), effective (used for permission checks) and saved (lets a program switch back). A setuid program that drops privileges must change all three, or it can regain root later.

Every setuid-root binary is root code that reads attacker-controlled arguments, environment variables and files, so bugs in them are privilege escalations. Linux ignores setuid on scripts, and in setuid programs the dynamic loader runs in secure-execution mode: it ignores preload entries containing a slash and only preloads setuid-marked libraries from the standard system directories, which closes the classic LD_PRELOAD attack. no_new_privs, available since Linux 3.5, lets a process opt out of gaining privilege entirely: once set it cannot be unset, it is inherited by children and preserved across exec, and setuid bits and file capabilities have no effect for that process tree. Sandboxes and systemd's NoNewPrivileges=yes rely on it.

Layer 3: capabilities

Historically the kernel asked "is this UID 0?". Capabilities let it ask a narrower question for each privileged operation:

CapabilityLets a process...Risk
CAP_NET_BIND_SERVICEBind ports below 1024Low
CAP_DAC_OVERRIDEIgnore file permission checksReads and writes any file
CAP_SYS_PTRACEDebug any processInjects code into privileged processes
CAP_SYS_MODULELoad kernel modulesKernel code execution
CAP_SYS_ADMINMount, many namespace and admin operationsOverloaded; close to root

Capabilities live in per-thread sets. The bounding set is the ceiling a process tree can never exceed, so dropping from it is durable. Ambient capabilities let a non-root service keep a single capability across exec. Files can carry capabilities, a narrow replacement for setuid root.

The practical rule: drop everything, add back the one capability an operation needs, and prefer a design change (a socket passed in by the init system, a broker process) over a broad grant.

Some numbers and history make the model concrete. The kernel defines 41 capabilities, numbered 0 to 40; the newest were split out of CAP_SYS_ADMIN because it had become a second root: CAP_PERFMON and CAP_BPF in Linux 5.8, CAP_CHECKPOINT_RESTORE in 5.9. The capabilities man page puts it bluntly, advising against choosing CAP_SYS_ADMIN if you can possibly avoid it. File capabilities arrived in 2.6.24, the per-thread bounding set in 2.6.25 and the ambient set in 4.3. Inside a user namespace, a process can hold a full set of capabilities, but they apply only to resources that namespace governs: loading kernel modules or setting the system clock still needs privilege in the initial namespace.

Worked example: reading a process's real privilege

A service runs as the non-root user app. Its /proc/<pid>/status shows:

LineValueDecoded
CapEff0000000000200000Only bit 21 set (0x200000 = 2^21): CAP_SYS_ADMIN. Close to root despite the UID
CapBnd000001ffffffffff2^41 − 1: all 41 capabilities. Nothing dropped from the ceiling
NoNewPrivs0Setuid binaries and file capabilities can still raise privilege

The fix in a systemd unit: an empty CapabilityBoundingSet=, NoNewPrivileges=yes, and, only if the service binds port 443, AmbientCapabilities=CAP_NET_BIND_SERVICE (bit 10). Re-reading should show CapEff: 0000000000000400 and NoNewPrivs: 1. If the service needed CAP_SYS_ADMIN only to load eBPF programs, Linux 5.8 and later let you grant CAP_BPF (plus CAP_PERFMON for tracing) instead.

LESSON · LINUX PRIVILEGE MODEL
The same 'non-root' service, before and after hardening
  1. 1

    1 · User ID

    app, not root

    • Looks unprivileged
    • Says little on its own

    Check groups and capabilities next

  2. 2

    2 · Effective capabilities

    CapEff

    • Before: bit 21, CAP_SYS_ADMIN
    • After: bit 10 only, bind port 443

    Decides what privileged calls succeed

  3. 3

    3 · Bounding set

    CapBnd

    • Before: all 41
    • After: empty or the one needed

    The ceiling for everything it executes

  4. 4

    4 · no_new_privs

    NoNewPrivs

    • Before: 0, setuid still works
    • After: 1, cannot gain privilege

    Irreversible and inherited

  5. 5

    5 · Mandatory access control

    SELinux or AppArmor profile

    • Confines even root in the process
    • Denials are signals, not noise

    Survives a compromise of the process

1/3

The UID misleads

A non-root UID with CAP_SYS_ADMIN is close to root.

Layer 4: mandatory access control

MAC adds a check after the discretionary one; both must pass, and the policy belongs to the administrator, not the process.

  • SELinux labels every process and object (for example httpd_t and httpd_sys_content_t) and allows only interactions the policy lists. A web server in its domain cannot read home directories or run a shell from /tmp, even as root. Labels travel with files, and multi-category labels keep containers apart.
  • AppArmor attaches a profile to a program path listing the files, capabilities and network access it may use. Simpler to write; path aliases (hard links, bind mounts) need care.

When MAC blocks something, read the denial, decide whether the access is intended, and fix it narrowly (relabel, enable a specific boolean, add one rule). Turning MAC off removes the containment for the next compromise.

LESSON · THE LINUX PRIVILEGE MODEL: PERMISSIONS, SETUID, CAPABILITIES
What stands between a compromised service and root

Protecting: Root on the host

  1. 1

    1 · Dedicated user, tight permissions

    nothing writable outside its data

    • Stops: Rewrites another service's config
  2. 2

    2 · no_new_privs

    setuid no longer elevates

    • Stops: Abuses a setuid binary
  3. 3

    3 · Empty capability bounding set

    no CAP_SYS_ADMIN, no CAP_SYS_MODULE

    • Stops: Uses a broad capability to mount or load a module
  4. 4

    4 · SELinux or AppArmor profile

    policy the process cannot change

    • Stops: Spawns a shell and reads keys
Each layer closes a different route. A service that is 'not root' but skips the middle two layers can still become root through a setuid helper or a capability.

Worked example: a web service's effective privilege

A service runs as app, not root. Before calling it low-privilege, check:

  1. CapEff in /proc/<pid>/status: does it hold CAP_SYS_ADMIN or CAP_DAC_OVERRIDE?
  2. Groups: is app in docker (the Docker socket is root) or disk?
  3. sudo -l -U app: any rule allowing an editor, interpreter or writable script?
  4. Writable paths: can app write a script root's cron runs, or a directory in a root service's library path?
  5. MAC: is the service confined, or running unconfined?

Any "yes" in steps 1 to 4 means the service is effectively root after compromise. A hardened systemd unit (NoNewPrivileges=yes, empty CapabilityBoundingSet=, ProtectSystem=strict, a system call filter) plus a MAC profile turns each answer into "no".

Misconceptions interviewers listen for

  • "Non-root means low privilege." Groups such as docker or disk, capabilities and sudo rules decide.
  • "Owner, group and other permissions combine." Exactly one set applies: owner if you own the file, otherwise group if you are in it, otherwise other.
  • "Dropping the effective UID is enough." Real, effective and saved IDs must all change, or the process can switch back.
  • "Set SELinux to permissive to fix the denial." That removes containment for the next compromise; fix the policy for the specific access instead.
  • "Root in a user namespace is real root." Its capabilities apply only to resources that namespace governs.

What if · interview follow-ups

Hold your answer as the constraints change

An attacker has code execution in a web service running as the user app on a Linux host. You are asked how bad it is and how to make the next one less bad.

  1. Start here

    What if the team says it is fine because the service is not root?

Primary references

References for the mechanisms discussed above, accessed 6 November 2026:

How this shows up in interviews

Questions such as "how do Unix permissions work?", "what are capabilities?", "SELinux or AppArmor?" and "a non-root service is compromised; can the attacker get root?" all draw on this model. Interviewers check the single-set evaluation rule, directory semantics, real versus effective IDs, why CAP_SYS_ADMIN is dangerous, and whether you know both checks must pass under MAC.

Say it in 30 seconds: "Linux privilege has four layers. Discretionary permissions set by owners, which a compromised owner can change. setuid binaries that run as their owner and are escalation surfaces. Capabilities that split root, where some, like CAP_SYS_ADMIN, are nearly root anyway. And MAC, SELinux or AppArmor, whose policy the process can't change. For a service I drop all capabilities, set no_new_privs, give it a dedicated user with no risky groups, and confine it with a MAC profile."

Check yourself

Say or write your answer first, then reveal. Explaining it from memory, in your own words, is the skill an interviewer is checking.

  1. 1A setuid-root program calls seteuid(getuid()) to drop privileges and then processes user input. What is wrong?

  2. 2SELinux denies a web server reading a new content directory. What is the right fix?

  3. 3A non-root service shows CapEff 0000000000200000. What does it hold?

  4. 4What does setting no_new_privs guarantee?

Practise it as an interviewer asks it

Course lessons that teach this

Related lessons