灯下哥谭 灯下哥谭
首页
关于
  • Hermes Agent 平台
  • Claude Code
  • OpenClaw
  • GPU 推理节点运维
  • DeepSeek Harness
  • MySQL 运维知识地图
  • Elasticsearch 运维知识地图
  • Redis 运维知识地图
  • TiDB 体系
  • DBA 常用 SQL 与命令
  • Nginx 运维知识地图
  • Prometheus 监控
  • Docker
  • Systemd
  • Iptables
  • Firewalld
  • Sshd
  • MySQL8 运维 SOP 手册
  • MySQL 实战 45 讲(读书笔记)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

灯下哥谭

灯还亮着
首页
关于
  • Hermes Agent 平台
  • Claude Code
  • OpenClaw
  • GPU 推理节点运维
  • DeepSeek Harness
  • MySQL 运维知识地图
  • Elasticsearch 运维知识地图
  • Redis 运维知识地图
  • TiDB 体系
  • DBA 常用 SQL 与命令
  • Nginx 运维知识地图
  • Prometheus 监控
  • Docker
  • Systemd
  • Iptables
  • Firewalld
  • Sshd
  • MySQL8 运维 SOP 手册
  • MySQL 实战 45 讲(读书笔记)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 工作笔记

    • 实用linux命令-nc
    • 实用linux命令-lsof
    • 实用linux命令-ss
    • Bash 备忘清单
    • Ansible 备忘清单
    • Linux 文件权限的三个反直觉陷阱
      • 基础:权限数字与字符表示的映射
      • 陷阱一:特殊权限位 setuid / setgid / sticky
        • setuid:临时提升执行者身份
        • setgid:目录继承所属组
        • sticky:保护目录中的他人文件
      • 陷阱二:umask 与新建文件的默认权限
      • 陷阱三:目录 write 权限与文件权限的解耦
        • sticky 位的真实用途:超越 /tmp
        • setgid 目录的协作验证:多人开发场景
      • 可复用要点
    • GPT分区使用 `parted` 扩展分区的操作流程
    • 实用linux命令-sed
    • VSCode快捷键备忘录
    • 实用linux命令-curl
  • 容器与编排

  • Nginx

  • 监控

  • 网络安全

  • 其他

  • Linux笔记
  • 工作笔记
灯下哥谭
2022-07-17
目录

Linux 文件权限的三个反直觉陷阱

理解权限数字(如 644、755)只是起点。生产环境中,权限问题的诡异表现往往来自三个容易被忽略的机制:特殊权限位(setuid/setgid/sticky)、umask 的减法逻辑,以及目录 write 权限与文件权限的解耦关系。本文通过三个可直接复现的场景,讲清这些陷阱的技术根因和防御方法。

版本说明

本文基于 Linux 内核 4.x/5.x/6.x 通用行为整理,涉及的权限机制为 POSIX 标准,在所有主流发行版(CentOS 7/8、Ubuntu 20.04/22.04/24.04、Debian 11/12)中表现一致。

# 基础:权限数字与字符表示的映射

在深入陷阱之前,先对齐基础表示法的互转逻辑。

权限数字是三位八进制数,分别对应文件所有者(user)、所属组(group)、**其他用户(others)**的权限组合。

数字 二进制 权限 含义
0 000 --- 无权限
1 001 --x 执行
2 010 -w- 写入
3 011 -wx 写入+执行
4 100 r-- 读取
5 101 r-x 读取+执行
6 110 rw- 读取+写入
7 111 rwx 读取+写入+执行

chmod 644 file.txt 展开为:

  • 所有者:6 = rw-(读取、写入)
  • 所属组:4 = r--(仅读取)
  • 其他用户:4 = r--(仅读取)

字符表示 -rw-r--r-- 的每一位对应关系:

- rw- r-- r--
|  |   |   |
|  |   |   └── 其他用户权限
|  |   └────── 所属组权限
|  └────────── 所有者权限
└───────────── 文件类型(-为普通文件,d为目录,l为链接)
1
2
3
4
5
6

