很多人第一次被这套身份体系绊倒都是在写一个需要读取受保护配置的小工具时程序明明用chmod 4755加上了 SUID 位ls -l里也出现了那个显眼的s但程序里打印出来的geteuid()依旧是自己配置文件照样打不开。反过来也有更吓人的情况——程序莫名其妙能删掉别人建的文件一查才发现是 SGID 目录的组继承在起作用。UID、GID、EUID、EGID、SUID、SGID 这六个名字看着像同一家族里的一串缩写实际上分属两个完全不同的层面进程的身份槽位和文件 inode 上的权限位。分不清这两层排查方向就会一路跑偏。这篇内容把两层的语义、转换规则、内核判定顺序、以及实际写代码时哪些坑必踩一次讲透。适合已经会用ls -l、chmod但被“为什么我提权了又掉回来了”“为什么这个目录下新建文件属组不对”这类问题卡住的同学也适合想把权限模型彻底补齐的运维和开发。1. 六个名字先摆到台面上认清1.1 为什么要把身份拆成好几个Unix 的权限模型本质上只有三组九位属主、属组、其他人各三位读写执行。这个模型极其简单简单到很多人以为权限就是“我是谁能不能动这个东西”。但只要系统里出现“普通用户需要完成一件只有特权身份才能完成的事”这个二分模型立刻就不够用了。最典型的例子是改密码。密码哈希存在一个普通用户连读都不许读的文件里可用户又必须能改自己的密码。如果直接把那个文件开成所有人可写整套认证体系当场报废如果让密码程序一直以 root 跑那用户输入的每一个字节都处在最高权限环境里任何一处越界写入都是灾难。系统的解法是让这个程序在执行的那一瞬间临时把“当前生效的身份”换成文件属主的身份程序跑完退出身份跟着进程一起消失对调用者本人没有任何残留影响。要做到“临时借、用完还”就必须把“你是谁”和“你现在以谁的身份在干活”拆成两个变量分别存。前者是记账用的后者是判定用的。再进一步为了支持“借了之后还能还回来”还得有第三个变量存“借之前是什么身份”。三个槽位一摆出来UID、EUID、SUID进程意义上的就都出现了GID 那一组是完全对称的镜像。理解了这个动机后面所有参数和调用规则都是它的自然推论而不是需要死记硬背的清单。1.2 一张表把术语撞车问题解决掉这套术语最坑的地方不是概念难而是同一个缩写在不同语境下指两样东西。SUID 既可以是进程里的 saved set-user-ID也可以是文件 inode 上的 set-user-ID 权限位。看文档时如果不注意上下文很容易把“设置 saved ID”和“设置文件权限位”搞混。先把这层窗户纸捅破缩写进程语境文件语境UIDreal user ID启动进程的那个用户inode 里的属主 uidEUIDeffective user ID当前权限判定的依据无此概念SUIDsaved set-user-ID切换前的 EUID 备份set-user-ID 权限位八进制 4000GIDreal group IDinode 里的属组 gidEGIDeffective group ID无此概念SGIDsaved set-group-ID切换前的 EGID 备份set-group-ID 权限位八进制 2000对目录另有语义还有一个容易漏掉的角色Linux 特有的fsuidfilesystem UID。内核在做文件访问检查时用的其实是 fsuid 而不是 euid。平时seteuid()会同步把 fsuid 改掉所以绝大多数情况下两者一致你感觉不到它的存在但一旦代码里出现setfsuid()就会出现“euid 是 root、fsuid 不是”这种诡异状态排查起来非常费劲。/proc/PID/status里的Uid:一行其实是四个数最后一个就是 fsuidgrep -E ^(Uid|Gid): /proc/self/status # Uid: 1000 1000 1000 1000 # Gid: 1000 1000 1000 1000四个数依次是 real、effective、saved、fs。平时四个值一样看着像废话等哪天它们不一致了这一行就是最快的现场证据。1.3 三条随手可用的查看命令排查权限问题第一件事永远是“把当前状态打出来”而不是靠猜。日常最顺手的三个id # 当前 shell 的 uid/gid 和附加组 id -u; id -g; id -G # 分开取脚本里常用 ps -o pid,user,euser,group,egroup,comm -p $$ps的euser/egroup列非常有用它直接显示进程的 effective 身份。当你不确定某个守护进程到底以谁的身份在跑这一列比看配置文件可靠得多。另外一个常被忽略的点ls -l显示的是文件的属主属组跟进程身份毫无关系。很多新手对着ls -l的输出研究“为什么我的程序权限不对”方向从一开始就错了。2. UID 与 GID进程和文件两侧的身份证2.1 进程出生时就带上的那套身份一个进程被fork出来时内核会把父进程的 real/effective/saved 三组身份原样复制过去。execve执行新程序时这组身份通常保持不变——这是默认行为也是为什么 SUID 位必须由内核在execve内部特殊处理才能生效。换句话说身份是进程的属性不是程序的属性。程序文件只是躺在磁盘上的一个静态对象它不“拥有”任何身份。用户的 uid 存在/etc/passwd第三个字段gid 在第四个字段用户属于哪些附加组写在/etc/group里。这里有个细节值得强调进程的附加组列表supplementary groups在权限判定中同样参与匹配。一个用户主组是staff同时被加进docker、devteam那么他对属组为devteam且组可读的文件就有读权限。id -G输出的就是这个完整列表而id -g只给主组。看到“组权限明明给了却不生效”八成是附加组没刷新——用户被usermod -aG加进新组后已经登录的会话不会自动感知必须重新登录或者newgrp一次。2.2 文件侧记的只有两个数字inode 里跟权限相关的核心信息就三样属主 uid、属组 gid、十二个权限位九个常规位加三个特殊位。用stat能看到全部stat -c %n uid%u gid%g mode%a (%A) /usr/bin/passwd # /usr/bin/passwd uid0 gid0 mode4755 (-rwsr-xr-x)注意mode4755这个四位八进制最高位 4 就是 SUID。三位数写法755和四位数写法4755的区别就在于是否显式带上了特殊位。chmod s这种写法看着方便实际上同时给 SUID 和 SGID 都置了位对普通文件很容易在不需要的地方留下一个 SUID。我个人的习惯是永远写四位数字宁可多打一个 0。2.3 权限判定的真实次序内核判定“这个进程能不能读这个文件”走的是一条固定顺序且一旦命中就停止进程的 EUID 是 0也就是 root 或具备相应能力→ 直接放行不看任何权限位。唯一例外是执行权限root 要执行一个文件文件至少得有一个 x 位否则还是EACCES。EUID 等于文件的属主 uid → 只看属主那三位组位和其他位一律忽略。进程的 EGID 或任一附加组等于文件属组 gid → 只看组那三位其他位忽略。以上都不匹配 → 只看其他位。第 2、3 步的“只看”两个字是重点。曾经有个经典困惑某文件权限rw-r-----属主是 A属组是 G进程以 B 身份运行但 B 在 G 组里结果写不进去——因为组位只有 r没有 w。这不算 bug是设计如此。很多人以为“属主和组权限会叠加”实际上不会。3. EUID 与 EGID真正决定能不能动的那个3.1 一个可以五分钟做完的实验抽象讲半天不如亲手看一眼。准备一个最小程序把几个身份全打出来/* ident.c —— 打印进程的六个身份槽位 */ #define _GNU_SOURCE #include stdio.h #include unistd.h #include sys/types.h int main(void) { uid_t r, e, s; gid_t gr, ge, gs; if (getresuid(r, e, s) ! 0) { perror(getresuid); return 1; } if (getresgid(gr, ge, gs) ! 0) { perror(getresgid); return 1; } printf(uid real%u effective%u saved%u\n, (unsigned)r, (unsigned)e, (unsigned)s); printf(gid real%u effective%u saved%u\n, (unsigned)gr, (unsigned)ge, (unsigned)gs); return 0; }编译完先原样跑一次四个数字全是自己的 uid。然后加上 SUID 位再跑gcc -Wall -O2 -o ident ident.c sudo chown root:root ident sudo chmod 4755 ident ./ident # uid real1000 effective0 saved0 # gid real1000 effective1000 saved1000real还是 1000因为启动它的确实是这个普通用户effective变成 0因为文件属主是 rootSUID 位生效了。这一行输出就是整套机制的全部真相记账身份没变判定身份被换了。此时这个进程去读只有 root 能读的文件内核看的是 effective0直接放行但要去查“这个操作是谁发起的”看的是 real1000。顺带说一句 GID上面的例子里 gid 三兄弟一个都没变因为只设了 SUID。想同时看到 EGID 变化把chmod换成6755SUIDSGIDsaved和effective的 gid 就会跟着变成文件的属组。3.2 三个槽位之间怎么来回切这一块是全篇最容易出错的地方因为 Linux 的setuid()家族在不同权限下有完全不同的行为而且有些操作是不可逆的。核心规则如下调用进程当前有 CAP_SETUID典型是 root进程无特权setuid(x)同时设置 real、effective、saved 三个槽位为 x不可逆只设 effective且 x 必须等于 real 或 saved否则 EPERMseteuid(x)只设 effective 为 x同上只设 effectivesetreuid(r, e)设 real 和 effective传 -1 表示不动可把 real 与 effective 互换受限制setresuid(r, e, s)三个槽位分别设传 -1 表示不动只能设为 real、effective、saved 三者之一表格里“不可逆”三个字必须画重点。很多人在 root 程序里想“先降权做点事做完再提回来”写成了setuid(getuid()); /* 想临时降权 */ /* ... 做点普通用户该做的事 ... */ setuid(0); /* 想提回来 —— 这里会失败 */第二次调用会返回 -1 并置EPERM因为三个槽位已经全部变成普通用户了saved 槽位也没了进程从内核视角看就是一个彻头彻尾的普通进程再没有任何“回去”的凭据。正确写法是保留 saveduid_t saved geteuid(); /* 先把特权身份记下来 */ if (seteuid(getuid()) ! 0) { perror(drop); return 1; } /* ... 以调用者身份干活 ... */ if (seteuid(saved) ! 0) { perror(restore); return 1; }seteuid只改 effectivesaved 槽位原封不动所以随时能切回去。这个模式在需要短暂使用特权的程序里是标准做法我个人的原则是特权窗口越短越好能不用就不用。3.3 saved 槽位存在的唯一理由单独看saved set-user-ID 这个槽位有点多余——为什么不直接记在用户态变量里因为用户态变量会被程序自己改也可能被缓冲区溢出之类的漏洞覆盖内核对它毫无信任可言。把“回程票”放在内核维护的进程结构里用户态代码只能通过受限的系统调用去影响它安全性完全不在一个量级。更实际的原因是如果只靠 real 和 effective 两个槽位一个 SUID 程序在降权之后就没有合法途径回到特权状态了除非它重新 exec 自己一次。而重新 exec 意味着重新走一遍加载、初始化、参数解析成本高且容易出岔子。saved 槽位的设计就是用一小块内核状态换来“可逆降权”这个能力。理解了这一点再看setresuid(uid, uid, -1)这种写法改 real 和 effective保留 saved就顺理成章了。4. SUID 与 SGID文件上的那三个特殊位4.1 SUID 位生效的硬性条件SUID 位不是万能开关它只在execve这个时间点上起作用而且必须同时满足一串条件缺一个就静默失效。这是排查时最浪费时间的地方因为没有报错只有“没生效”。文件必须是可执行的普通文件且当前进程有执行权限。文件所在文件系统必须以允许 suid的方式挂载。带nosuid选项挂载的分区上所有 SUID 位一律被忽略常见于/tmp、/home的独立分区以及各种内存文件系统。文件必须是二进制可执行文件。以#!开头的脚本Linux 内核会明确忽略 SUID 位这一点后面单独讲。进程不能处于no_new_privs状态。容器环境里这个标志很常见它会直接让所有提权机制失效。文件所在的文件系统如果是用user选项挂载的可移动介质同样受限制。验证挂载选项的命令findmnt -T /path/to/your/binary -o TARGET,FSTYPE,OPTIONS输出里的OPTIONS如果含nosuid那不管你怎么chmod 4755都不会有用。4.2 SGID 位在文件和目录上是两套语义SUID 只有一种含义执行时把 EUID 换成文件属主。SGID 就分裂了在普通文件上执行时把 EGID 换成文件属组。用途比 SUID 窄得多典型场景是需要访问某个组专属资源的工具程序。在目录上完全不涉及提权。它表示“在这个目录里新建的文件和子目录属组自动继承目录的属组而不是创建者的主组”。而且新建的子目录会自动带上 SGID 位让继承链一路延续下去。目录 SGID 这个特性是多人协作目录的基石。没有它一个团队共享目录会变成灾难A 建的文件属组是 A 的主组B 建的是 B 的主组最后目录里躺着一堆不同属组的文件谁也说不清谁能改。有了它所有新文件统一归属团队组配合chmod 2775就能做出一个干净的协作区。4.3 协作目录的完整实操假设要给一个开发小组建一个共享区组名叫devteam虚构名称实际按自己环境替换sudo groupadd devteam sudo usermod -aG devteam alice sudo usermod -aG devteam bob sudo mkdir -p /srv/shared/proj sudo chown root:devteam /srv/shared/proj sudo chmod 2775 /srv/shared/proj2775拆开看2 是 SGID后面 775 是属主和属组可读写执行、其他人可读可执行。设置完之后验证ls -ld /srv/shared/proj # drwxrwsr-x 2 root devteam 4096 ... /srv/shared/proj # ^ 属组位的 x 变成了 s那个小写的s就是 SGID 生效的标志。如果显示成大写的S说明属组位没有 x 权限此时 SGID 处于“置了位但无效”的状态是个典型的配置失误。接着以组成员身份新建文件观察属性mkdir -p /srv/shared/proj/sub touch /srv/shared/proj/a.txt ls -l /srv/shared/proj # -rw-r--r-- 1 alice devteam ... a.txt # drwxrwsr-x 2 alice devteam ... suba.txt的属组是devteam而不是alice的主组sub目录也自动带上了 SGID。这就是继承链在起作用。这里必须提醒一个高频踩坑点SGID 只管属组不管权限位。上面a.txt的权限是644也就是组只能读不能写。原因是创建进程的 umask 是022touch请求的666被掩掉写位之后剩下644。协作目录里大家要互相改文件必须让 umask 变成002在/etc/profile或组级配置里设或者用默认 ACL 兜底sudo setfacl -d -m g:devteam:rwx /srv/shared/proj默认 ACL 的好处是它对新文件和对新目录同时生效而且不受 umask 干扰比依赖每个人自己改 umask 靠谱得多。我现在的习惯是协作目录一律 SGID 默认 ACL 双保险umask 靠个人自觉这事儿基本不可靠。5. 动手写一个受控的特权程序5.1 需求与设计的取舍模拟一个很常见的需求某服务需要从/var/lib/mockapp/secret.conf读一行配置这个文件权限600、属主 root普通用户读不了。完整的解法有两条路一是给服务配一个系统服务单元以 root 身份启动二是做一个带 SUID 位的小工具只做“读这一行”这一件事。前者权限过大后者权限可控但需要小心编写。这里选第二条但要严格遵守“最小特权窗口”原则特权只用于open那一下文件描述符拿到之后立刻降权后续所有输出、格式化、写日志都在调用者身份下完成。这样做的好处是即使后续代码有漏洞攻击面也被限制在一个已打开的描述符上而不是整个进程。5.2 完整代码与逐段说明/* reader.c —— 只读取固定路径配置的一行内容 */ #define _GNU_SOURCE #include stdio.h #include stdlib.h #include unistd.h #include fcntl.h #include string.h #include errno.h #include sys/types.h #include sys/prctl.h #define CONF_PATH /var/lib/mockapp/secret.conf int main(void) { uid_t ruid, euid, suid; char buf[512]; ssize_t n; int fd; /* 1. 开跑第一件事禁止本进程再获得新特权 */ if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) ! 0) { perror(prctl); return 1; } if (getresuid(ruid, euid, suid) ! 0) { perror(getresuid); return 1; } /* 2. 路径写死绝不拼接 argv 或环境变量 */ fd open(CONF_PATH, O_RDONLY | O_NOFOLLOW | O_CLOEXEC); if (fd 0) { fprintf(stderr, open %s: %s\n, CONF_PATH, strerror(errno)); return 1; } /* 3. 拿到 fd 立刻丢掉特权后面的活全用调用者身份干 */ if (seteuid(ruid) ! 0) { perror(seteuid); close(fd); return 1; } n read(fd, buf, sizeof(buf) - 1); close(fd); if (n 0) { perror(read); return 1; } buf[n] \0; /* 4. 极简输出不调用 shell不做格式化拼接 */ fputs(buf, stdout); return 0; }几个决定值得逐个说清楚。PR_SET_NO_NEW_PRIVS放在最前面。它的作用是让这个进程及其所有后代永远无法再通过execve获得新特权。即使后面代码被注入也没法exec一个 SUID shell 把权限放大。代价是这个标志不可撤销所以必须放在“我已经确定不需要再 exec 任何提权程序”之后。对这个程序来说完全没问题。O_NOFOLLOW防的是符号链接攻击。如果攻击者能控制路径上的某一环比如/var/lib/mockapp目录可写他可以把secret.conf换成一个指向别处的软链让特权进程去打开任意文件。加了O_NOFOLLOW最后一段是软链就直接失败。更彻底的做法是用openat2配合RESOLVE_NO_SYMLINKS和目录 fd 锚定但对这个场景O_NOFOLLOW已经够用。O_CLOEXEC防止描述符泄漏到子进程。虽然这个程序不 fork但养成习惯比事后补要好。降权时机是整段代码的灵魂。open返回的 fd 已经完成了权限检查之后对这个 fd 的read不再受身份影响。所以完全可以在降权之后再读——这一步把“持有特权的时长”压缩到了两次系统调用之间。如果反过来写成“先读完再降权”特权窗口就覆盖了整个read虽然功能一样但风险面大了一截。5.3 部署、赋权与验证# 准备受保护的文件属主 root仅属主可读 sudo mkdir -p /var/lib/mockapp echo tokenmock-value-123 | sudo tee /var/lib/mockapp/secret.conf /dev/null sudo chown root:root /var/lib/mockapp/secret.conf sudo chmod 600 /var/lib/mockapp/secret.conf # 编译并以 root 身份安装 gcc -Wall -Wextra -O2 -o reader reader.c sudo install -o root -g root -m 4755 reader /usr/local/bin/mock-readerinstall命令比cp再chmod更适合这个场景它能在一次操作里同时指定属主、属组和权限模式顺手把 SUID 位设好。用cp的话普通cp会按 umask 建新文件SUID 位直接丢掉cp -p虽然保留权限但保留的是源文件的可能是 755还得再chmod一次多一步就多一次出错机会。验证分三层# 第一层文件属性对不对 ls -l /usr/local/bin/mock-reader # -rwsr-xr-x 1 root root ... /usr/local/bin/mock-reader # 第二层普通用户直接读原文件应该失败 cat /var/lib/mockapp/secret.conf # cat: /var/lib/mockapp/secret.conf: Permission denied # 第三层通过工具读应该成功 mock-reader # tokenmock-value-123如果第三层失败先别改代码回到第 6 章的排查表走一遍。九成的情况是挂载选项或者权限位被后续操作覆盖了。5.4 用 strace 看一眼内核到底做了什么理论讲再多不如直接看系统调用序列。用strace过滤出关键调用strace -f -e traceexecve,openat,setresuid,setuid,write ./mock-reader 21 | head -20你会看到execve之后内核把进程的 effective uid 改成了 0然后是我们的openat接着一次setresuid把 effective 降回普通用户最后才是write。这一串调用的顺序就是“特权窗口”的可视化。把这个序列截图存下来以后向别人解释 SUID 到底做了什么比讲十分钟原理管用。6. 常见问题与排查实录6.1 SUID 不生效的速查表这是我这几年被问得最多的一类问题几乎全部落在这张表里现象大概率原因验证命令处理geteuid()还是自己权限位没设上ls -l看是否有ssudo chmod 4755 file同上分区以nosuid挂载findmnt -T path -o OPTIONS换到/usr/local/bin同上文件在内存盘或可移动介质上findmnt -T path -o FSTYPE挪到常规磁盘分区同上进程带了no_new_privsgrep NoNewPrivs /proc/PID/status检查容器或服务单元配置脚本怎么设都没反应内核忽略#!脚本的 SUID换二进制程序验证写 C 程序或用其他提权路径文件被覆盖后位丢了编辑器/编译输出重建了 inodels -l时间戳每次部署后重新赋权备份还原后失效归档工具未保留特殊位tar -tvf看权限列用tar -p或install大写的S有s位但没有x位ls -lchmod ux补上执行位6.2 脚本为什么不能带 SUID这条值得单独展开因为网上流传的错误说法太多了。#!/bin/sh这样的脚本内核在binfmt_script处理阶段会主动忽略SUID 和 SGID 位。原因很实际脚本真正的解释器路径是由脚本第一行指定的如果 SUID 生效攻击者可以构造一个软链指向任意解释器或者利用解释器自身的参数注入来获得特权执行内核根本没法安全地保证“执行的是你看到的那个脚本”。相对的绕过方式是给解释器本身设 SUID那是另一个层面的问题而且现代发行版几乎不会再这么干。所以看到“给 shell 脚本加 SUID 就能提权”的教程直接跳过就好那是基于某些老旧系统的误传。真正需要这个能力时写一个几十行的 C 程序用execvp去调脚本把 SUID 设在 C 程序上——即便如此也要极其小心参数传递因为execvp后面的脚本依然以调用者身份运行你只是获得了“以特权身份启动一个解释器”的能力这本身就危险。6.3 排查的几个手法与教训第一个教训先用id和/proc/self/status确认真实状态再动代码。我见过太多次花两小时改代码、最后发现是文件在/tmp上而/tmp是nosuid。状态确认这一步只花十秒钟却能砍掉大半的无效排查。第二个手法临时把getresuid和getresgid的返回值打进日志包括每一次seteuid前后的值。身份切换出问题时日志里能看到确切的跳变点比在调试器里单步快得多。生产环境不方便打日志的话/proc/PID/status的Uid:行随时可读用外部进程观察也行。第三个是我自己踩过的坑用chmod gs给一个目录设 SGID设完发现新建文件属组还是不对。原因是那个目录的属组根本不是目标组chmod gs只置位不改属组所以继承的是“目录当前的属组”——一个毫无意义的组。正确顺序永远是先chown :group再chmod。顺序反了不会报错只会让你对着正确的结果怀疑人生。7. 安全边界这几个位是双刃剑7.1 定期审计是必须做的事SUID 程序是整台机器上权限最高的东西之一任何一处代码缺陷都可能被放大成完整的提权路径。审计的第一步是把家底摸清楚# 列出所有带 SUID 或 SGID 的文件 find / -xdev \( -perm -4000 -o -perm -2000 \) -type f -printf %M %u:%g %p\n 2/dev/null # 只看 SUID find / -xdev -perm -4000 -type f 2/dev/null-xdev别漏否则会顺着挂载点爬进网络盘或者容器镜像跑上半小时还没结束。-perm -4000里的减号表示“这些位必须全部置上”跟-perm /4000任意一位置上是两回事写错了结果会差很多。把输出存成基线隔一段时间 diff 一次新增的 SUID 文件就是重点审查对象。很多入侵排查就是从这一条命令开始的。7.2 写特权程序的几条红线绝不调用 shell。system()、popen()、execl(/bin/sh, ...)一律不要出现在特权路径上。环境变量污染、路径劫持、参数注入任何一个都能让你前面所有的校验白做。绝不信任环境变量。PATH、IFS、LD_PRELOAD、LD_LIBRARY_PATH、TMPDIR全部不可信。要么在程序入口处把它们清空要么干脆只依赖绝对路径。绝不信任参数。命令行参数是用户完全可控的输入。路径类参数要做白名单校验数字类参数要做范围校验文件名里带..和 nul 字节的要拒绝。打开文件一律带O_NOFOLLOW和O_CLOEXEC。前者防软链后者防描述符泄漏。临时文件不要用固定路径。用mkstemp在不可预测的目录下创建并立刻unlink避免符号链接竞争。特权窗口尽量短。拿到需要的资源后立刻seteuid降权不要“反正还有几步就结束了一起降”。退出前不需要手动恢复特权。进程结束身份跟着没了别画蛇添足地seteuid(0)。7.3 一张加固对照表风险点具体表现加固手段路径注入用argv[1]拼成路径去open路径写死或用白名单校验软链跟随攻击者把目标换成软链O_NOFOLLOW必要时openat2环境变量污染依赖PATH找子程序只用绝对路径入口处clearenv描述符泄漏特权 fd 被子进程继承O_CLOEXEC或用close_range特权窗口过长整个main都在 root 下跑资源到手立刻seteuid降权权限放大程序能exec出 SUID shellPR_SET_NO_NEW_PRIVS竞态条件检查和使用之间文件被替换用 fd 操作代替路径操作先open再fstat写这类程序的心态要摆正不是在“实现功能”而是在“设计一个对外暴露的最小攻击面”。功能写错顶多是程序不能用权限写错可能是整台机器被人拿走。8. 我个人的几条经验最后说几条纯经验层面的东西都是文档里不会写、但实际排障时最省时间的。一个是永远用四位八进制写 chmod。chmod 2755和chmod 755差的不只是样式前者明确表达了“我要 SGID”后者是“我不关心特殊位”。半年后回来 review 的人可能就是你自己一眼就能看出意图不用去stat反查。另一个是SGID 目录和 umask 必须配套考虑。只设 SGID 不设 umask 或默认 ACL协作目录里成员之间照样互相改不了文件看起来像 SGID 没生效实际上是权限位不够。这两个东西一个是“属组归属”一个是“属组权限”缺一个都跑不通但报错时不会告诉你缺的是哪个。还有一个提醒升级或重装任何带 SUID 位的程序之后重新核一遍权限位。包管理器通常会把位设对但手工覆盖安装、从源码编译后make install、或者从别处拷过来的二进制经常悄悄丢掉这个位。上面那条find命令加上比较十分钟能覆盖全机器性价比极高。至于 EUID 和 saved 槽位那部分真正记住的方法只有一个自己动手写一次“临时降权再回来”的代码故意用setuid写错一次看到EPERM再用seteuid改对。那个报错比任何文章都让人印象深刻。
企业数字化 ERP 产品动态
相关推荐
零基础学Linux:从CentOS安装到Shell脚本与集群管理路线 简介:这份千页规模的 Linux 架构师文档面向希望从零基础走向企业级运维与架构岗位的读者,按基础、进阶、高级三段编排共 23 章,可用于系统梳理知识体系,也可作为日常查阅手册。压缩包内为 1 个 PDF 文件,约 28.82MB&am… · 2026/11/3 12:05:12
激活函数全解析:从梯度消失到端侧量化部署选型 调参调到凌晨,loss 卡在一个数上不动,最后发现问题出在一个默认激活函数上——这种事我遇到过不止一次。激活函数看起来是网络里最不起眼的一行代码,新手往往觉得框架里默认写个 ReLU 就完事了,但真正做过模型调优和端侧部署的人都… · 2026/11/3 12:05:12
腾讯北极星网关架构与微服务治理实践 1. 北极星腾讯平台网关实现方案解析 在分布式系统架构中,服务网关作为流量入口和统一管控节点,承担着至关重要的角色。腾讯北极星(Polaris)作为一款开源的微服务治理平台,其网关实现方案融合了腾讯内部多年大规模服务治… · 2026/11/3 12:05:12
SQL Server即时通讯数据查询实战:多表关联、索引优化与窗口函数 1. 从一张“QQ查询”需求单说起:这个项目到底在做什么很多人第一次看到“数据库(SQL Server)QQ查询”这个标题,第一反应是“查QQ号?”,其实真正落到开发场景里,它指的是一套围绕即时通讯类业务数… · 2026/11/3 12:53:33
R语言试题自测:从基础漏洞到统计检验的实战验证 简介:面向R语言初学者与备考复习人群的试题练习文档,围绕基础统计概念与R语言入门操作展开,可用于课堂测验、自学自测与考前查漏补缺。内容覆盖描述性统计与推断性统计的区分、名义数据与计量数据等数据类型辨析,以及getwd()、hea… · 2026/11/3 12:53:33
HTML网页开发入门:从基础到实战 1. HTML网页操作基础入门 HTML作为网页开发的基石语言,是每个想学习网页制作的新手必须掌握的第一课。记得我第一次接触HTML时,被那些尖括号和标签搞得晕头转向,但实际动手操作几次后才发现,它远比想象中简单有趣。下面我就从最基… · 2026/11/3 12:53:33
SkyWalking自定义告警指标与日志监控实战 1. SkyWalking自定义告警指标深度解析 在分布式系统监控领域,SkyWalking作为一款优秀的APM工具,其告警功能一直是运维人员关注的焦点。传统告警配置往往局限于预设指标,而实际生产环境中,我们经常需要根据业务特性定制专属监控规则… · 2026/11/3 12:53:33
SQL Server实战:用QQ场景练透数据库查询与优化 1. 从"QQ查询"这个标题说起:一个被低估的数据库实战切口 第一次看到"数据库(SQL Server)QQ查询"这个标题,很多人可能会愣一下——QQ和SQL Server有什么关系?其实这正是这个项目最有意思的地方。它… · 2026/11/3 12:53:33
协同管理软件核心解析:表单、流程、门户与空间分层 简介:这份资源是致远互联A8协同管理软件V9.0集团版的官方用户操作手册,面向集团型企业管理者、信息化实施人员及日常办公用户,帮助其快速熟悉平台功能、完成客户端配置并掌握协同事项处理流程。手册围绕协同“九宫格”管理理念展开࿰… · 2026/11/3 12:52:22
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 6:04:24
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 5:52:46
STM32F411RE与MAX14431实现SPI隔离:工业现场抗共模干扰实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
ESP32+大模型AI硬件开发:从Demo到产品的8个工程问题 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
基于MAX14431BAEE+与PIC18F85J10的SPI隔离方案设计与实践 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 6:04:24
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 5:52:46