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, |
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? ( |
Capabilities |
Does this binary hold the specific privilege the syscall needs? ( |
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. ( |
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 ( |
$ 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, |
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 |
Symbolic display in ls -l for the same modes.
Mode |
|
Notes |
|---|---|---|
4755 |
rwsr-xr-x |
setuid + 755 |
2755 |
rwxr-sr-x |
setgid + 755 |
1777 |
rwxrwxrwt |
sticky + 777 (this is |
4750 |
rwsr-x— |
setuid + 750 |
Capital letters mean the bit is set but the corresponding x is not.
Symbol |
Meaning |
|---|---|
|
setuid / setgid set, but no execute (rarely useful) |
|
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 |
|
op |
|
perm |
|
$ 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 |
|---|---|---|
|
effective |
active immediately on exec; if missing, the kernel won’t enforce it |
|
inheritable |
passed across |
|
permitted |
the upper bound the process may move into effective |
Common capabilities the operator meets in the wild.
Capability |
Grants |
|---|---|
|
open raw / packet sockets ( |
|
bind a TCP/UDP port below 1024 without being root |
|
configure interfaces, routes, firewalls |
|
bypass file read/write/execute permission checks |
|
bypass file read and directory traversal checks |
|
umbrella catch-all (mount, namespaces, many sysctls); near-root |
|
attach to any process with |
|
load / unload kernel modules |
|
change file owner |
|
signal any process |
|
arbitrary UID / GID change |
|
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 ( |
per-program profile (path-based) |
Modes |
|
|
View label |
|
|
Switch mode |
|
|
Audit log |
|
|
Translate denial |
|
|
Authoring tools |
|
|
$ 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 config |
600 |
|
|
700 |
directory must shield the keys it holds |
|
700 |
cloud credentials cache |
|
700 |
GPG refuses on wider modes |
|
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 |
|
440 or 600 |
|
|
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+son/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 |
|---|---|
|
account entries, one line per user ( |
|
password hashes and aging data (root-readable only) |
|
groups and their members |
|
group password and admin info (root-readable) |
|
sub-id ranges for user namespaces (rootless containers) |
|
defaults for |
|
distribution-specific |
|
files copied into a new user’s home |
|
TTYs root may log in on (legacy) |
sudo#
Path |
Purpose |
|---|---|
|
main sudo policy (edit only with |
|
drop-in policy fragments (edit with |
|
sudo audit trail |
PAM (Pluggable Authentication Modules)#
Path |
Purpose |
|---|---|
|
legacy combined config |
|
one file per service ( |
|
|
|
drop-in fragments |
|
|
|
password complexity policy |
|
account lockout after failures |
SELinux (Fedora / RHEL / Android)#
Path |
Purpose |
|---|---|
|
mode ( |
|
compiled policy modules |
|
AVC denial log |
Tools include getenforce, setenforce, semanage,
getsebool, setsebool, audit2allow, and ls -Z /
ps -Z for contexts.
AppArmor (Ubuntu / SUSE)#
Path |
Purpose |
|---|---|
|
per-application profiles |
|
local overrides |
|
denial logs |
Tools include aa-status, aa-enforce, aa-complain,
aa-genprof, and aa-logprof.
Capabilities and ACLs#
Path / tool |
Purpose |
|---|---|
|
per-binary capabilities (no config file; stored as extended attributes) |
|
POSIX ACLs (extended attributes on the file) |
|
filesystem must support and enable ACLs |
Audit (Linux Audit subsystem)#
Path |
Purpose |
|---|---|
|
daemon config |
|
compiled rule set |
|
drop-in fragments |
|
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.