NoNewPrivileges=true是Linux内核在execve()阶段强制锁死进程权限边界的机制,它禁用setuid/setgid、文件capability等所有提权路径,防止高权限进程越权访问资源。

NoNewPrivileges=true 是什么,为什么必须开
NoNewPrivileges=true 不是“禁止提权”的开关,而是让进程从 execve() 系统调用那一刻起就锁死权限边界:它会阻止后续任何通过 setuid/setgid 二进制、文件 capability、SUID 脚本或 fork+exec 继承高权限子进程的方式获得新权限。哪怕你启动时用了 root,只要开了这行,execve("/usr/bin/ping", ...) 就永远拿不到 CAP_NET_RAW —— 因为内核直接在 exec 阶段丢弃所有 capability 并忽略文件属性中的权限位。
常见错误是只配 User= 却漏掉它:比如服务以 root 启动后 drop 到普通用户,但中间若执行了带 cap_net_admin+ep 的二进制,仍可能提权;又或者用 system("sudo ...") 类调用,也会因未设此选项而成功。
怎么在 service 文件里正确启用
编辑你的单元文件(如 /etc/systemd/system/myapp.service),在 [Service] 段落中加入:
[Service] User=myapp Group=myapp NoNewPrivileges=true
注意三点:
- 必须和
User=/Group=配合使用,否则单独开没意义(root 进程本身就不该有新权限可提) - 不能写成
NoNewPrivileges=yes—— systemd 只认true或false,写错会导致解析失败、服务无法启动 - 它对 ExecStartPre/ExecStartPost 中的命令同样生效,所以这些脚本里也不能依赖 setuid 工具
开了之后哪些行为会被拦截
典型被拒的操作包括:
- 子进程尝试
setuid(0)或setgid(0)—— 直接返回 -1,errno=EPERM - 执行带
cap_sys_admin+ep的二进制(如mount)—— 报错Operation not permitted - 调用
prctl(PR_SET_SECUREBITS, SECBIT_NO_SETUID_FIXUP)类加固逻辑 —— 内核已提前封死路径 - 用
LD_PRELOAD注入特权库(配合 SUID 程序)—— 即使程序本身是 SUID,加载器也会跳过预加载
验证是否生效:启动服务后运行 cat /proc/$(pgrep myapp)/status | grep NoNewPrivs,输出应为 NoNewPrivs: 1。
容易被忽略的兼容性坑
某些旧版工具链或自研二进制在 fork/exec 时隐式依赖权限继承,开启后可能静默失败:
- Golang 编译的二进制若含
//go:linkname调用 libc setuid,会卡在 syscall 返回 EPERM - Java 应用用
Runtime.exec()调用su或sudo—— 不再弹密码提示,直接抛 IOException - Python 的
subprocess.Popen(..., shell=True)若 PATH 中混入了 SUID 脚本,exec 阶段就被内核拦住 - 务必检查
journalctl -u myapp -n 50是否出现Operation not permitted类报错,而不是只看服务是否“跑起来了”
真正关键的不是“能不能开”,而是开完之后整个调用链是否还完整 —— 很多故障藏在子进程失败却不报错的日志角落里。











