Permissions#

Permissions are the operator’s first lever on the platform. Every read, write, and execute on a Linux box passes through a stack of checks (identity, mode bits, ACLs, sudo, capabilities, MAC), and the operator who can read that stack at a glance is the operator who knows where privilege lives, where it leaks, and where it can be tightened.

On objective the question is always the same. Who can do what here, and where is the escalation surface? On a defended estate the question inverts. Where is the over-permissive grant the next intruder will walk through? Both jobs read the same files and run the same tools.

Linux access control is layered. A request to read or write passes through every layer below; a denial at any one fails the operation.

Layer

Question it answers

Identity

Who is the subject? (UID, GID, supplementary groups, /etc/passwd, /etc/group)

Authentication

Did this session prove the identity in the first place? (PAM, SSH, login)

DAC

Does the file’s owner allow this UID/GID combination? (mode bits + POSIX ACLs)

Privilege

Is the subject allowed to act as another identity? (sudo, setuid, setgid)

Capabilities

Does this binary hold the specific privilege the syscall needs? (CAP_NET_RAW, CAP_DAC_OVERRIDE, …)

MAC

Does the system-wide policy permit this access regardless of who owns the file? (SELinux, AppArmor)

Audit

Record what was attempted, allowed, or denied. (auditd, /var/log/audit/audit.log)

DAC is the default every Linux user learns first. The other layers extend or restrict it. ACLs add finer DAC entries; sudo and capabilities open controlled escalation paths; MAC narrows what even root may do; PAM gates how identities log in; auditd writes it all down. Each section below covers one layer.

Users and Groups#

Identity primitives. id, whoami, and getent answer “who am I and what groups am I in”; useradd, passwd, usermod, and userdel create, password, modify, and remove accounts. Almost every other permission decision starts here.

$ id
$ id operator
$ whoami
$ groups operator
$ getent passwd operator
$ getent group sudo

$ sudo useradd -m -s /bin/bash operator
$ sudo passwd operator
$ sudo usermod -aG docker operator
$ sudo userdel -r operator

Mode bits#

Unix DAC has nine permission bits in three triplets (owner / group / other), each rwx, plus three “special” bits (setuid, setgid, sticky) and a default-mask (umask) applied at file creation. The following subsections cover each piece.

The whole picture#

The first ls -l field encodes the file type, the three permission triplets, and any special-bit overlay in a compact 10-character form. Reading it is a daily operator skill; every other tool in this section either checks or modifies these bits.

$ ls -l hello.sh
-rwxr-xr-x  1  operator  staff  1024  Jan  1 12:00  hello.sh
^│└┴┘└┴┘└┴┘
││ │  │  └ other  (r-x)
││ │  └─── group  (r-x)
││ └────── owner  (rwx)
│└──────── file type (- = file, d = dir, l = link, c = char dev,
│                      b = block dev, p = pipe, s = socket)
└───────── permission triplet leader (could be 's', 'S', 't', 'T')

Bits and triplets#

Each of the three triplets has three bits, read / write / execute. ls -l shows the symbolic form; chmod accepts both symbolic and octal.

Bit

Octal

Meaning (file)

r

4

read

w

2

write

x

1

execute

For directories the meanings shift slightly.

Bit

Meaning (directory)

r

list directory contents

w

create / delete / rename entries

x

traverse (cd into; needed to access any file inside)

$ chmod 750 script.sh
$ chmod u+x,go-rwx script.sh
$ chmod -R go-w shared/

$ chown operator:staff file
$ chown -R operator:staff project/

Octal cheat sheet#

Three octal digits map to the three triplets (owner, group, other). Each digit is the sum of the read (4), write (2), and execute (1) bits, so 7 = read + write + execute and 5 = read + execute. The cheat sheets below cover the patterns you actually type.

Octal

Symbolic

Common use

0

no permissions

1

–x

traverse only

2

-w-

write only

3

-wx

rare; usually 7 or 5 instead

4

r–

read only

5

r-x

read + traverse (typical for dirs)

6

rw-

read + write

7

rwx

full

Common combined modes.

Octal

Symbolic

Use

600

rw——-

private file (SSH keys, .env)

644

rw-r–r–

world-readable file

664

rw-rw-r–

group-writable file

700

rwx——

private directory

755

rwxr-xr-x

executable / world-readable directory

775

rwxrwxr-x

group-writable directory

777

rwxrwxrwx

everyone all (avoid in production)

400

r——–

read-only private (SSH keys often)

444

r–r–r–

read-only world-readable

Special bits#

A fourth (leading) octal digit holds three special bits, setuid, setgid, and sticky. These change how a file or directory is treated by the kernel (running as the owner, inheriting a group, restricting deletion) rather than who may read or write it.

Bit

Octal

