Linux File Permissions, Ownership, and POSIX ACLs
I enforced least-privilege file access using the standard user/group/other model, special bits, and POSIX ACLs for finer control. Explicit, auditable modes and ACLs let multiple users share directories without over-granting access.
Objective & Context
Permissions are the first access-control layer on every Linux file. This lab covers octal modes, ownership, setuid/setgid/sticky bits, umask defaults, and ACLs for cases the three-class model cannot express, aligning to NIST AC-6 least privilege.
Learning Objectives
- Read and set octal permission modes with chmod for files and directories.
- Change file ownership and group ownership with chown and chgrp.
- Apply setuid, setgid, and sticky bits and explain what each one does.
- Predict default permissions from umask values.
- Grant per-user access beyond the owner/group/other model using POSIX ACLs.
Environment & Prerequisites
Prerequisites
- Linux with coreutils and the acl package:
sudo apt install -y aclorsudo dnf install -y acl. - Root or sudo access, since changing ownership requires elevation.
- Two or more test user accounts and a shared directory to experiment on.
- Completion of the file management lab is recommended.
Step-by-Step Execution
1. Set explicit octal modes
chmod 750 /srv/app && chown app:web /srv/appExpected output:
(no output on success; verify with: stat -c "%a %U:%G %n" /srv/app)
750 app:web /srv/app
What this teaches: Octal 750 means rwxr-x---: full control for the owner, read/execute for the group, nothing for others. Directories need the execute bit for traversal.
2. Grant a user access via ACL
setfacl -m u:deploy:rx /srv/app && getfacl /srv/appExpected output:
# file: /srv/app
# owner: root
# group: web
user::rwx
user:deploy:r-x
group::r-x
mask::r-x
other::---
What this teaches: setfacl adds an access control entry for one user without changing the base mode; getfacl reveals the effective permissions, including the mask entry that caps every named user and group entry. If you later grant u:deploy:rwx but the mask stays r-x, deploy still gets only r-x, which is the number one reason ACL grants appear not to work.
3. Set a setgid directory for shared group ownership
chmod 2775 /srv/sharedExpected output:
(no output on success; verify with: stat -c "%a %n" /srv/shared)
2775 /srv/shared
What this teaches: The leading 2 is the setgid bit: new files in the directory inherit the directory's group instead of the creator's primary group, which keeps shared trees consistently owned.
How It Works
Every Linux file carries three permission triples: one for the owning user, one for the owning group, and one for everyone else. Each triple is three bits (read, write, execute), conventionally written as one octal digit, so 750 is a compact way of saying rwxr-x---. The kernel checks them in order and stops at the first class that matches the requesting process.
The special bits extend this model. setuid (4) on an executable runs the program as the file's owner, setgid (2) runs it as the file's group or forces group inheritance on directories, and the sticky bit (1) on a directory like /tmp lets users delete only their own files. umask then subtracts from the default 666/777 at creation time, which is why new files are 644 and new directories 755 under the common 022 umask.
POSIX ACLs exist because the three-class model cannot express rules like 'give deploy read access but nobody else outside the group'. An ACL attaches named user and group entries to the file, and getfacl shows a mask entry that caps the effective rights of all named entries. Checking the mask is the step most people skip when an ACL 'does not work'.
Validation & Testing
As each test user, attempt allowed and disallowed actions and confirm the kernel enforces the expected result. Pass criteria: owner/group/other behave per mode, the ACL grants exactly the named user, and setgid propagates group ownership to new files.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| ACL grants access but the user is still denied | The ACL mask entry caps effective permissions below the grant | Check getfacl output and raise the mask with setfacl -m m::rx |
| chmod 777 seemed necessary to make it work | Ownership or group membership is wrong, not the mode | Fix with chown/chgrp and use 750/640 instead of opening to everyone |
| New files ignore the directory group | setgid bit is missing on the shared directory | Apply chmod g+s so new files inherit the directory group |
| Script fails with permission denied despite +x | The filesystem is mounted noexec, or the shebang interpreter lacks rights | Remount without noexec or move the script to an executable path |
| umask change had no effect on existing files | umask only applies at file creation time | Adjust existing files explicitly with chmod |
| setuid on a shell script does nothing | The kernel ignores setuid on interpreted scripts for safety | Compile a helper binary or use sudo rules instead |
Advanced: Troubleshooting
- Unexpected access: an ACL may override base bits; inspect with getfacl.
- New files wrong group: set the setgid bit on the parent directory.
- Too-open defaults: tighten umask (027) for service accounts.
Key Results
- Enforced least privilege with explicit octal modes across service dirs.
- Granted per-user access via ACLs without widening group membership.
- Standardized shared directories with setgid for consistent ownership.
- Hardened default creation modes with a stricter umask.
Key Takeaways
- Octal modes encode three triples; the kernel stops at the first class that matches.
- Directories need the execute bit for traversal, not just listing.
- setgid directories keep shared trees under one group automatically.
- umask only shapes newly created files; it never changes existing ones.
- When ACLs misbehave, check the mask entry first.
Practice Challenges
Challenge 1: Secure a web root
Goal: set /srv/app so the owner has full control, group web can read and traverse, and others get nothing, then verify with stat.
Hint: one octal digit per class.
Challenge 2: Debug a denied deploy
Goal: a deploy user gets permission denied on a group-readable file; use namei -l and getfacl to find whether the blocker is the path, the group, or the mask.
Hint: every parent directory needs the execute bit.