# 陷阱一:特殊权限位 setuid / setgid / sticky

# setuid:临时提升执行者身份

当一个可执行文件被设置了 setuid 位(chmod u+s),任何用户执行该文件时,进程的有效 UID 会临时切换为文件所有者的 UID。

典型场景:/usr/bin/passwd 需要修改 /etc/shadow(仅 root 可写),但普通用户需要改密码。passwd 命令的权限为:

ls -l /usr/bin/passwd
1

预期输出:

-rwsr-xr-x 1 root root 68208 Jul 15  2022 /usr/bin/passwd
1

注意 rws 中的 s —— 这就是 setuid 位(位于所有者执行权限位)。

风险:若 setuid 程序存在漏洞,攻击者可以以文件所有者(通常是 root)身份执行任意代码。因此,审计脚本常扫描全盘的 setuid 文件:

find / -perm -4000 -type f 2>/dev/null
1

-4000 匹配 setuid 位(八进制 4000)。

# setgid:目录继承所属组

setgid(chmod g+s)在文件上的行为与 setuid 类似(临时切换有效 GID),但在目录上有独特效果:在该目录下新建的文件/子目录,其所属组自动继承父目录的组,而非创建者的主组。

应用场景:多用户共享目录。假设 /data/shared 属于 developers 组,设置 setgid 后,所有成员创建的文件组都是 developers,便于协作:

sudo chgrp developers /data/shared
sudo chmod g+s /data/shared
ls -ld /data/shared
1
2
3

预期输出:

drwxrwsr-x 2 root developers 4096 Jan 15 10:30 /data/shared
1

注意 rws —— 目录的 setgid 显示在所有者组的执行位。

验证继承行为:

# 切换到一个以 developers 为 supplementary group 的用户
touch /data/shared/newfile
ls -l /data/shared/newfile
1
2
3

预期输出:

-rw-r--r-- 1 alice developers 0 Jan 15 10:35 /data/shared/newfile
1

文件组自动为 developers,而非 alice 的主组。

# sticky:保护目录中的他人文件

sticky 位(chmod +t)仅对目录有意义。设置了 sticky 位的目录(如 /tmp),用户只能删除或重命名自己拥有的文件,即使该目录对所有人可写。

查看 /tmp 的权限:

ls -ld /tmp
1

预期输出:

drwxrwxrwt 10 root root 4096 Jan 15 09:00 /tmp
1

末尾的 t 表示 sticky 位已设置。

反直觉表现:假设 /tmp/shared 权限为 777 但未设 sticky,用户 A 可以删除用户 B 的文件。一旦设置 sticky,即使权限仍为 777,删除操作也受到限制。

设置 sticky 位:

chmod +t /data/upload
# 或八进制表示(在原有权限前加 1)
chmod 1777 /data/upload
1
2
3

验证:目录权限变为 drwxrwxrwt。

# 陷阱二:umask 与新建文件的默认权限

许多新手困惑:为什么 touch 新建的文件权限是 644(rw-r--r--),而不是 666(rw-rw-rw-)?目录为什么是 755 而不是 777?

答案在 umask —— 它定义了需要剥夺的权限。

查看当前 umask:

umask
1

预期输出(常见值):

0022
1

umask 也是三位八进制,含义与 chmod 相反:

  • 第一位:特殊权限位(通常忽略或硬编码为 0)
  • 第二位:剥夺文件所有者的哪些权限
  • 第三位:剥夺所属组的哪些权限
  • 第四位:剥夺其他用户的哪些权限

计算逻辑:

  • 文件默认基底:666(rw-rw-rw-),因为新建文件通常不应默认可执行
  • 目录默认基底:777(rwxrwxrwx),因为目录需要可执行位才能进入

实际权限 = 基底 - umask(按位取反后与):

文件:666 & ~022 = 644(rw-r--r--)
目录:777 & ~022 = 755(rwxr-xr-x)
1
2

验证实验:

# 临时修改 umask 为 0002(剥夺其他用户的写权限)
(umask 0002 && touch testfile && mkdir testdir && ls -ld testfile testdir)
1
2

预期输出:

-rw-rw-r-- 1 user user 0 Jan 15 11:00 testfile
drwxrwxr-x 2 user user 4096 Jan 15 11:00 testdir
1
2

清理:

rm -rf testfile testdir
1

常见 umask 值:

umask 文件默认 目录默认 适用场景
022 644 755 保守(其他用户只读)
002 664 775 组协作(同组成员可写)
007 660 770 严格(其他用户无权限)
077 600 700 私有(仅自己访问)

永久修改 umask:编辑 ~/.bashrc(用户级)或 /etc/profile(系统级):

echo 'umask 002' >> ~/.bashrc
source ~/.bashrc
1
2

# 陷阱三:目录 write 权限与文件权限的解耦

这是最常见的权限认知盲区:能否删除一个文件,取决于其父目录的权限,而非文件本身的权限。

场景复现:

# 创建测试目录结构
mkdir -p /tmp/perms_demo
cd /tmp/perms_demo

# 创建文件,设置权限为 000(任何人无权限)
touch protected_file
chmod 000 protected_file
ls -l protected_file
1
2
3
4
5
6
7
8

预期输出:

---------- 1 user user 0 Jan 15 11:30 protected_file
1

此时,即使文件本身权限为 000,只要父目录 /tmp/perms_demo 对某用户可写,该用户就可以删除 protected_file:

# 在另一个终端,以同组另一用户身份
rm /tmp/perms_demo/protected_file
1
2

预期结果:删除成功(若目录权限允许)。

防御措施:

  1. 敏感目录取消写权限:若目录不需要频繁增删文件,将其权限设为 755 而非 777
  2. 使用 sticky 位:如前文所述,chmod +t 可限制用户仅能删除自己的文件
  3. 使用 immutable 属性(ext4/xfs):
sudo chattr +i protected_file
lsattr protected_file
1
2

预期输出:

----i---------e------- protected_file
1

i 属性表示 immutable,即使是 root 也无法修改或删除,除非先移除该属性(chattr -i)。

验证保护效果:

rm protected_file
1

预期输出:

rm: cannot remove 'protected_file': Permission denied
1

清理实验环境:

sudo chattr -i protected_file
cd /tmp && rm -rf perms_demo
1
2

# sticky 位的真实用途:超越 /tmp

/tmp 的 sticky 位是教科书级示例,但生产中 sticky 位还有更广泛的应用场景:

FTP/SFTP 上传目录:多用户上传文件的共享目录,管理员希望所有人都能写入,但用户只能管理自己的文件。

sudo mkdir /data/ftp_upload
sudo chmod 1777 /data/ftp_upload
ls -ld /data/ftp_upload
1
2
3

预期输出:

drwxrwxrwt 2 root root 4096 Jan 15 14:00 /data/ftp_upload
1

在此目录下,用户 A 无法删除或重命名用户 B 的文件,即使两者都对目录有写权限。

共享日志目录:多服务向同一目录写日志,防止服务 A 的进程误删服务 B 的日志。

验证 sticky 位效果:

# 用户 alice 创建文件
sudo -u alice touch /data/ftp_upload/alice_file

# 用户 bob 尝试删除(即使 bob 在目录的写权限组中)
sudo -u bob rm /data/ftp_upload/alice_file
1
2
3
4
5

预期输出:

rm: cannot remove '/data/ftp_upload/alice_file': Operation not permitted
1

这正是 sticky 位的核心价值:在保持目录开放写入的同时,保护个体文件不被他人清理。

# setgid 目录的协作验证:多人开发场景

setgid 在多人开发环境中有明确的实用价值。以下是一个完整的验证流程:

场景设定:开发组 devteam 有三名成员 alice、bob、carol,共享代码目录 /data/project。

准备:

# 创建组和用户
sudo groupadd devteam
sudo usermod -aG devteam alice
sudo usermod -aG devteam bob
sudo usermod -aG devteam carol