Meaning

setuid

4000

on an executable, runs as the file’s owner

setgid

2000

on an executable, runs as the file’s group; on a directory, new entries inherit the directory’s group

sticky

1000

on a directory, only the file owner can delete (the mode used by /tmp)

Symbolic display in ls -l for the same modes.

Mode

ls -l

Notes

4755

rwsr-xr-x

setuid + 755

2755

rwxr-sr-x

setgid + 755

1777

rwxrwxrwt

sticky + 777 (this is /tmp)

4750

rwsr-x—

setuid + 750

Capital letters mean the bit is set but the corresponding x is not.

Symbol

Meaning

S

setuid / setgid set, but no execute (rarely useful)

T

sticky set on a directory without execute for “other”

$ chmod u+s ./prog
$ chmod g+s shared/
$ chmod +t /tmp

Symbolic chmod#

chmod accepts both octal and symbolic syntax. The symbolic form is chmod {who}{op}{perm}, which is convenient when you want to flip a single bit without recalculating the whole mode; for example, removing world-write or adding group-read on a single file:

Field

Values

who

u user, g group, o other, a all

op

+ add, - remove, = set exactly

perm

r, w, x, X (execute only if dir or already executable), s (setuid/setgid), t (sticky), or u / g / o to copy from another triplet

$ chmod u+x script.sh
$ chmod go-w sensitive.conf
$ chmod 0644 file
$ chmod -R u+rwX,go-rwx project/
$ chmod g+s shared/
$ chmod +t /tmp

umask#

The umask masks bits off the default permissions on file creation. New files are created with mode 0666 & ~umask, new directories with 0777 & ~umask, so a umask of 022 produces 644 files and 755 directories. Login shells set this in /etc/profile or PAM.

umask

Default file

Default directory

022

644

755

027

640

750

077

600

700

002

664

775

$ umask
$ umask 022
$ umask 077

sudo#

sudo runs a single command as another user (root by default) according to rules in /etc/sudoers and any drop-ins under /etc/sudoers.d/. Always edit with visudo so the file is syntax-checked before it replaces the live one; a broken sudoers can lock everyone out.

        sequenceDiagram
  participant O as operator
  participant S as sudo
  participant R as sudoers
  participant C as command
  O->>S: sudo cmd
  S->>R: check rules
  R-->>S: allow + log
  S->>C: exec as target
  Note over C: runs as root (or -u USER)
    
$ sudo systemctl restart nginx
$ sudo -u postgres psql
$ sudo -i
$ sudo -l

ACLs#

When ls -l shows a trailing +, the file has an extended ACL. ACLs allow finer-grained access than the standard ugo bits, with per-user or per-group entries beyond the file’s owner and group, and default ACLs that new entries in a directory inherit automatically.

A trailing + on the mode column flags an extended ACL.

-rw-r--r--+ 1 operator staff 0 Jan  1 12:00 file
$ ls -l file
$ getfacl path/
$ setfacl -m u:bob:rwx path/
$ setfacl -d -m g:devs:rx dir/
$ setfacl -b file

Linux Capabilities#

Capabilities subdivide root’s privileges so a program can perform specific privileged operations without being setuid root. ping getting cap_net_raw is the standard example. It can craft raw packets but cannot do anything else only root could do.

A capability set on a binary lives in extended attributes (security.capability) and applies any time that binary runs. The suffix after = selects which sets receive the capability:

Suffix

Set

Effect

e

effective

active immediately on exec; if missing, the kernel won’t enforce it

i

inheritable

passed across execve to a child if the child also has it permitted

p

permitted

the upper bound the process may move into effective

Common capabilities the operator meets in the wild.

Capability

Grants

CAP_NET_RAW

open raw / packet sockets (ping, tcpdump)

CAP_NET_BIND_SERVICE

bind a TCP/UDP port below 1024 without being root

CAP_NET_ADMIN

configure interfaces, routes, firewalls

CAP_DAC_OVERRIDE

bypass file read/write/execute permission checks

CAP_DAC_READ_SEARCH

bypass file read and directory traversal checks

CAP_SYS_ADMIN

umbrella catch-all (mount, namespaces, many sysctls); near-root

CAP_SYS_PTRACE

attach to any process with ptrace

CAP_SYS_MODULE

load / unload kernel modules

CAP_CHOWN

change file owner

CAP_KILL

signal any process

CAP_SETUID / CAP_SETGID

arbitrary UID / GID change

CAP_AUDIT_WRITE

write to the kernel audit log

A binary holding CAP_DAC_OVERRIDE, CAP_SYS_ADMIN, CAP_SYS_PTRACE, CAP_SYS_MODULE, or CAP_SETUID is functionally a privilege-escalation primitive; treat it with the same suspicion as a setuid-root binary.

