2023 年,我干过一件现在想起来还觉得好笑的事:我对着一个文件,把它的权限,从 644 一路改到了 777——把所有人的读、写、执行权限全开到底——可那个文件,还是访问不了。事情是这样的:我给一台服务器上的 Nginx,配了一个新的静态文件目录,不是默认的 /usr/share/nginx/html,而是我自己建的 /data/www。配完一访问,浏览器给我一个 403 Forbidden。403 是"禁止访问",我太熟了——权限问题嘛。我 ls -l 看那个文件:权限 644,属主是 nginx,一切正常。我又把目录一级一级往上查权限,全都对。可还是 403。我有点上头,直接 chmod -R 777 /data/www——我把这个目录,对全世界,彻底敞开了。然后刷新——还是 403。这一下我懵了。一个文件,权限已经是 777,在我所知的整个 Linux 权限体系里,这意味着"任何人、做任何事,都被允许"。可 Nginx 就是打不开它。Nginx 的错误日志里写着 Permission denied——"权限被拒绝"。我盯着那个 777 和那句 Permission denied,它俩面对面站着,公然地互相矛盾。是哪个"权限"被拒绝了?我以为我认识的那个权限,已经全开了啊。这件事逼着我把 SELinux、安全上下文、audit.log 这一整套我一直假装不存在的东西彻底理清了。本文复盘这次实战。
问题背景
环境:CentOS 7,Nginx,静态文件目录改到了 /data/www
事故现象:
- 访问 /data/www 下的静态文件,浏览器报 403 Forbidden
- ★ 文件权限 644 -> 改成 777,依然 403
- ls -l 看属主、权限,全都正常
现场排查:
# 1. 文件权限看着完全正常
$ ls -l /data/www/index.html
-rwxrwxrwx 1 nginx nginx 1240 ... # ★ 已经 777 了,还是不行
# 2. Nginx 错误日志:Permission denied
$ tail /var/log/nginx/error.log
... open() "/data/www/index.html" failed (13: Permission denied)
# ^^^^^^^^^^^^^^^^^ ★ 权限被拒
# 3. ★ 权限都 777 了还报权限拒绝 —— 怀疑 SELinux
$ getenforce
Enforcing # ★ SELinux 开着,而且在强制模式
# 4. ★ 看文件的 SELinux 上下文(context)
$ ls -Z /data/www/index.html
-rwxrwxrwx nginx nginx unconfined_u:object_r:default_t:s0 index.html
# ^^^^^^^^^ ★ 类型是 default_t
# 5. ★ 对比 Nginx 默认目录里文件的上下文
$ ls -Z /usr/share/nginx/html/index.html
-rw-r--r-- root root system_u:object_r:httpd_sys_content_t:s0 index.html
# ^^^^^^^^^^^^^^^^^^^ ★ 是这个类型!
# 6. ★ 看 SELinux 的拦截记录,实锤
$ ausearch -m avc -ts recent | tail
type=AVC ... denied { read } ... comm="nginx"
... tcontext=...:default_t:s0 tclass=file
# ★ SELinux 明确拒绝了 nginx 进程 read 一个 default_t 类型的文件
根因(后来想清楚的):
1. ★ Linux 上其实有【两套】权限系统,叠在一起:
- DAC(自主访问控制):就是 rwx、属主属组 ——
我一直以为的"权限",chmod 改的就是它。
- MAC(强制访问控制):★ 就是 SELinux ——
一套独立的、更严格的权限系统,我一直忽略它。
2. ★ 一次访问,必须【两套都放行】才能成。DAC 我
开到了 777,可 MAC(SELinux)这一关没过。
3. SELinux 的逻辑和 rwx 完全不同:它给每个文件、
每个进程都贴一个【类型标签(context)】,然后
按规则规定"哪个类型的进程,能碰哪个类型的文件"。
4. Nginx 进程的类型是 httpd_t,规则规定它只能读
httpd_sys_content_t 类型的文件。而我在 /data/www
下新建的文件,类型是 default_t —— 不在允许之列。
5. ★ 所以:不管 rwx 改成多少,SELinux 那一关只看
【类型标签】对不对,标签不对,777 也没用。
不是 rwx 的问题,是 SELinux 这第二套权限没放行。
修复 1:权限明明对却 Permission denied——先看 SELinux
# === ★ 一个判断:rwx 全对却还报权限拒绝,先怀疑 SELinux ===
# === ★ 典型信号:rwx 和报错"公然矛盾" ===
# 出现下面这种情况,几乎可以直接怀疑 SELinux:
# - 文件权限明明够(甚至 777),却报 Permission denied。
# - 属主属组都对,服务还是访问不了文件。
# - 服务监听一个非标准端口,起不来/连不上。
# - 程序写一个目录,权限够却 Permission denied。
# ★ 共同点:用 rwx 那套逻辑【完全解释不通】。
# 解释不通,就是 SELinux 在背后动手的信号。
# === ★ 第一步:SELinux 现在是什么状态 ===
$ getenforce
Enforcing
# ★ 三种状态:
# - Enforcing :强制模式 —— SELinux 规则【真的拦】。
# - Permissive:宽容模式 —— 规则不拦,但【记录】
# 本来会拦的事(排查神器,见下)。
# - Disabled :彻底关闭。
$ sestatus # 看更全的状态
SELinux status: enabled
Current mode: enforcing
# === ★ 第二步:关键验证 —— 临时切到 Permissive 试试 ===
# 这是确认"是不是 SELinux 干的"最干脆的一招:
$ setenforce 0 # 临时切成 Permissive(不拦了,只记录)
# 现在再访问一次那个文件:
# - ★ 能访问了 -> 实锤,就是 SELinux 拦的。
# - 还是不行 -> 不是 SELinux,回头查别的。
$ setenforce 1 # ★ 验证完【立刻】切回 Enforcing
# ⚠ setenforce 0 只是【临时验证】手段,绝不是修复 ——
# 验证完一定切回来,别把它当解决方案(见修复 5)。
# === ★ 一个要破除的坏习惯:别动不动就关 SELinux ===
# 很多人遇到 SELinux 拦,第一反应是永久关掉它
# (改 /etc/selinux/config 为 disabled)。
# ★ 这等于因为防盗门有点不好开,就把防盗门拆了。
# SELinux 是一道真实有用的安全防线 —— 它的价值
# 恰恰是:就算某个服务被攻破,SELinux 也能把
# 攻击者能碰的东西死死限制住。正确做法是
# 【学会配它】,不是拆了它(修复 5)。
# === 认知 ===
# ★ "rwx 怎么看都对,却还是 Permission denied" ——
# 这句话本身,就是 SELinux 最响亮的报警。getenforce
# 一下,setenforce 0 验证一下,真相立刻明朗。
修复 2:SELinux 是什么——rwx 之上的第二套权限
# === ★ 理解 SELinux:它和 rwx 是【两套】东西 ===
# === ★ 第一套:DAC —— 你早就认识的 rwx ===
# DAC = 自主访问控制。就是:
# - 文件有属主、属组、其他人,各有 rwx。
# - chmod / chown 改的就是它。
# ★ 它的逻辑是"谁拥有,谁说了算":文件属主可以
# 自主决定把权限开给谁。这是【自主】的含义。
# === ★ 第二套:MAC —— SELinux ===
# MAC = 强制访问控制。SELinux 就是 Linux 上 MAC 的
# 实现。它的逻辑和 DAC 完全不同:
# - 不看"谁拥有",看"你是什么、要碰的是什么"。
# - 规则由【系统统一的策略】定死,文件属主
# 【无权】自己放宽 —— 这是【强制】的含义。
# ★ 一句话:DAC 问"你够不够格";MAC 问"按规矩,
# 你这种身份的程序,到底准不准碰这种东西"。
# === ★ 最关键的一句:两套权限,要【都】放行 ===
# 一次文件访问能不能成,是【两道关卡串联】:
# 访问请求 -> 过 DAC(rwx)这关 -> 过 MAC(SELinux)
# 这关 -> 才算成功。
# ★ 任何一关拒绝,整个访问就失败。
# ★ 我这次:DAC 我开到了 777(这关过了),但 MAC
# 没过 —— 串联里有一关没过,结果就是失败。
# 我却一直只在 DAC 这一关上使劲。
# === ★ SELinux 的核心机制:给万物贴"类型标签" ===
# SELinux 给系统里的一切,都贴一个叫 context
# (安全上下文)的标签:
# - 每个【文件】有一个 context(类型,如
# httpd_sys_content_t)。
# - 每个【进程】也有一个 context(类型,叫
# domain,如 httpd_t)。
# 然后,SELinux 的策略规则,本质上就是一张大表:
# "httpd_t 这种进程,允许 read httpd_sys_content_t
# 这种文件" —— 一条一条这样的规则。
# ★ 进程要碰文件,SELinux 就查这张表:你这个 domain,
# 对那个文件的 type,有没有被授予这个动作。没有,
# 就 denied —— 和 rwx 是 777 还是 000,毫无关系。
# === 认知 ===
# ★ SELinux 不是"更严格的 rwx",它是【另一套维度
# 完全不同的权限】。它不关心属主和 rwx,它只关心
# "进程的类型"和"文件的类型"配不配。理解了这个,
# "777 还被拒"就一点都不矛盾了。
修复 3:看懂 context——文件和进程的"类型标签"
# === ★ 学会读 SELinux 的标签:context ===
# === ★ context 的格式:四段 ===
# 用户:角色:类型:级别
# user:role:type:level
$ ls -Z /usr/share/nginx/html/index.html
system_u:object_r:httpd_sys_content_t:s0
# ^^^^^^^^ ^^^^^^^^ ^^^^^^^^^^^^^^^^^^ ^^
# 用户 角色 ★ 类型(type) 级别
# ★ 日常排查,90% 的问题都出在【第三段:类型】。
# user / role / level 多数时候不用管。
# === ★ 看文件的 context:ls -Z ===
$ ls -Z /data/www/
unconfined_u:object_r:default_t:s0 index.html
# ★ 我这次的文件,类型是 default_t —— 一个"没被
# 特别归类"的默认类型。Nginx 没权限读它。
# 对比一下"对的"应该长什么样:
$ ls -Z /usr/share/nginx/html/
system_u:object_r:httpd_sys_content_t:s0 index.html
# ★ httpd_sys_content_t —— "给 httpd 当网页内容的
# 文件"。Nginx 要读的,就得是这个类型。
# === ★ 看进程的 context:ps -Z ===
$ ps -eZ | grep nginx
system_u:system_r:httpd_t:s0 8866 ? nginx: worker
# ^^^^^^^ ★ Nginx 进程的类型(domain)是 httpd_t
# ★ 于是这次的事就清楚了:
# httpd_t 的进程,想 read 一个 default_t 的文件 ——
# 策略表里没这条规则 -> denied。
# === ★ 常见的几个文件类型,眼熟一下 ===
# httpd_sys_content_t —— 网站静态内容(能被 httpd 读)
# httpd_sys_rw_content_t—— 允许 httpd 读写的目录
# httpd_log_t —— httpd 的日志
# var_log_t —— /var/log 下的日志
# ssh_home_t —— ~/.ssh 相关
# default_t —— ★ 没被特别归类的"默认",
# 新建在非标准路径的文件常是它
# === ★ 为什么我新建的文件会是 default_t ===
# SELinux 有一套规则,规定"某个【路径】下新建的
# 文件,默认贴什么类型标签"。
# - /usr/share/nginx/html/ 这个路径,规则说
# "这里的文件标 httpd_sys_content_t"。
# - ★ /data/www 这个路径,是我自己新建的,SELinux
# 的规则里【没有针对它的条目】 -> 落到 default_t。
# ★ 这就是"换了个非标准目录就 403"的根本原因 ——
# 新目录没有对应的 SELinux 标签规则。
# === 认知 ===
# ★ 排查 SELinux,就是 ls -Z 看文件类型、ps -Z 看
# 进程类型,然后判断"这个进程类型,该不该能碰
# 这个文件类型"。问题几乎都在【类型(type)】这段。
修复 4:读 SELinux 日志——它拦了什么,都记着
# === ★ SELinux 拦的每一下,都写进了 audit 日志 ===
# === ★ 拦截记录叫 AVC,在 audit.log 里 ===
# SELinux 每拒绝一次访问,都生成一条 AVC
# (Access Vector Cache)记录,写进:
$ tail /var/log/audit/audit.log
type=AVC msg=audit(...): avc: denied { read } for
pid=8866 comm="nginx" name="index.html"
scontext=system_u:system_r:httpd_t:s0
tcontext=unconfined_u:object_r:default_t:s0
tclass=file
# ★ 逐字段读懂这条记录:
# - denied { read } -> 被拒的动作是 read
# - comm="nginx" -> 是 nginx 这个进程
# - scontext=...httpd_t -> 源:进程的类型是 httpd_t
# - tcontext=...default_t-> 目标:文件的类型是 default_t
# - tclass=file -> 目标是个普通文件
# ★ 翻译成人话:httpd_t 的进程想 read 一个 default_t
# 的文件,被 SELinux 拒了。问题和解法都在这条里。
# === ★ ausearch:比 tail 更好用,专门搜 AVC ===
$ ausearch -m avc -ts recent # 最近的 AVC 拒绝记录
$ ausearch -m avc -ts today # 今天的
$ ausearch -m avc -c nginx # 只看 nginx 相关的
# -m avc:只看 AVC 类型;-ts:时间范围;-c:按命令名筛。
# === ★ sealert / audit2why:让它"说人话"并给建议 ===
# 原始 AVC 不好读。这两个工具能把它翻译成大白话,
# 还会【直接告诉你怎么修】:
$ yum install -y setroubleshoot-server # 提供 sealert
$ sealert -a /var/log/audit/audit.log
# ★ 输出会像这样:
# "SELinux is preventing nginx from read access on
# the file index.html."
# 并给出建议:"If you want to allow ... you need to
# change the file context to httpd_sys_content_t,
# run: semanage fcontext ... ; restorecon ..."
# ★ audit2why:解释"为什么被拦":
$ ausearch -m avc -ts recent | audit2why
# === ★ 一个排查技巧:Permissive 模式抓全 ===
# 如果一个操作要碰好几样东西,Enforcing 下它撞第一
# 个拦截就失败了,你看不到后面还会被拦什么。
# ★ 临时 setenforce 0(Permissive),让操作完整跑
# 一遍 —— SELinux 不拦了,但【把所有本该拦的都
# 记进 audit.log】。这样能一次性抓全所有拦截点。
$ setenforce 0
$ # 完整执行一遍出问题的操作
$ ausearch -m avc -ts recent # 一次看全所有 denied
$ setenforce 1 # ★ 抓完切回来
# === 认知 ===
# ★ SELinux 不是个"沉默地拦你"的黑盒。它把每一次
# 拦截,连人带物、连动作,全记在 audit.log 里。
# ausearch + sealert,问题是什么、怎么修,它直接
# 摊开告诉你。
修复 5:正确解法——给资源配对 SELinux,而不是关掉它
# === ★ 解法:让文件/端口的 SELinux 标签,符合规则 ===
# === ★ 解法 1:给文件打对的 context(本文这次)===
# 问题是 /data/www 的文件类型是 default_t,要把它
# 改成 httpd_sys_content_t。
# ★ 错误示范:只用 chcon 改
$ chcon -t httpd_sys_content_t -R /data/www
# chcon 能立刻改,403 也确实没了 —— ★ 但它是【临时】的!
# 一旦将来 restorecon、或文件系统重新打标签,
# 会按"路径规则"把它【改回 default_t】。治标不治本。
# ★ 正确做法:semanage fcontext —— 改"路径规则"本身
# 告诉 SELinux:"以后 /data/www 下的文件,都该是
# httpd_sys_content_t":
$ semanage fcontext -a -t httpd_sys_content_t "/data/www(/.*)?"
# ^^ 新增一条规则 ^^^^^^^^^^^^^^ 路径正则
# 然后用 restorecon 按【新规则】给现有文件重新打标签:
$ restorecon -Rv /data/www
# ★ semanage fcontext 改的是规则(永久),restorecon
# 是按规则去落实。这套组合才是【根治】 —— 以后
# 在这目录新建文件,也会自动是对的类型。
$ ls -Z /data/www/index.html # 验证:类型对了
# === ★ 解法 2:布尔值 setsebool —— 开关式的策略调整 ===
# 有些需求不是改文件标签,而是 SELinux 预留了一个
# "开关(boolean)"。比如要允许 httpd 发起网络
# 连接(反向代理常需要):
$ getsebool -a | grep httpd # 看 httpd 相关的所有开关
httpd_can_network_connect --> off
# 打开它(-P 表示永久):
$ setsebool -P httpd_can_network_connect on
# ★ 典型场景:nginx 做反代连后端报错,多半就是这个
# 开关没开。布尔值是 SELinux 留给你的"标准旋钮",
# 优先找有没有现成的布尔值,比写自定义策略简单。
# === ★ 解法 3:非标准端口,要 semanage port 登记 ===
# 让一个服务监听非默认端口(如 Nginx 听 8888),
# SELinux 也会拦 —— 因为 8888 不在 http 端口类型里:
$ semanage port -l | grep http_port
http_port_t tcp 80, 81, 443, 8080, ...
# 把 8888 登记进 http 端口类型:
$ semanage port -a -t http_port_t -p tcp 8888
# ★ "服务换了端口就起不来",SELinux 是常见元凶之一。
# === ★ 解法 4:实在没有现成规则,用 audit2allow 生成 ===
# 极少数情况,需求很特殊,没有现成的 fcontext/boolean
# 能满足。可以根据 AVC 记录生成一条自定义策略:
$ ausearch -m avc -ts recent | audit2allow -M mypolicy
$ semodule -i mypolicy.pp # 加载这条自定义策略
# ⚠ 慎用:要先看懂 audit2allow 生成的规则【放行了
# 什么】,别盲目放行 —— 那会削弱 SELinux 的保护。
# === ★ 解法 5:为什么不要"直接关掉 SELinux" ===
# setenforce 0 / config 改 disabled,确实能"解决"
# 所有 SELinux 问题 —— 代价是你拆掉了一整道安全
# 防线。★ SELinux 的核心价值:即便 Nginx 被入侵,
# 它也只能在 httpd_t 的牢笼里活动,碰不了
# /etc/shadow、碰不了别的服务的数据。关掉它,
# 这层"出事也能兜住"的保险就没了。
# ★ 正确姿势:遇到拦截,花十分钟用 ausearch 看清、
# 用 semanage 配对 —— 而不是拆门。
# === 验证 ===
$ ls -Z /data/www/index.html # 文件类型 httpd_sys_content_t
$ getenforce # 应为 Enforcing(没被你关掉)
$ curl -I http://localhost/index.html # 200,不再 403
$ ausearch -m avc -ts recent # 不再有新的 denied
# ★ SELinux 仍是 Enforcing、访问也正常 —— 这才叫
# "修好了",而不是"绕过了"。
口诀放进脑子:SELinux 拦了别关它,ls -Z 看标签,semanage 配对。
修复 6:SELinux 排查纪律
# === 这次事故暴露的认知盲区,定几条纪律 ===
# === 1. ★ Linux 有两套权限:DAC(rwx)和 MAC(SELinux),要都放行 ===
# === 2. ★ rwx 全对甚至 777 还报 Permission denied,第一个怀疑 SELinux ===
$ getenforce
# === 3. ★ setenforce 0 临时验证是不是 SELinux,验证完立刻 setenforce 1 ===
# === 4. SELinux 看的是"进程类型"和"文件类型"配不配,和 rwx 属主完全无关 ===
# === 5. ★ ls -Z 看文件 context,ps -Z 看进程 context,问题多在第三段 type ===
# === 6. ★ 拦截记录在 audit.log 的 AVC,用 ausearch -m avc 看,sealert 翻译成人话 ===
$ ausearch -m avc -ts recent
# === 7. ★ 改文件标签用 semanage fcontext + restorecon,别只用 chcon(会被改回) ===
# === 8. 能用布尔值解决的用 setsebool -P,非标准端口用 semanage port 登记 ===
$ getsebool -a | grep 服务名
# === 9. ★ 别动不动 disabled 关掉 SELinux,那是拆掉一整道安全防线 ===
# === 10. 排查"权限对却访问不了"的步骤链 ===
$ getenforce # ① SELinux 在 Enforcing 吗
$ setenforce 0 试一下 再切回 # ② 临时验证是不是它
$ ausearch -m avc -ts recent # ③ 看它拦了什么
$ ls -Z 文件 ; ps -Z 进程 # ④ 对比类型标签
$ semanage fcontext + restorecon # ⑤ 配对标签,根治
# 按这个顺序,"权限对却被拒"基本能定位、能根治。
命令速查
需求 命令
=============================================================
看 SELinux 当前状态 getenforce / sestatus
临时切宽容模式(验证用) setenforce 0
切回强制模式 setenforce 1
看文件的 SELinux 上下文 ls -Z 文件
看进程的 SELinux 上下文 ps -eZ | grep 进程名
看 SELinux 拦截记录 ausearch -m avc -ts recent
拦截记录翻译成人话+建议 sealert -a /var/log/audit/audit.log
解释为什么被拦 ausearch -m avc -ts recent | audit2why
临时改文件类型(会被改回) chcon -t 类型 -R 路径
永久加路径标签规则 semanage fcontext -a -t 类型 "路径(/.*)?"
按规则重新打标签 restorecon -Rv 路径
看某服务的布尔开关 getsebool -a | grep 服务名
永久设置布尔开关 setsebool -P 开关名 on
登记非标准端口 semanage port -a -t 类型 -p tcp 端口
口诀:rwx 全对甚至 777 还 Permission denied,十有八九是 SELinux 这第二套权限
ls -Z 看类型标签对不对,semanage fcontext + restorecon 配对,别拆门关 SELinux
避坑清单
- Linux 上有两套权限叠在一起,DAC 就是 rwx 属主,MAC 就是 SELinux,一次访问必须两套都放行
- 文件权限明明对甚至 chmod 777 了还报 Permission denied,第一个该怀疑的就是 SELinux
- getenforce 看状态,setenforce 0 临时切宽容验证是不是 SELinux,验证完一定立刻 setenforce 1 切回
- SELinux 不看属主和 rwx,它看进程的类型和文件的类型配不配,逻辑和 rwx 完全是两回事
- ls -Z 看文件上下文,ps -Z 看进程上下文,上下文四段里日常问题几乎都出在第三段类型 type
- 新建在非标准路径的文件常是 default_t 类型,服务无权读它,这是换目录就 403 的根本原因
- SELinux 每次拦截都写进 audit.log 的 AVC 记录,用 ausearch -m avc 看,sealert 能翻译成人话给建议
- 改文件标签要用 semanage fcontext 加规则再 restorecon 落实,只用 chcon 是临时的会被改回去
- 能用 setsebool 布尔开关解决的优先用开关,服务监听非标准端口要 semanage port 登记进端口类型
- 别因为 SELinux 拦了就 disabled 关掉它,那是拆掉一整道安全防线,正确做法是花十分钟学会配它
总结
这次"把文件权限开到 777 却依然访问不了"的事故,纠正了我一个深植于心、却从来没被我拿出来检验过的预设:我一直以为,"权限",是一个【单一】的东西。在我的世界里,一个文件能不能被访问,就是由那一串 rwx 说了算的,就这一套规则,清清楚楚,没有别的。这套观念太完整、太自洽了,完整到我从未觉得它"只是一种"——我以为它就是"权限"的全部。所以当我把 rwx 推到 777——这一套规则所能表达的、最彻底的"全部放行"——之后,我心里是有底气的:在"权限"这件事上,我已经把话说到头了,我已经对全世界敞开了大门。正因为如此,当 Nginx 依然回我一句 Permission denied 时,我才会陷入那种近乎荒诞的困惑:门我已经拆到不能再拆了,你说的到底是哪一道门被锁着?——我甚至没有能力想象,这个问题的答案,会是"另一道门"。因为在我的认知地图上,根本就只画了一道门。复盘到根上,我才明白,Linux 上的"权限",从来就不是一套规则,而是【两套】,它们叠在一起,各自独立地、沉默地把守着。第一套,就是我熟知的 rwx,它在安全领域有个名字叫 DAC,自主访问控制——它的提问方式是:"你这个用户,对这个文件,够不够格?"我把它回答成了"完全够格"。可还有第二套,叫 SELinux,叫 MAC,强制访问控制——它存在于我视野的彻底盲区里,而它的提问方式,和第一套截然不同。它根本不在乎"你是谁、够不够格",它问的是另一个维度的问题:"你这个【种类】的程序,按照系统定下的规矩,到底准不准去碰这个【种类】的资源?"我的 Nginx,是 httpd_t 这一"工种"的进程;而我新建的那个文件,因为待在一个 SELinux 从没听说过的目录里,被打上了 default_t——"无名之物"——这个标签。规矩里写得明明白白:httpd_t 这种工种,不许碰 default_t 这种东西。这一关,和 rwx 是 777 还是 000,没有一丝一毫的关系。我把第一道门拆得粉碎,而第二道门,我连它的存在都不知道,自然也从没想过要去开它。一次访问要成功,是这两道门【串联】着的——必须两道都点头。我却把全部的力气,反反复复地,只砸在我认识的那一道门上。这次最大的收获,是我意识到,最危险的盲区,不是那些我"知道自己不懂"的东西——那些我至少还会心存戒备、还会去查。最危险的,是那些我【以为自己已经完全掌握】的东西。"权限"对我来说,就是这样一个领域:我对它太熟悉、太自信了,自信到我认定 rwx 就是这件事的全部边界,自信到我把"权限"这个词,和"rwx"这三个字母,在脑子里划上了一个绝对的等号。而恰恰是这个自以为是的等号,亲手为我焊死了那扇通往 SELinux 的门——一个你认定"我已经全懂了"的领域,会让你彻底丧失在这个领域里继续追问的动力。你不会去查一个你坚信不存在的东西。所以下一次,当一个我"非常熟悉"的领域里,冒出一个用我全部的知识都解释不通的矛盾时,我不会再把它归结为"一定是我哪个细节弄错了、再仔细查一遍 rwx 就好"。我会逼着自己,对那个让我无比自信的领域,问一个谦卑的问题:我所掌握的这一套,会不会其实只是这件事情的【其中一套】?在我视野之外,会不会还平行地站着另一套规则,它一直在那里,一直在生效,只因为我从没怀疑过它的存在,我就从来没有看见过它?——很多解不开的矛盾,不是因为我们把已知的东西弄错了,而是因为在我们坚信"已经看到了全部"的地方,其实还藏着另一半我们从未抬眼去看的世界。
—— 别看了 · 2026