How Linux Decides Who Can Open a File (A Visual Deep Dive)
Kernel credentials, newgrp internals, in-memory Inodes, and the exact order of Discretionary Access Control.
Canonical home: blog.overflowbyte.cloud
Interactive companion
The three instruments this article argues from, running right here. No embed, no second page.
Step the newgrp syscall sequence
Bash forks a child process. The child inherits identical copies of every credential pointer from the parent shell.
| ID type | UID (user) | GID (group) | Supplementary groups |
|---|---|---|---|
| Real | 1000 (alice) | 1000 (alice) | 1000 (alice) |
| Effective | 1000 (alice) | 1000 (alice) | 1000 (alice) |
| Saved | 1000 (alice) | 1000 (alice) | 1000 (alice) |
| Filesystem | 1000 (alice) | 1000 (alice) | 1000 (alice) |
Toggle the 16 bits of i_mode
- Octal mode
- 0755
- ls -l string
- -rwxr-xr-x
- File type
- Regular file
- Special flags
- None
Run the DAC cascade
- Check owner
current->fsuid == inode->i_uidMatch: fsuid 1000 == i_uid 1000, and the owner bits carry 'w' (rw-).
- Check group
current->fsgid == inode->i_gid, or in supplementary groupsSkipped — the owner already matched.
- Check others
fall through to the last three mode bitsSkipped.
- Root override
capable_wrt_inode_uidgid(CAP_DAC_OVERRIDE)Skipped.
Access granted — owner write permitted
You add a developer to a new group:
sudo usermod -aG developers alice
Alice runs cd /opt/projects/dev or tries opening a shared file.
Result: Permission denied.
Why does an open interactive shell refuse to see newly assigned groups? What data structures in the Linux kernel actually govern permissions? And why do most explanations of Linux file permissions get three critical details wrong?
In this ByteByteGo-style visual architectural explainer, we trace the entire journey from /etc/group down to kernel task_struct, VFS Inodes, and the exact step-by-step logic inside generic_permission().
1. The Stale Groups Problem: Why Open Shells Never Re-read /etc/group
To understand why usermod -aG fails open shells, we must look at how credentials enter the kernel:
[Login / SSH] ---> PAM reads /etc/passwd & /etc/group
│
▼
Kernel allocates struct cred in RAM
│
▼
PID 1042 (bash) points to struct cred
- Authentication Step: When Alice logs in via SSH or a terminal display manager, PAM reads
/etc/passwdand/etc/group. - Kernel Initialization: The kernel allocates a reference-counted
struct credin memory and attaches it to the shell process (task_struct). - Decoupling from Disk: Once allocated, running processes never re-read disk files like
/etc/group. Modifying/etc/groupupdates bytes on your disk block, but PID 1042 still references its existing in-memorystruct cred.
2. Kernel Architecture: task_struct → struct cred
Inside <linux/sched.h>, every running thread is tracked by a struct task_struct. Inside that struct lies a pointer to struct cred:
+-------------------------------------------------------+
| struct task_struct |
| pid_t pid; |
| const struct cred __rcu *real_cred; /* Real creds */ |
| const struct cred __rcu *cred; /* Eff. creds */ |
+-------------------------------------------------------+
│
▼
+-------------------------------------------------------+
| struct cred |
| atomic_t usage; |
| kuid_t uid; /* Real UID */ |
| kgid_t gid; /* Real GID */ |
| kuid_t suid; /* Saved UID */ |
| kgid_t sgid; /* Saved GID */ |
| kuid_t euid; /* Effective UID */ |
| kgid_t egid; /* Effective GID */ |
| kuid_t fsuid; /* Filesystem UID (USED FOR FILES!)*/|
| kgid_t fsgid; /* Filesystem GID (USED FOR FILES!)*/|
| struct group_info *group_info; /* Supplementary */ |
+-------------------------------------------------------+
The 4 UIDs and GIDs: What Each Actually Does
| Credential | Role | Why It Exists |
|---|---|---|
Real ID (uid/gid) | Process Ownership | Identifies who invoked the process. Governs signal delivery (kill). |
Effective ID (euid/egid) | General Authorization | Evaluates capabilities, creating IPC message queues, shared memory. |
Saved ID (suid/sgid) | State Preservation | Allows setuid binaries to drop root privileges and re-elevate them safely. |
Filesystem ID (fsuid/fsgid) | File Checks | The actual ID evaluated during VFS file checks! |
[!IMPORTANT] Technical Correction #1: File permission checks do not compare the inode owner against
euid. Linux specifically compares againstfsuid. Linux introducedfsuidso NFS and network daemons could check file access on behalf of a remote user without granting that remote user permission to signal or kill the daemon process.
3. What newgrp Actually Does: The 6-Step Syscall Sequence
When Alice runs newgrp developers, here is the exact syscall sequence:
[PID 1042: bash]
│
├─ 1. fork() ─────────────► [PID 1080: Child Process]
│ │
│ 2. execve("/usr/bin/newgrp") (SetUID Root)
│ │ --> euid becomes 0 (root)
│ │
│ 3. setgroups(developers, ...)
│ │ --> Updates supplementary list
│ │
│ 4. setgid(developers_gid)
│ │ --> Privileged call sets ALL 4 GIDs!
│ │
│ 5. setuid(alice_uid)
│ │ --> Drops root permanently
│ │
│ 6. execve("/bin/bash")
│ │ --> Replaces PID 1080 with refreshed shell!
▼ ▼
[Suspended Parent] [New Active Shell with Developers Group]
[!IMPORTANT] Technical Correction #2:
newgrpchanges much more than justegidorfsgid. Because it runs with root privilege, itssetgid()syscall sets all four GIDs (gid,egid,sgid,fsgid) to the target group, and itssetgroups()syscall adds the group to the supplementary group array.
4. Inodes in Memory: How Path Lookup Works
When a process requests an open("/var/log/app.log", O_RDWR):
- Dentry Cache Lookup: The kernel checks the in-memory Directory Entry (
dentry) cache. If cached, it immediately retrieves the in-memorystruct inode. - Inode Table Resolution: If not in cache, the kernel reads the filesystem block group descriptor and inode table from physical storage, allocating an in-memory
struct inode. - Metadata Inspection: The inode contains:
i_uid: Numerical owner user ID.i_gid: Numerical owner group ID.i_mode: A 16-bit integer encoding file type, special bits, and permissions.
5. Decoding the 16-Bit i_mode Grid
Every file permission in Linux is stored as a single 16-bit unsigned integer:
+---------------+---------------+---------------+---------------+
| 15 14 13 12 | 11 10 9 | 8 7 6 | 5 4 3 | 2 1 0 |
| File Type | Special Bits | Owner (User) | Group | Other |
+---------------+---------------+---------------+---------------+---------------+
| 1 0 0 0 | 0 1 0 | 1 1 1 | 1 1 1 | 0 0 0 |
+---------------+---------------+---------------+---------------+---------------+
Regular (010) SGID (2) r w x (7) r w x (7) - - - (0)
Octal representation: 0102770 → File mode 2770 (drwxrws---).
6. DAC Flowchart: How generic_permission() Evaluates Access
When testing whether Alice, Bob, or Carol can access a file, the kernel calls generic_permission() in fs/namei.c:
Does fsuid == inode.i_uid?
/ \
YES NO
/ \
Check Owner Bits Does fsgid == inode.i_gid OR
(User r/w/x bits) in supplementary group list?
/ \ / \
MATCH NO MATCH YES NO
/ \ / \
GRANT \ Check Group Bits Check Other Bits
\ (Group r/w/x) (Other r/w/x)
\ / \ / \
\ MATCH NO MATCH MATCH NO MATCH
\ / \ / \
\ GRANT \ GRANT \
\ \ \
└──────────────────────┴───────────────────┘
│
Are mode bits refusing access?
│
▼
Does process have CAP_DAC_OVERRIDE
or CAP_DAC_READ_SEARCH?
/ \
YES NO
/ \
GRANT DENY (-EACCES)
[!IMPORTANT] Technical Correction #3: Root's override comes last! The kernel does not short-circuit root at the very beginning of standard DAC checks. The mode bits are evaluated first. Only if the mode bits refuse the requested access does
generic_permission()invoke capability checks likecapable_wrt_inode_uidgid(CAP_DAC_OVERRIDE).
7. The Collaborative Directory: SGID in Action
When collaborating in a team directory:
# Set SGID on shared directory
sudo chmod 2770 /opt/projects/dev
sudo chown root:developers /opt/projects/dev
- Any file created inside
/opt/projects/devautomatically inherits the groupdevelopersrather than the author's primary group. - Any subdirectory created automatically inherits both the group and the
SGIDbit!
8. Verify It on Your Machine Right Now
Open your terminal and run:
# 1. Inspect your current shell's four UIDs and GIDs:
grep -E 'Uid|Gid|Groups' /proc/$$/status
# 2. Inspect Inode metadata, raw octal, and ownership of /etc/passwd:
stat -c "Inode: %i | Mode: %a (%A) | Owner: %U(%u):%G(%g)" /etc/passwd
# 3. Test newgrp and observe GIDs updating in real-time:
newgrp <your-group>
grep -E 'Gid|Groups' /proc/$$/status
exit
Interactive visual models, the 16-bit live calculator, and step-by-step state animations are available on our Live Interactive Visual Lab on OverflowByte.
Full lab setups, EX294 exam drills, and low-level Linux administration guides live in my Learning Notes →