Linux Users and Permissions — Street Ops¶
Real-world operational scenarios for user and permission issues. These are the situations you'll actually face in production, not textbook exercises.
Emergency Root Access Recovery¶
Scenario: You're locked out of root on a physical/VM server¶
No one knows the root password, and the only sudo user's account is locked or deleted.
Recovery via GRUB (single-user mode):
- Reboot the machine
- At the GRUB menu, press
eto edit the boot entry - Find the line starting with
linuxorlinuxefi - Append
init=/bin/bashto the end of the line - Press
Ctrl+XorF10to boot
# You now have a root shell with root filesystem mounted read-only
# Remount root read-write
$ mount -o remount,rw /
# Reset root password
$ passwd root
# Or unlock a locked account
$ passwd -u deploy
$ usermod -U deploy
# If you need to fix sudoers
$ visudo
# Sync and reboot
$ sync
$ exec /sbin/init
# Or: reboot -f
Recovery via rescue/live USB:
# Boot from USB, mount the system root
$ mount /dev/sda2 /mnt
$ mount --bind /dev /mnt/dev
$ mount --bind /proc /mnt/proc
$ mount --bind /sys /mnt/sys
# Chroot into the system
$ chroot /mnt /bin/bash
# Now you're effectively root on the installed system
$ passwd root
$ exit
$ umount -R /mnt
$ reboot
For cloud instances (AWS/GCP/Azure):
You typically can't access GRUB. Instead: 1. Stop the instance 2. Detach the root volume 3. Attach it to a working instance as a secondary volume 4. Mount it, chroot, fix the issue 5. Reattach to the original instance
Locked Account Troubleshooting¶
Scenario: A user reports "Permission denied" when trying to SSH¶
Systematic diagnosis:
# 1. Check if account exists
$ id baduser
id: 'baduser': no such user
# 2. Check if account is locked (! or * prefix in shadow)
$ sudo grep baduser /etc/shadow
baduser:!$6$salt$hash...:19500:0:99999:7:::
# The ! prefix means the account is LOCKED
# 3. Check if account is expired
$ sudo chage -l baduser
Account expires : Jan 01, 2026 # <-- expired!
# 4. Check if password is expired
$ sudo chage -l baduser
Password expires : Dec 01, 2025 # <-- expired!
Password inactive : Jan 01, 2026 # <-- inactive period expired too
# 5. Check if shell is nologin
$ grep baduser /etc/passwd
baduser:x:1001:1001::/home/baduser:/sbin/nologin
# 6. Check PAM restrictions
$ sudo grep -r baduser /etc/security/access.conf
- : baduser : ALL # <-- explicitly denied
# 7. Check SSH-specific restrictions
$ sudo grep -E "^(AllowUsers|DenyUsers|AllowGroups|DenyGroups)" /etc/ssh/sshd_config
AllowGroups ssh-users # <-- user might not be in this group
# 8. Check faillock (too many failed attempts)
$ sudo faillock --user baduser
baduser:
When Type Source Valid
2026-03-19 10:30:00 RHOST 10.0.0.99 V
2026-03-19 10:30:05 RHOST 10.0.0.99 V
2026-03-19 10:30:10 RHOST 10.0.0.99 V
# Three failures — might be locked by pam_faillock
Fixes:
# Unlock account
$ sudo usermod -U baduser
# Reset failed login counter
$ sudo faillock --user baduser --reset
# Extend account expiry
$ sudo chage -E -1 baduser # Remove expiry
$ sudo chage -E 2027-12-31 baduser # Set new expiry
# Force password reset on next login
$ sudo chage -d 0 baduser
# Change shell from nologin
$ sudo usermod -s /bin/bash baduser
# Add to SSH allowed group
$ sudo usermod -aG ssh-users baduser
Permission Auditing¶
Scenario: Security team wants a report of SUID binaries and world-writable files¶
# Find all SUID binaries
$ sudo find / -perm -4000 -type f 2>/dev/null | sort
/usr/bin/chage
/usr/bin/gpasswd
/usr/bin/mount
/usr/bin/newgrp
/usr/bin/passwd
/usr/bin/su
/usr/bin/sudo
/usr/bin/umount
# Review this list — anything unexpected is a red flag
# Find all SGID binaries
$ sudo find / -perm -2000 -type f 2>/dev/null | sort
# Find world-writable files (excluding /proc, /sys, /tmp)
$ sudo find / -path /proc -prune -o -path /sys -prune -o -path /tmp -prune -o \
-perm -0002 -type f -print 2>/dev/null
# Find world-writable directories (without sticky bit — dangerous)
$ sudo find / -path /proc -prune -o -path /sys -prune -o \
-perm -0002 ! -perm -1000 -type d -print 2>/dev/null
# Find files with no owner
$ sudo find / -path /proc -prune -o -nouser -print 2>/dev/null
# Find files with no group
$ sudo find / -path /proc -prune -o -nogroup -print 2>/dev/null
# Audit home directory permissions
$ for dir in /home/*/; do
user=$(basename "$dir")
perms=$(stat -c '%a' "$dir")
echo "$user: $dir ($perms)"
if [ "$perms" != "700" ] && [ "$perms" != "750" ]; then
echo " WARNING: Home directory permissions too open"
fi
done
# Check for .ssh directories with wrong permissions
$ for dir in /home/*/.ssh; do
[ -d "$dir" ] || continue
perms=$(stat -c '%a' "$dir")
echo "$dir: $perms"
if [ "$perms" != "700" ]; then
echo " WARNING: .ssh directory should be 700"
fi
for f in "$dir"/authorized_keys "$dir"/id_rsa "$dir"/id_ed25519; do
[ -f "$f" ] || continue
fperms=$(stat -c '%a' "$f")
echo " $f: $fperms"
done
done
ACL Debugging¶
Scenario: A user has group access but still gets "Permission denied"¶
# Check standard permissions
$ ls -la /opt/project/data.csv
-rw-rw---- 1 app developers 1024 Mar 19 10:00 /opt/project/data.csv
# User is in the group...
$ id analyst
uid=1002(analyst) gid=1002(analyst) groups=1002(analyst),2000(developers)
# But wait — are there ACLs overriding?
$ getfacl /opt/project/data.csv
# file: opt/project/data.csv
# owner: app
# group: developers
user::rw-
user:analyst:--- # <-- EXPLICIT DENY for this user via ACL!
group::rw-
mask::rw-
other::---
# The named user ACL entry overrides the group permission.
# Fix: remove the restrictive ACL
$ sudo setfacl -x u:analyst /opt/project/data.csv
# Check the mask too — it can silently reduce effective permissions
$ getfacl /opt/project/data.csv
mask::r-- # <-- mask limits effective group/named-user perms to read-only!
# Fix mask
$ sudo setfacl -m m::rw- /opt/project/data.csv
Debugging the full permission chain¶
# Use namei to trace every directory in the path
$ namei -l /opt/project/subdir/data.csv
f: /opt/project/subdir/data.csv
drwxr-xr-x root root /
drwxr-xr-x root root opt
drwxr-x--- root devteam project # <-- analyst not in devteam!
drwxrwxr-x app developers subdir
-rw-rw---- app developers data.csv
# The user can't traverse /opt/project/ because they're not in devteam group
# Fix: either add analyst to devteam, or add an ACL on the directory
$ sudo setfacl -m u:analyst:x /opt/project/
sudo Log Analysis¶
Scenario: Investigate who ran what as root last night¶
# Search auth log for sudo usage
$ sudo grep "sudo:" /var/log/auth.log | grep "COMMAND" | tail -20
Mar 19 02:15:33 server01 sudo: deploy : TTY=pts/0 ; PWD=/home/deploy ; USER=root ; COMMAND=/usr/bin/systemctl restart nginx
Mar 19 02:16:01 server01 sudo: deploy : TTY=pts/0 ; PWD=/home/deploy ; USER=root ; COMMAND=/usr/bin/vim /etc/nginx/nginx.conf
Mar 19 03:45:22 server01 sudo: unknown : TTY=pts/1 ; PWD=/tmp ; USER=root ; COMMAND=/bin/bash
# Failed sudo attempts (potential security concern)
$ sudo grep "NOT in sudoers" /var/log/auth.log
Mar 19 03:44:55 server01 sudo: intruder : user NOT in sudoers ; TTY=pts/1 ; PWD=/tmp ; USER=root ; COMMAND=/bin/bash
# Using journalctl (if journal is persistent)
$ sudo journalctl _COMM=sudo --since "yesterday" --until "today" | grep COMMAND
# Who sudoed to root and when
$ sudo grep "session opened" /var/log/auth.log | grep sudo
Mar 19 02:15:33 server01 sudo: pam_unix(sudo:session): session opened for user root(uid=0) by deploy(uid=1000)
# Audit trail (if auditd is running)
$ sudo ausearch -m USER_CMD --start yesterday -i
Mass Permission Fix After Bad chmod -R¶
Scenario: Someone ran chmod -R 777 / or chmod -R 644 /var¶
This is an emergency. The system may become unstable quickly.
If chmod -R 777 / was run (worst case):
# SUID/SGID bits are destroyed. Critical binaries stop working.
# su, sudo, passwd, ping — all broken.
# If you still have a root shell:
# Fix the most critical files first
$ chmod 4755 /usr/bin/sudo
$ chmod 4755 /usr/bin/su
$ chmod 4755 /usr/bin/passwd
$ chmod 4755 /usr/bin/newgrp
$ chmod 0600 /etc/shadow
$ chmod 0600 /etc/gshadow
$ chmod 0644 /etc/passwd
$ chmod 0644 /etc/group
$ chmod 0440 /etc/sudoers
$ chmod 1777 /tmp
$ chmod 700 /root
# Fix SSH (or SSH will refuse to work)
$ chmod 700 /root/.ssh 2>/dev/null
$ chmod 600 /root/.ssh/authorized_keys 2>/dev/null
$ chmod 600 /etc/ssh/ssh_host_*_key
$ chmod 644 /etc/ssh/ssh_host_*_key.pub
$ chmod 644 /etc/ssh/sshd_config
# Restore from package manager (best approach for full recovery)
# Debian/Ubuntu:
$ dpkg --verify 2>/dev/null | grep "^..5" | awk '{print $2}' | while read pkg; do
apt-get install --reinstall "$pkg"
done
# RHEL/CentOS:
$ rpm -Va 2>/dev/null | grep "^.M" | awk '{print $NF}' | while read file; do
rpm -qf "$file" | head -1
done | sort -u | while read pkg; do
yum reinstall -y "$pkg"
done
If chmod -R 644 /var was run:
# Directories lost execute bit — nothing inside /var is accessible
# Fix: restore execute on directories only
$ sudo find /var -type d -exec chmod +x {} \;
# Then fix specific known permissions
$ sudo chmod 1777 /var/tmp
$ sudo chmod 755 /var/log /var/run /var/lib
$ sudo chmod 750 /var/log/journal 2>/dev/null
# Let package manager verify and fix
$ sudo dpkg --verify 2>&1 | grep "/var" | awk '{print $2}' | \
xargs -I{} rpm -qf {} 2>/dev/null | sort -u
NFS UID Mapping Issues¶
Scenario: Files created on NFS share show wrong ownership¶
# On the NFS client, files show as "nobody:nogroup"
$ ls -la /mnt/nfs/
-rw-r--r-- 1 nobody nogroup 1024 Mar 19 10:00 data.csv
# Or files show numeric UIDs instead of usernames
$ ls -la /mnt/nfs/
-rw-r--r-- 1 1000 1000 1024 Mar 19 10:00 data.csv
Diagnosis:
# Check if the UID exists on the client
$ id 1000
uid=1000(deploy) gid=1000(deploy) # Good — mapping exists
# If id fails, the UID doesn't have a local mapping
# Check NFS mount options
$ mount | grep nfs
192.168.1.10:/export on /mnt/nfs type nfs4 (rw,sec=sys,...)
# Check if idmapd is running (NFSv4)
$ systemctl status nfs-idmapd
# Check idmapd config
$ cat /etc/idmapd.conf
[General]
Domain = example.com # Must match on client AND server!
# Check if root_squash is causing issues
# On NFS server:
$ cat /etc/exports
/export 10.0.0.0/24(rw,sync,root_squash)
# root_squash maps UID 0 to nobody — this is normal and desired
# all_squash maps ALL users to nobody — might be the problem
/export 10.0.0.0/24(rw,sync,all_squash)
Fixes:
# Ensure UIDs/GIDs match between client and server
# Or use LDAP/SSSD for centralized identity
# For NFSv4, ensure idmapd domain matches
# Client AND server /etc/idmapd.conf:
[General]
Domain = example.com
# Restart idmapd after changes
$ sudo systemctl restart nfs-idmapd
# If using all_squash with anonuid/anongid for a shared directory:
# Server /etc/exports:
/export/shared 10.0.0.0/24(rw,sync,all_squash,anonuid=2000,anongid=2000)
# Clear the NFS client's idmap cache
$ sudo nfsidmap -c
Investigating "Permission Denied" for a Running Service¶
Scenario: A systemd service fails to start with permission errors¶
# Check the service logs
$ journalctl -u myapp.service --since "5 minutes ago"
Mar 19 10:45:22 server01 myapp[1234]: Error: EACCES: permission denied, open '/var/lib/myapp/data.db'
# Check what user the service runs as
$ systemctl show myapp.service -p User -p Group
User=myapp
Group=myapp
# Verify the user can access the file
$ sudo -u myapp test -r /var/lib/myapp/data.db && echo "OK" || echo "DENIED"
DENIED
# Check the full path chain
$ namei -l /var/lib/myapp/data.db
f: /var/lib/myapp/data.db
drwxr-xr-x root root /
drwxr-xr-x root root var
drwxr-xr-x root root lib
drwx------ root root myapp # <-- myapp user can't enter this directory!
-rw-r--r-- myapp myapp data.db
# Fix
$ sudo chown myapp:myapp /var/lib/myapp
$ sudo chmod 750 /var/lib/myapp
# Also check SELinux context if applicable
$ ls -laZ /var/lib/myapp/
$ sudo restorecon -Rv /var/lib/myapp/
# Check systemd hardening that might restrict access
$ systemctl show myapp.service | grep -E "(ProtectSystem|ProtectHome|ReadWrite|ReadOnly|Inaccessible)"
ProtectSystem=strict # <-- only /proc, /dev, /sys are writable
ReadWritePaths=/var/lib/myapp # <-- must be listed here!
Bulk User Management¶
Scenario: Onboard 50 new developers with consistent permissions¶
#!/bin/bash
# bulk-create-users.sh
GROUP="developers"
SHELL="/bin/bash"
EXPIRY="2027-12-31"
# Ensure group exists
getent group "$GROUP" > /dev/null || groupadd "$GROUP"
while IFS=',' read -r username fullname; do
# Skip header or empty lines
[[ "$username" =~ ^#|^$ ]] && continue
if id "$username" &>/dev/null; then
echo "SKIP: $username already exists"
continue
fi
useradd -m -s "$SHELL" -G "$GROUP,docker" -c "$fullname" -e "$EXPIRY" "$username"
# Set temporary password (force change on first login)
echo "${username}:TempPass123!" | chpasswd
chage -d 0 "$username"
# Set proper home directory permissions
chmod 750 "/home/$username"
# Create standard directories
mkdir -p "/home/$username/.ssh"
chmod 700 "/home/$username/.ssh"
chown -R "${username}:${username}" "/home/$username"
echo "CREATED: $username ($fullname)"
done < users.csv
echo "Done. All users must change password on first login."
Emergency: sudoers Syntax Error Recovery¶
Scenario: Someone edited /etc/sudoers directly (not visudo) and introduced a syntax error. Now nobody can sudo.¶
# Attempting sudo gives:
$ sudo -l
>>> /etc/sudoers: syntax error near line 25 <<<
sudo: parse error in /etc/sudoers near line 25
sudo: no valid sudoers sources found, quitting
# Options:
# 1. If you have an open root shell: just fix it
$ visudo # Will show you the error and let you fix it
# 2. If you have pkexec (PolicyKit) available:
$ pkexec visudo
# 3. Boot into single-user mode (see Emergency Root Access Recovery above)
# 4. If the error is in a drop-in file:
$ pkexec rm /etc/sudoers.d/broken-file
# Or rename it:
$ pkexec mv /etc/sudoers.d/broken-file /etc/sudoers.d/broken-file.bak
Prevention: Always use visudo. If you must automate sudoers changes:
# Validate syntax before installing
$ visudo -cf /tmp/new-sudoers-fragment
/tmp/new-sudoers-fragment: parsed OK
# Only then install
$ cp /tmp/new-sudoers-fragment /etc/sudoers.d/99-myconfig
$ chmod 440 /etc/sudoers.d/99-myconfig