Skip to content
Learn Security

Linux File Permissions and Ownership Management

The article delves into the intricate realm of Linux file permissions and ownership management, elucidating the vital role they play in securing endpoint devices. It discusses the nuances of setting access controls, managing user privileges, and ensuring data integrity on Linux systems. By understanding and implementing these practices effectively, organizations can fortify their endpoints against unauthorized access and potential security breaches.

Linux is a multi-user operating system, which means that multiple users can access the same system and resources.

Linux has always been built with multiple users in mind. From the very beginning, its design assumed that many people would be sharing the same system — and that not everyone should be able to touch everything. The mechanism that enforces this is Discretionary Access Control (DAC), built into the kernel through file permissions and ownership rules.

For anyone working in cybersecurity, knowing how these permissions work is not optional. Misconfigured file permissions are one of the most common ways attackers escalate privileges or walk off with data they shouldn’t have. Whether you’re hardening a server, auditing a system, or just trying to understand why a script won’t run, this is foundational knowledge.

Let’s break it all down — from how the permission string is read, to the special bits that trip people up, to the audit commands you should be running regularly.


Understanding the Permission String

In Linux, everything is a file — regular files, directories, sockets, device handles, all of it. Access rules for each are stored in the file’s inode, a metadata structure the kernel maintains. When you run ls -l, that first column of characters is your window into those rules.

%%{init: {'theme': 'dark', 'themeVariables': {'primaryColor': '#1e293b', 'primaryTextColor': '#e2e8f0', 'primaryBorderColor': '#475569', 'lineColor': '#0284c7', 'secondaryColor': '#0f172a', 'tertiaryColor': '#1e293b', 'background': '#0f172a', 'mainBkg': '#1e293b', 'nodeBorder': '#475569', 'clusterBkg': '#0f172a', 'titleColor': '#e2e8f0', 'edgeLabelBackground': '#1e293b'}}}%% graph TD classDef accent fill:#1e293b,stroke:#475569,stroke-width:2px,color:#e2e8f0; classDef rootNode fill:#0f172a,stroke:#0284c7,stroke-width:2px,color:#38bdf8; Root["Linux Permission String"]:::rootNode Root --> Type["File Type Identifier
(-, d, l, c, b, s, p)"]:::accent Root --> Owner["Owner (User)
r w x — Octal 7"]:::accent Root --> Group["Group Access
r - x — Octal 5"]:::accent Root --> Others["Others (World)
r - - — Octal 4"]:::accent Owner --> SUID["Special Bit: SUID (s)
Runs as File Owner"]:::accent Group --> SGID["Special Bit: SGID (s)
Runs as Group / Inherits Group"]:::accent Others --> Sticky["Special Bit: Sticky (t)
Only Owner Can Delete"]:::accent

The 10-character string breaks down like this:

  1. Character 1 — File Type: Tells you what kind of object this is.

    • - : Regular file
    • d : Directory
    • l : Symbolic link
    • c : Character device (e.g., /dev/tty)
    • b : Block device (e.g., /dev/sda)
    • s : Local socket
    • p : Named pipe (FIFO)
  2. Characters 2–4 — Owner Permissions: What the file’s owner can do.

  3. Characters 5–7 — Group Permissions: What members of the file’s group can do.

  4. Characters 8–10 — Others Permissions: What everyone else on the system can do.

Each of those three-character blocks can contain Read (r), Write (w), and Execute (x) — or a dash if that permission isn’t granted.

Advertisement

From Bits to Octal: How Numbers Map to Permissions

Under the hood, Linux represents permissions as binary bits — each permission is either on (1) or off (0). Knowing this makes octal notation feel much less arbitrary:

  • Read (r) = binary 100 = 4
  • Write (w) = binary 010 = 2
  • Execute (x) = binary 001 = 1

Add them up for each segment and you get a single digit representing that category’s access level:

OctalPermissionsSymbolicBinary
0None---000
1Execute only--x001
2Write only-w-010
3Write + Execute-wx011
4Read onlyr--100
5Read + Executer-x101
6Read + Writerw-110
7Full accessrwx111

Take rwxr-xr-x as an example:

  • Owner: rwx = 4 + 2 + 1 = 7
  • Group: r-x = 4 + 0 + 1 = 5
  • Others: r-x = 4 + 0 + 1 = 5
  • Result: 755

Common Permission Modes in Practice

  • chmod 777 file — Everyone gets full access. Never use this in production.
  • chmod 755 script.sh — Owner can modify and run it; everyone else can read and execute it.
  • chmod 644 config.txt — Owner can read and edit; others can only read.
  • chmod 600 id_rsa — Only the owner can read and write. Essential for SSH private keys.
  • chmod 700 private_dir — Only the owner can enter the directory at all.

[!WARNING] A common mistake: thinking chmod 700 means read-only for others. It doesn’t — rwx------ means group and world users have zero access, not read-only access. If you want full access for the owner and read/execute for everyone else, use 755. For files where execute isn’t needed, use 744.


Changing Permissions and Ownership

chmod — Two Ways to Use It

You can set permissions using octal numbers (as above) or symbolic notation, which targets specific categories and makes incremental changes easier to reason about.

Symbolic notation uses scope letters — u (user/owner), g (group), o (others), a (all) — combined with operators (+ to add, - to remove, = to set exactly).

CommandWhat It Does
chmod +x run.shAdds execute permission for everyone
chmod u-w config.cfgRemoves write permission from the owner
chmod o=r public.txtSets others to read-only, removing any other access they had
chmod u+wx,g-x,o=rx fileMixed update across all three categories in one command