$ getcap /usr/bin/ping
$ sudo getcap -r / 2>/dev/null
$ sudo setcap cap_net_bind_service=+ep ./server
$ sudo setcap cap_net_admin,cap_net_raw=eip /usr/bin/wireshark
$ sudo setcap -r ./server

Mandatory Access Control#

Beyond the discretionary ugo + ACL model, Linux ships with two mandatory access control frameworks. Both confine processes to a policy regardless of who owns them, and both ship enabled by default on their respective distributions. MAC narrows what is allowed; even root is bound by the policy in enforcing mode.

SELinux

AppArmor

Distros

Fedora, RHEL, CentOS, Android

Ubuntu, Debian, SUSE

Policy unit

security context (user:role:type:level)

per-program profile (path-based)

Modes

enforcing, permissive, disabled

enforce, complain, unconfined

View label

ls -Z, ps -Z, id -Z

aa-status, ps auxZ

Switch mode

setenforce 0 / setenforce 1

aa-complain / aa-enforce

Audit log

/var/log/audit/audit.log (AVC denials)

/var/log/syslog or /var/log/audit/audit.log

Translate denial

audit2allow, audit2why

aa-logprof

Authoring tools

semanage, setsebool, checkmodule

aa-genprof, aa-autodep

$ getenforce
$ sudo setenforce 0
$ ls -Z /etc/passwd
$ sudo semanage fcontext -l | head

$ aa-status
$ sudo aa-complain /usr/sbin/nginx
$ sudo aa-enforce /usr/sbin/nginx

Common Tasks#

Find SUID / SGID binaries. Privilege-escalation surface.

$ sudo find / -perm -4000 -type f 2>/dev/null
$ sudo find / -perm -2000 -type f 2>/dev/null
$ sudo find / -perm /6000 -type f -newer /etc/hostname 2>/dev/null

Audit file capabilities. Finer than SUID, often missed.

$ sudo getcap -r / 2>/dev/null
$ sudo setcap -v cap_net_raw+ep /usr/bin/ping

Find world-writable. Common persistence vectors.

$ sudo find / -xdev -type d -perm -0002 2>/dev/null
$ sudo find / -xdev -type f -perm -0002 2>/dev/null
$ ls -ld /tmp /var/tmp /dev/shm

Audit sudo policy. Who can run what as whom.

$ sudo -l
$ sudo cat /etc/sudoers
$ sudo grep -rE 'NOPASSWD|ALL=' /etc/sudoers /etc/sudoers.d/ 2>/dev/null

Inspect ACLs. Mode bits hide ACL grants.

$ getfacl /path 2>/dev/null
$ sudo find / -xdev -type f \! -perm -o=r -exec getfacl {} \; 2>/dev/null | head

Check MAC enforcement. SELinux / AppArmor add a layer.

$ getenforce 2>/dev/null
$ ls -lZ /etc/passwd 2>/dev/null
$ aa-status 2>/dev/null

Set safe modes on operator artifacts. Keys, configs, scripts.