# 创建共享目录并设置 setgid
sudo mkdir /data/project
sudo chgrp devteam /data/project
sudo chmod g+s /data/project
sudo chmod 2775 /data/project
1
2
3
4
5
6
7
8
9
10
11

验证继承行为:

# alice 创建文件
sudo -u alice touch /data/project/shared_module.py
ls -l /data/project/shared_module.py
1
2
3

预期输出:

-rw-r--r-- 1 alice devteam 0 Jan 15 14:30 /data/project/shared_module.py
1

注意文件组为 devteam(继承自父目录),而非 alice 的主组。这意味着 bob 和 carol 作为 devteam 成员,天然对该文件有组权限(此处为读权限)。

验证协作写入:若项目需要多人编辑同一文件,需配合 umask 002 让组内默认可写:

# 设置目录权限让组内默认可写
sudo chmod g+w /data/project
# 各用户将 umask 设为 002
echo 'umask 002' >> ~/.bashrc
1
2
3
4

此后创建的文件权限为 664(rw-rw-r--),组内成员可读写。

清理测试环境:

sudo rm -rf /data/project /data/ftp_upload
1

# 可复用要点

  1. 特殊权限位是双刃剑:setuid 提升权限带来便利也引入攻击面,生产环境应定期审计 find / -perm -4000;setgid 目录是多用户协作的简洁方案,文件组自动继承;sticky 位保护公共目录(如 FTP 上传区、共享日志目录)不被恶意清理。

  2. umask 是减法逻辑:新建文件权限 = 基底(666/777)& ~umask。协作场景将 umask 从 022 改为 002,配合 setgid 目录实现组内无缝协作。

  3. 删除权限在目录不在文件:文件权限 000 不能阻止删除,父目录写权限才是决定因素。敏感文件使用 chattr +i 或 sticky 目录提供双重保护。

  4. 验证优先于假设:权限问题排查时,同时检查 ls -ld 目录、ls -l 文件 和 id 用户名,确认 UID/GID 匹配预期。特殊权限位用 find / -perm /6000 定位。


🤖 Agent 可直接解析的元数据块(点击展开)
{
  "_meta": {
    "doc_version": "2022-07-17",
    "article_id": "linux-permission-traps",
    "profile_context": "any",
    "estimated_setup_time": "20min"
  },
  "quick_start": {
    "setuid_audit": "find / -perm -4000 -type f 2>/dev/null",
    "setgid_dir_setup": "sudo chmod g+s /data/shared",
    "sticky_dir_setup": "chmod +t /data/upload",
    "check_umask": "umask",
    "set_umask": "echo 'umask 002' >> ~/.bashrc",
    "immutable_file": "sudo chattr +i /path/to/file"
  },
  "safety_rules": [
    "setuid 程序必须定期审计,陌生 setuid 文件可能是入侵痕迹",
    "umask 002 适合协作场景,022 是保守默认值",
    "chattr +i 需要 root 权限解除,确保自己能访问 root 再设置"
  ],
  "verification": {
    "check_setuid": "ls -l /usr/bin/passwd | grep rws",
    "check_setgid": "ls -ld /data/shared | grep rws",
    "check_sticky": "ls -ld /tmp | grep rwt",
    "check_default_perms": "(umask && touch t && ls -l t && rm t)"
  },
  "troubleshooting": {
    "cannot_delete": "检查父目录权限(ls -ld 目录)而非文件权限",
    "group_not_inherited": "检查父目录是否设置 g+s,以及用户是否在 supplementary group 中"
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#SRE#Linux
上次更新: 9/11/2026

← Ansible 备忘清单 GPT分区使用 `parted` 扩展分区的操作流程→

最近更新
01
DeepSeek Harness 实战 06|学习笔记:插件、工具、技能不在同一个维度上 原创
09-11
02
DeepSeek Harness 实战 05|让两个编码 Agent 共用一份长期记忆 原创
09-09
03
DeepSeek Harness 实战 04|学习笔记:从「已知限制」里读出三处设计张力 原创
09-08
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式