chown and chgrp — Changing Who Owns the File

Only root (or a user with sudo) can change file ownership.

# Change the owner
sudo chown alice database.db

# Change the group
sudo chgrp security database.db

# Change both at once
sudo chown alice:security database.db

# Apply recursively to a directory and all its contents
sudo chown -R alice:security /var/www/html/

Special Permission Bits: SUID, SGID, and the Sticky Bit

Standard permissions cover most situations, but Linux has three additional bits that modify how execution works. These come up constantly in security assessments, so it’s worth understanding them well.

SUID — Set User ID

  • Octal value: 4000 (shows as s in the owner’s execute position: rws------)
  • When set on an executable, it runs with the file owner’s privileges, not the caller’s.
  • The classic example is /usr/bin/passwd — it’s owned by root and SUID-flagged, which is how a regular user can update their own password entry in /etc/shadow without having root access themselves.
  • The security risk is real: if a binary that can drop into a shell (like find, vim, or nmap) is SUID root, any local user can use it to run commands as root. This is a staple of privilege escalation.

SGID — Set Group ID

  • Octal value: 2000 (shows as s in the group’s execute position)
  • On files: the binary executes with the owning group’s permissions.
  • On directories: files created inside inherit the directory’s group, not the creator’s primary group. This is useful for shared project folders where you want consistent group ownership on all new files.

Sticky Bit

  • Octal value: 1000 (shows as t in the others’ execute position: rwxrwxrwt)
  • On a directory, only the file’s owner, the directory’s owner, or root can delete or rename files inside — even if the directory itself is world-writable.
  • The /tmp directory (drwxrwxrwt) is the canonical example. Anyone can write there, but no one can delete someone else’s files.
Special BitOctalFile EffectDirectory Effect
SUID4000Runs as file ownerN/A
SGID2000Runs as file groupNew files inherit parent group
Sticky Bit1000N/AOnly owners can delete

To set these:

chmod u+s executable_file    # SUID
chmod g+s shared_directory   # SGID
chmod +t shared_directory    # Sticky bit
chmod 4755 script            # SUID + rwxr-xr-x in one command

Hardening Default Permissions

umask — Setting Sane Defaults at Creation Time

Every time a file or directory is created, the system needs a starting point for permissions. That’s what umask does — it subtracts from the maximum default templates:

  • File template: 666 (rw-rw-rw-)
  • Directory template: 777 (rwxrwxrwx)

With the standard umask of 022:

  • New files: 666 minus 022 = 644 (rw-r--r--)
  • New directories: 777 minus 022 = 755 (rwxr-xr-x)

For environments that handle sensitive data, tighten the umask to 027:

  • New files: 666 minus 027 = 640 (no access for others at all)
  • New directories: 777 minus 027 = 750

For strict isolation, use 077:

  • New files: 600 (owner read/write only)
  • New directories: 700 (owner only)
# Check your current umask
umask

# Set it temporarily
umask 027

To make it permanent, add umask 027 to /etc/profile (system-wide) or ~/.bashrc / ~/.zshrc (per user).


File Attributes: Protection Beyond Permissions

Standard permissions, even when set correctly, can still be overridden by root. Filesystem-level attributes go further.

Immutable bit (+i) — No one, not even root, can modify, rename, append to, or delete the file:

sudo chattr +i /etc/resolv.conf
lsattr /etc/resolv.conf
sudo chattr -i /etc/resolv.conf   # Remove it

Append-only bit (+a) — Data can only be added to the end of the file; existing content can’t be altered or deleted. This is ideal for log files you want to protect from tampering:

sudo chattr +a /var/log/secure_audit.log

Access Control Lists — Granular Per-User Rules

Sometimes the owner/group/other model isn’t fine-grained enough. What if you need to give a single user read access to a file, but they’re not the owner and not in the group? That’s what POSIX ACLs are for.

# See current ACLs on a file
getfacl sensitive_report.txt

# Grant read-only access to a specific user
setfacl -m u:bob:r sensitive_report.txt

# Revoke that access
setfacl -x u:bob sensitive_report.txt

Security Audit: What to Check Regularly

These commands should be part of any standard system audit or hardening review.

Find SUID Binaries

find / -perm -4000 -type f 2>/dev/null

Cross-reference the results against GTFOBins — a curated list of binaries that can be abused when they carry SUID.

Find SGID Binaries

find / -perm -2000 -type f 2>/dev/null

Find World-Writable Files

These should almost never exist outside /tmp and similar scratch directories:

find / -perm -o+w -type f ! -path "/proc/*" ! -path "/sys/*" 2>/dev/null

Find Orphaned Files

When a user account is deleted, their files remain — owned by a UID that no longer maps to anyone. If a new user is later created with the same UID, they automatically inherit those files:

find / -nouser 2>/dev/null
find / -nogroup 2>/dev/null

Verify Critical Config Files

# /etc/passwd should be world-readable but only root-writable
ls -l /etc/passwd   # Expected: -rw-r--r-- (644)

# /etc/shadow holds password hashes — no world access at all
ls -l /etc/shadow   # Expected: -rw-r----- (640) or -r-------- (400)

Building these checks into your configuration management pipeline — whether that’s Ansible, Chef, or a simple cron job running a shell script — means misconfigurations get caught before they become incidents.


Share article

Subscribe to my newsletter

Receive my case study and the latest articles on my WhatsApp Channel.

Warning