Get the math right: rwx means different things for files vs directories
Most misunderstandings come from treating a directory's rwx as a file's rwx. For a file, r reads content, w writes content, x executes. For a directory, r lists the entries, w creates/deletes/renames entries inside it, and x traverses that directory to reach a target below.
That is why a directory showing only -r-------- looks readable yet cannot be entered—it lacks x; while one with x but no r lets you reach the files inside without being able to list names. These edges are the source of endless mysterious Permission denied errors, so set the model straight first.
ll -d /srv/data
# 目录必须有 x 才能被 traverse
chmod a+x /srv/data
# 查看后最直观的校验
ls -l /srv/data/secret.txt /srv/data
# 十进制速查:r=4 w=2 x=1
umaskEnter ACLs: when bit permissions cannot express one-user-one-right
A common business need is "dev can write, ops can write, everyone else read-only", which the classic nine bits cannot express since they give group only one bucket. So use setfacl/getfacl to grant write to specific users rather than stuffing everyone into one group.
ACLs change how permissions display: ls -l shows a + after the permission bits, and from then on the rwx bits represent the mask, not the real rights. A classic trap is running chmod over a + directory which silently wipes the ACL; a demo and a fix follow.
setfacl -m u:devuser:rwx -R /srv/app # 给特定用户递归写
setfacl -m u:opsuser:rwx /srv/app/deploy.sh
setfacl -x u:devuser /srv/app/secret # 撤销某人
# 错误示范:chmod 覆盖掉 ACL
chmod 644 /srv/app/deploy.sh # + 消失,ACL 被重置
# 修复对照:保留 ACL 前提下再改 mask
setfacl -m m::rwx /srv/app/deploy.sh
# 查看
getfacl /srv/app/deploy.shsetuid/SGID/sticky: the three special bits are a slippery board by the pool
setuid lets a binary run as its owner—/usr/bin/passwd runs as root. It is genuinely useful, but adding setuid to any script you have written is an incident waiting to happen: the shebang runs it through bash in a root context, multiplying the blast radius. SGID makes new files in a directory inherit the directory 's group, handy for shared collaboration dirs.
The sticky bit (the trailing 1 in 1777) lets only the owner delete their own files; /tmp is the live example. When judging special bits, do not trust only the s/t letter—uppercase S/T means the bit is set but x is not, so it does nothing and confuses everyone.
ls -l /usr/bin/passwd # -rwsr-xr-x 有 s 正常
find / -perm -4000 2>/dev/null # 审计所有 setuid 文件
chmod 4755 /opt/app/bin/app # setuid(通常不要对脚本用)
chmod 2755 -R /srv/shared # SGID 共享目录 组继承
# 大写 S/T = 位设了可能没 x
ls -l /tmp # drwxrwxrwtsudo: stop handing out ALL, minimal grants let you sleep
Dropping a user into the wheel/sudo group hands them the whole box, but the operational reality is often just 'that person only needs to restart Nginx'. Use visudo to grant single commands, reserve NOPASSWD for low-frequency side-effect-free actions, and keep dangerous system-admin commands behind the password prompt.
Command paths must be absolute, and beware alias and wildcard traps: NOPASSWD: ALL versus NOPASSWD: /usr/sbin/reboot are worlds apart. sudo -l shows what the current user may execute—the very first tool for verifying a grant.
sudo -l
# visudo 内容片段
# 错误示范:整机全部放开
# dev ALL=(ALL) NOPASSWD: ALL
# 修复对照:只给单一命令
dev ALL=(root) NOPASSWD: /usr/bin/systemctl restart nginx
dev ALL=(root) /usr/sbin/reboot, /usr/bin/systemctl stop firewalld
# 校验语法
visudo -c
sudo -l -U devVerify with two accounts, not by trusting the bit combo
Permission configs done on paper leak easily: the mask blocks someone already authorized, find cannot see setuid that root can, or a rule gets swallowed by a wildcard earlier in the chain. The most persuasive verification is switching to the affected account and actually trying ls/write/execute, then switching back to confirm root is unaffected.
For setuid and ACL the checks differ: setuid looks at the runtime euid (see with id -u or a whoami line), ACL checks that the target user can read the rwx matching mask. Bake the verification into a rerunnable script so rollback is one re-run.
# 错误示范:以为 777 一定对
# chmod 777 /srv/app
# 修复对照:按账号实测
sudo -u devuser ls -l /srv/app/deploy.sh
sudo -u devuser touch /srv/app/probe.txt
sudo -u devuser id -u # 测 setuid 时看 euid
# 脚本化验证
for u in devuser opsuser; do echo -n "$u: "; sudo -u $u test -r /srv/app/secret && echo "can read" || echo "denied"; done