$ chmod 700 ~/.ssh
$ chmod 600 ~/.ssh/id_*
$ chmod 600 ~/.aws/credentials .env
$ chmod 700 ~/scripts/*.sh

Change ownership. Recursive, preserve, by reference.

$ sudo chown user:group /path
$ sudo chown -R --reference=/etc/skel /home/<user>
$ sudo chgrp -R ops /srv/ops
$ sudo chown -h user:group /path/symlink

Apply common mode patterns. Recursive directories vs files.

$ sudo find /srv/web -type d -exec chmod 755 {} +
$ sudo find /srv/web -type f -exec chmod 644 {} +
$ chmod u+s /usr/local/bin/tool        # SUID
$ chmod g+s /srv/shared                # SGID dir, inherits group

Grant or revoke an ACL. Per-user or per-group, beyond mode bits.

$ sudo setfacl -m u:alice:rx /srv/data
$ sudo setfacl -m d:g:ops:rwx /srv/data    # default ACL on new files
$ sudo setfacl -x u:alice /srv/data
$ sudo setfacl -b /srv/data                # strip all ACLs

Set or remove a capability. Finer-grained than SUID.

$ sudo setcap cap_net_bind_service=+ep /usr/local/bin/svc
$ sudo getcap /usr/local/bin/svc
$ sudo setcap -r /usr/local/bin/svc

Inspect and adjust umask. Default mode for newly-created files.

$ umask                       # current value
$ umask 027                   # tighter default
$ grep -rEn 'umask' /etc/profile /etc/profile.d/ ~/.bashrc 2>/dev/null

Common Patterns#

The modes that come up so often they’re worth memorizing, and the ones where the tooling rejects anything else. SSH, in particular, refuses to use a private key with permissions any wider than 600 and a config any wider than its expected mode.

Type

Mode

Reason

SSH private key

600

ssh refuses to use it otherwise

SSH config

600

ssh warns at higher modes

~/.ssh

700

directory must shield the keys it holds

~/.aws, ~/.gcloud

700

cloud credentials cache

~/.gnupg

700

GPG refuses on wider modes

.env

600

keep secrets private

Source code (file)

644

world-readable text

Source code (executable)

755

world-executable script or binary

Web-served static

644

read-only for the web server’s group

Cron job script

700 or 755

depends on who runs it

/etc/sudoers

440 or 600

sudo refuses otherwise

/etc/shadow

640 (root:shadow) or 000

distro-dependent; never world-readable

A few patterns the kernel actively ignores or treats specially:

  • SUID on shell scripts is ignored on every modern Linux kernel; the bit applies only to native executables. Wrap a script in a setuid C binary if you need it.

  • World-writable directories must have the sticky bit (1777, e.g. /tmp) or anyone can delete anyone else’s files.

  • Setgid on a directory causes new entries to inherit the directory’s group (g+s on /srv/shared); useful for shared group workspaces.

Files#

The on-disk source of truth for who can log in, what they can do once they’re in, and which subsystems are allowed to grant exceptions.

Users and groups#

Path

Purpose

/etc/passwd

account entries, one line per user (name:x:uid:gid:gecos:home:shell)

/etc/shadow

password hashes and aging data (root-readable only)

/etc/group

groups and their members

/etc/gshadow

group password and admin info (root-readable)

/etc/subuid, /etc/subgid

sub-id ranges for user namespaces (rootless containers)

/etc/login.defs

defaults for useradd, passwd, shadow

/etc/default/useradd

distribution-specific useradd defaults

/etc/skel/

files copied into a new user’s home

/etc/securetty

TTYs root may log in on (legacy)

sudo#

Path

Purpose

/etc/sudoers

main sudo policy (edit only with visudo)

/etc/sudoers.d/*

drop-in policy fragments (edit with visudo -f /etc/sudoers.d/myrule)

/var/log/sudo*, journalctl -t sudo

sudo audit trail

PAM (Pluggable Authentication Modules)#

Path

Purpose

/etc/pam.conf

legacy combined config

/etc/pam.d/

one file per service (sshd, login, sudo, passwd)

/etc/security/limits.conf

ulimit-style per-user resource limits

/etc/security/limits.d/*.conf

drop-in fragments

/etc/security/access.conf

pam_access rules

/etc/security/pwquality.conf

password complexity policy

/etc/security/faillock.conf

account lockout after failures

SELinux (Fedora / RHEL / Android)#

Path

Purpose

/etc/selinux/config

mode (enforcing / permissive / disabled) and policy

/etc/selinux/<policy>/

compiled policy modules

/var/log/audit/audit.log

AVC denial log

Tools include getenforce, setenforce, semanage, getsebool, setsebool, audit2allow, and ls -Z / ps -Z for contexts.

AppArmor (Ubuntu / SUSE)#

Path

Purpose

/etc/apparmor.d/

per-application profiles

/etc/apparmor.d/local/

local overrides

/var/log/apparmor/

denial logs

Tools include aa-status, aa-enforce, aa-complain, aa-genprof, and aa-logprof.

Capabilities and ACLs#

Path / tool

Purpose

getcap, setcap

per-binary capabilities (no config file; stored as extended attributes)

getfacl, setfacl

POSIX ACLs (extended attributes on the file)

acl mount option (ext-family)

filesystem must support and enable ACLs

Audit (Linux Audit subsystem)#

Path

Purpose

/etc/audit/auditd.conf

daemon config

/etc/audit/audit.rules

compiled rule set

/etc/audit/rules.d/*.rules

drop-in fragments

/var/log/audit/audit.log

audit events

References#

  • man 1 chmod, man 1 chown, man 1 umask (DAC primitives).

  • man 5 sudoers, man 8 sudo, man 8 visudo.

  • man 1 getfacl, man 1 setfacl, man 5 acl.

  • man 7 capabilities, man 8 getcap, man 8 setcap.

  • man 8 selinux, man 8 semanage, man 5 selinux_config.

  • man 8 apparmor, man 8 aa-status, man 5 apparmor.d.

  • man 8 auditctl, man 8 auditd, man 5 audit.rules.

  • man 5 pam.conf, man 8 pam, man 5 pam_unix.

  • Users for the identity layer this page builds on.

  • Hardening for the defender’s view of the same primitives.

  • Processes for how UIDs, GIDs, and capabilities live on a running process.

  • Linux for the command quick-reference.

  • Linux capabilities (LWN)

  • SELinux Project Wiki

  • AppArmor documentation