Back to the series

Interactive lab · kernel internals

Linux permissions, visually

Eight passes over the same question: which identity does the kernel actually compare against an inode? Step the newgrp syscall sequence, toggle the 16 bits of i_mode, and run the DAC cascade yourself.

The stale groups dilemma — why usermod -aG fails open shells

When an admin adds you to a group, your open terminal does not inherit it. Step through the lifecycle.

Linux processes never poll /etc/group. Credentials are an immutable, reference-counted in-kernel object.

Kernel structure: task_struct → struct cred

Every thread has a task_struct pointing at its struct cred. Four distinct ID pairs live there, and only one of them decides file access.

Credential fieldNameKernel role
uid / gidReal IDIdentifies the user who spawned the process. Determines signal delivery permissions — who may kill it.
euid / egidEffective IDUsed for general authorization, creating IPC primitives, and evaluating privileged capabilities.
suid / sgidSaved set-IDPreserves the prior effective ID across a setuid binary, so a process can drop and re-elevate privilege safely.
fsuid / fsgidFilesystem IDThe ID actually used for file access. When the kernel checks inode permissions in the VFS it compares against fsuid, not euid.

NFS servers and other userspace file servers are why fsuid exists: they can impersonate a user for file access without also inheriting that user’s signal permissions.

Interactive newgrp syscall player

Six steps, from fork() to the refreshed shell. Watch which credential fields change at each one.

1 / 6fork()

Bash forks a child process. The child inherits identical copies of every credential pointer from the parent shell.

ID typeUID (user)GID (group)Supplementary groups
Real1000 (alice)1000 (alice)1000 (alice)
Effective1000 (alice)1000 (alice)1000 (alice)
Saved1000 (alice)1000 (alice)1000 (alice)
Filesystem1000 (alice)1000 (alice)1000 (alice)

newgrp is setuid-root. Its privileged setgid() call changes all four GID fields — real, effective, saved and filesystem — not just the effective one.

Inodes & dentry path resolution

Opening /var/log/app.log walks three layers of cache before any disk block is touched.

1. Dentry cache (RAM)

The kernel checks its hash table of directory entries, resolving the filename string to an inode number in a sub-microsecond RAM lookup.

2. Inode table (disk → VFS inode in RAM)

Reads the block group descriptor and inode bitmap from disk, loading a struct inode of 128 or 256 bytes holding i_mode, i_uid, i_gid, size and the extent tree.

3. Page cache & data blocks

Reads the extent pointers out of the inode and streams 4 KB physical blocks into the system page cache.

Interactive i_mode 16-bit grid

Toggle any bit, or load a preset, and watch the octal mode, the ls -l string and the file type stay in sync.

Octal mode
0755
ls -l string
-rwxr-xr-x
File type
Regular file
Special flags
None

The execute column carries two meanings at once. A set-id or sticky bit replaces x with s or t, and uppercases it when the execute bit underneath is clear — which is how ls shows you a set-id bit that can never actually fire.

Interactive DAC flowchart (generic_permission)

Four actors attempt a write to a file owned by alice:developers, mode 0640 (-rw-r-----).

  1. Check ownercurrent->fsuid == inode->i_uid

    Match: fsuid 1000 == i_uid 1000, and the owner bits carry 'w' (rw-).

  2. Check groupcurrent->fsgid == inode->i_gid, or in supplementary groups

    Skipped — the owner already matched.

  3. Check othersfall through to the last three mode bits

    Skipped.

  4. Root overridecapable_wrt_inode_uidgid(CAP_DAC_OVERRIDE)

    Skipped.

Access granted — owner write permitted

Root’s override comes last. The kernel first checks whether the permission bits naturally permit access; only on refusal does it test for CAP_DAC_OVERRIDE.

SGID directory demo

What changes about file creation when the setgid bit is applied to a directory.

Standard directory (0775)

owned by alice:developers

$ touch /data/plain/report.txt
$ ls -l /data/plain/report.txt
-rw-r--r-- 1 bob bob 0 Oct 25 10:00 report.txt

The file's group defaults to Bob's primary group, so team members in developers cannot write to it.

SGID directory (2775, drwxrwsr-x)

owned by alice:developers, SGID set

$ touch /data/collab/report.txt
$ ls -l /data/collab/report.txt
-rw-r--r-- 1 bob developers 0 Oct 25 10:00 report.txt

The kernel inherits the parent directory's GID. New subdirectories inherit the SGID bit as well.

Try it yourself

Run these in any Linux shell to read your own live credentials and inode metadata.

verify-permissions.sh
# 1. Inspect all four UIDs and GIDs of your current shell process
grep -E 'Uid|Gid|Groups' /proc/$$/status

# 2. Inspect inode metadata, permissions and the raw octal mode
stat -c "File: %n | Inode: %i | Octal: %a | Access: %A | Owner: %U(%u):%G(%g)" /etc/passwd

# 3. Simulate newgrp and observe the subshell's GIDs
newgrp developers
grep -E 'Gid|Groups' /proc/$$/status
exit