linux内核2.4.22起禁止非root用户可执行文件通过chmod u+s启用setuid提权,因内核静默降权;需root所有、系统路径、禁用安全编译选项才可能生效,但风险极高。

为什么直接 chmod u+s 不起作用
Linux 内核从 2.4.22 起,默认禁止非 root 用户的可执行文件通过 chmod u+s 设置 SetUID 位来提权运行——哪怕你用 sudo chmod u+s ./a.out 成功设置了,运行时也不会切换到文件所有者身份,getuid() 和 geteuid() 依然相等。这不是权限没设上,而是内核策略拦截了:普通用户编译的二进制默认没有 capability,且内核对非 root 所有、非系统路径下的 SetUID 程序做了静默降权(即忽略 SUID 位)。
setcap cap_net_bind_service+ep 是更可控的选择
比起 SetUID,给程序授予特定 capability 更细粒度、更安全,也绕过了 SUID 的诸多限制。比如想让普通用户程序监听 1024 以下端口,只需:
sudo setcap cap_net_bind_service+ep ./myserver
注意:cap_net_bind_service 是唯一能替代 root 绑定低端口的能力;+ep 表示“effective + permitted”,确保运行时真正生效。常见错误包括:
-
setcap后忘记加./或绝对路径,导致提示 “Operation not supported”(实际是找不到文件) - 对符号链接执行
setcap—— 它只作用于目标文件,不继承链接 - 程序动态链接了未被信任的库(如 LD_LIBRARY_PATH 指向用户目录),内核会自动清空 capability(
prctl(PR_GET_SECUREBITS)可查)
必须用 root 所有才能用 SetUID 吗
可以,但代价高、风险大,且仅在严格受限场景下可行:
- 可执行文件必须由
root拥有(chown root:root ./a.out) - 必须放在
/usr/local/bin或/opt/等非/tmp、非用户家目录的路径下(否则内核拒绝提权) - 编译时需禁用 stack protector 和 PIE(否则可能触发内核的“unsafe exec”检查):
g++ -no-pie -fno-stack-protector -z noexecstack main.cpp -o a.out - 运行前仍要
chmod u+s ./a.out,且确认ls -l中显示-rwsr-xr-x(注意 s 在 owner 权限位)
即便满足以上,一旦程序有任意内存漏洞(如栈溢出、UAF),攻击者就能直接拿到 root shell —— 这是 capability 方案刻意规避的设计风险。
验证 capability 是否生效最可靠的方法
别只信 getcap ./a.out 输出,要实测运行时能力:
./a.out -c "cat /proc/self/status | grep CapEff"
输出类似 CapEff: 0000000000000400,对照 /usr/include/asm-generic/auxvec.h 中 AT_CAPABILITY_EFFECTIVE 的位掩码,或用 capsh --print 辅助解码。更直接的是在程序里调用:cap_get_proc() + cap_get_flag() 检查 CAP_NET_BIND_SERVICE 是否为 CAP_SET。容易忽略的一点:如果程序 fork 出子进程,默认会丢掉 capability,除非显式调用 cap_set_proc() 或设置 cap_inh 位。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











