Linux Files, Directories, Links, Permissions and ACLs
A pathname is not the file itself. Directories map names to inode-backed objects, hard links add another name for the same inode, and symbolic links store a path to resolve later. Permissions are also evaluated along the path, not only on the final file. Administrators who skip those distinctions often delete the wrong name, expose a directory through an ACL mask, or apply a recursive change across a mount or symbolic link boundary they did not intend to cross.
Model names, objects and path traversal separately
A regular file has an inode containing metadata and references to data. Directory entries associate names with inode numbers. Removing one hard link removes one name; storage is reclaimed only when the link count and open references permit it. A symbolic link has its own inode and stores a path, which may become dangling or resolve differently inside a chroot, container or changed mount layout.
For a directory, read permits listing names, execute permits traversal and write permits changing entries, subject to sticky-bit and policy controls. Effective access can also involve POSIX ACL entries, their mask, SELinux labels, mount options and application privilege. Diagnose each layer rather than changing mode to 777.
Build the working model
| Layer | Question to answer | Evidence |
|---|---|---|
| Path | Which names and directories are traversed? | namei -om and resolved path |
| Object | Which inode and filesystem own the data? | stat, device/inode and link count |
| Discretionary access | Which owner, group, mode and ACL apply? | getfacl including effective mask |
| Mandatory access | Which SELinux context and policy decision apply? | ls -Z and AVC evidence |
| Mount boundary | Which filesystem and options contain the object? | findmnt -T and mount options |
Implementation sequence
- Resolve without modifying. Use
namei,readlink,statandfindmntto understand the path. - Identify the access subject. Record the real/effective account, supplementary groups and service confinement.
- Read modes and ACLs together. An ACL mask can reduce named-user or group permissions even when entries appear broader.
- Separate ownership from collaboration. Prefer a defined group, setgid directory and default ACL over repeated broad mode changes.
- Bound recursion. Inventory the exact tree, mounts, link behavior and object count before any recursive operation.
- Verify as the intended account. Root success does not prove an application or user can traverse and use the path.
Commands and expected evidence
mkdir -p /tmp/nitwings-links/source
printf '%s\n' sample > /tmp/nitwings-links/source/data.txt
ln /tmp/nitwings-links/source/data.txt /tmp/nitwings-links/hard-name
ln -s source/data.txt /tmp/nitwings-links/symbolic-name
stat -c '%d:%i links=%h mode=%A owner=%U:%G name=%n' /tmp/nitwings-links/source/data.txt /tmp/nitwings-links/hard-name /tmp/nitwings-links/symbolic-name
readlink /tmp/nitwings-links/symbolic-name
readlink -f /tmp/nitwings-links/symbolic-name
namei -om /tmp/nitwings-links/source/data.txt
findmnt -T /tmp/nitwings-links/source/data.txt
getfacl -p /tmp/nitwings-links/source/data.txt
ls -lZ /tmp/nitwings-links/source/data.txt- Matching device and inode numbers prove the two hard-link names refer to the same inode on that filesystem. The symbolic link has its own inode.
readlink -ffollows the current path to a resolved destination. Do not use the result as authorization if an attacker can change path components concurrently.namei -omshows each directory component and makes a missing execute permission visible.- ACL and SELinux evidence complement mode bits. Changing only one layer may hide rather than solve the access decision.
Evidence and acceptance criteria
| Evidence | Healthy result | Failure meaning |
|---|---|---|
| Object identity | Expected device, inode, type and link count | Wrong object, bind mount or link assumption |
| Path traversal | Every required directory permits the intended traversal | Final-file mode is irrelevant until the path can be reached |
| ACL result | Named entries and mask produce intended effective access | Displayed ACL entry may be limited by the mask |
| SELinux context | Type matches the service and approved path purpose | Policy denial or incorrectly labelled content |
| Recursive scope | Reviewed object list excludes other mounts and unintended links | Change can escape the approved tree or alter unrelated data |
Worked scenario: the file is readable but the service gets permission denied
An application file shows group-readable mode, and an administrator repeatedly widens it. The service still fails because its account cannot traverse a parent directory. After execute permission is corrected through the intended service group, SELinux then records that the custom path has a type not permitted to the service.
The team restores ordinary discretionary permissions, assigns the documented SELinux file-context rule for that application path, applies it with restorecon, and retests as the service account. The fix addresses both decisions without making the file world-writable or disabling enforcement.
Practical how-to cases
Case 1: Compare hard and symbolic links
Prove which names share an inode and what happens when the original name is removed.
d=$(mktemp -d)
printf data >"$d/original"
ln "$d/original" "$d/hard"
ln -s original "$d/symbolic"
stat -c '%i %h %F %n' "$d/original" "$d/hard" "$d/symbolic"
rm -- "$d/original"
cat "$d/hard"
readlink "$d/symbolic"| Checkpoint | What to establish |
|---|---|
| Expected result | The hard link retains the file and inode; the symbolic link retains path text but becomes dangling. |
| If it fails | A hard-link failure across filesystems is expected because inode identity is filesystem-local. |
| Safe recovery | Recreate the intended name from the verified hard link or correct the symbolic target; never guess the desired content. |
Case 2: Build shared group access
Create a collaboration directory whose new files inherit the project group.
sudo groupadd -f webops
sudo install -d -o root -g webops -m 2770 /srv/webops
sudo -u student touch /srv/webops/test 2>/dev/null || true
namei -om /srv/webops/test
stat -c '%A %U:%G %n' /srv/webops /srv/webops/test| Checkpoint | What to establish |
|---|---|
| Expected result | Authorized members can traverse and create; the directory setgid bit makes new entries inherit webops. |
| If it fails | Permission denied can come from membership, parent traversal, ACL mask, SELinux or read-only mount state. |
| Safe recovery | Remove the test file and restore the recorded owner/mode on the exact directory. Do not run recursive chmod on /srv. |
Case 3: Grant and verify an ACL
Give one named user read access without changing the file owning group.
sudo setfacl -m u:student:r-- /srv/webops/report.txt
getfacl -p /srv/webops/report.txt
sudo -u student head -n 1 /srv/webops/report.txt
namei -om /srv/webops/report.txt| Checkpoint | What to establish |
|---|---|
| Expected result | The access ACL names student and the effective mask permits read; every parent permits traversal. |
| If it fails | An ACL entry can exist but be limited by its mask, or a parent can block traversal. |
| Safe recovery | Remove only the added entry with setfacl -x u:student and verify the resulting ACL. |
Case 4: Repair default ownership without following surprises
Preview a bounded tree before applying a recursive ownership correction.
sudo find /srv/webops -xdev -printf '%u:%g %m %p\n' | head -50
sudo find /srv/webops -xdev -type f ! -group webops -print
# After review only:
sudo find /srv/webops -xdev -type f ! -group webops -exec chgrp webops -- {} +| Checkpoint | What to establish |
|---|---|
| Expected result | Only files on the intended filesystem and outside the expected group appear in the preview; the reviewed set is corrected. |
| If it fails | A large or unexpected result means the boundary or assumption is wrong. |
| Safe recovery | Stop before mutation, refine the predicate, and restore ownership from the captured manifest if a reviewed change is incorrect. |
Independent practice tasks
- Create a team directory with setgid and a default ACL, then prove inheritance using two lab users.
- Show how umask 0022 and 0027 change newly created file and directory modes.
- Find files writable by others inside a bounded lab filesystem without crossing mounts.
- Recover access to a file blocked separately by parent traversal, ACL mask and SELinux label.
For this lesson on Linux Files, Links, Permissions and ACLs, complete each task without copying the worked command sequence. Record the initial state, exact change, verification, negative test and recovery command. A task is unfinished if it works now but does not survive a reboot where persistence is required.
Troubleshooting by symptom
| Symptom | Inspect first | Defensible next action |
|---|---|---|
| Permission denied despite readable final file | Parent traversal, ACL mask, SELinux and mount options | Correct the exact denying layer and retest as the subject |
| Deleting source appears not to free space | Hard-link count and processes holding an unlinked file | Find remaining names/open descriptors; do not delete unrelated data |
| Symbolic link works on one host only | Relative target, mount layout, chroot/container root | Use a stable intended path and verify inside the real execution context |
| Recursive ownership takes too long or crosses data | Mount boundaries, object count and link traversal | Stop safely, compare evidence and use a bounded find -xdev plan |
Unsafe operations and recovery boundaries
- Unsafe:
chmod -R 777grants broad write access and does not fix SELinux, path or identity design. Use the narrow owner/group/ACL required. - Unsafe: recursive
chownon an unresolved application or filesystem root can break packages, services and security boundaries. Inventory exact targets first. - Unsafe: following mutable symbolic links during privileged automation can redirect a change. Work from verified file descriptors or controlled trees where possible.
Rewritten knowledge checks
Guided lab and acceptance test
- Create the hard and symbolic links shown in the command block and compare inode, type and link count.
- Remove the source filename and prove which names still access the data. Recreate the lab before continuing.
- Create a group collaboration directory with setgid behavior and a default ACL for one lab user.
- Use
nameiandgetfaclto predict access before testing as that user. - Remove parent-directory traversal in the lab, observe the exact failure, then restore only the required permission.
- Document why mode bits, ACLs, SELinux and mount options are separate evidence layers.