一次 SELinux 排障实战:从 Permission denied 到彻底解决


一次看似简单的服务启动失败,背后隐藏着 SELinux 对软链接的"特殊关照"


一、事故现场:服务起不来了

上班第一天,测试同事发来消息:"服务起不来了,报 Permission denied。"

查看服务状态:

[root@S-756 test]# systemctl status FrameWorkA_dfitc.service
● FrameWorkA_dfitc.service - FrameWorkA_dfitc
     Loaded: loaded (/etc/systemd/system/FrameWorkA_dfitc.service; disabled)
     Active: activating (auto-restart) (Result: exit-code)
    Process: 538504 ExecStart=/root/gyb/test/FrameWorkA_dfitc (code=exited, status=203/EXEC)
   Main PID: 538504 (code=exited, status=203/EXEC)

查看日志:

[root@S-756 test]# journalctl -u FrameWorkA_dfitc.service
Jun 24 10:19:34 S-756 systemd[538504]: FrameWorkA_dfitc.service: Failed to locate executable /root/gyb/test/FrameWorkA_dfitc: Permission denied
Jun 24 10:19:34 S-756 systemd[538504]: FrameWorkA_dfitc.service: Failed at step EXEC spawning /root/gyb/test/FrameWorkA_dfitc: Permission denied

status=203/EXEC 这个错误码很明确——systemd 尝试执行程序时失败了。而 Permission denied 则给了我们第一个线索:这不是文件不存在,而是权限问题


二、排查思路:从文件权限到 SELinux

第 1 步:检查文件是否存在

[root@S-756 test]# ll /root/gyb/test/FrameWorkA_dfitc
lrwxrwxrwx. 1 root root 24 Jun 23 17:51 /root/gyb/test/FrameWorkA_dfitc -> /root/gyb/test/FrameWorkA

文件存在,是软链接,权限 777,没问题。

第 2 步:快速定位——关闭 SELinux

排障的基本原则:先确认问题范围。执行 setenforce 0 临时关闭 SELinux:

[root@S-756 test]# setenforce 0
[root@S-756 test]# systemctl restart FrameWorkA_dfitc.service
[root@S-756 test]# systemctl status FrameWorkA_dfitc.service
● FrameWorkA_dfitc.service - FrameWorkA_dfitc
     Active: active (running)   # ✅ 起来了!

确认了,是 SELinux 的问题。

⚠️ 注意:setenforce 0 仅用于测试定位,重启后失效,不要用它作为永久解决方案。

第 3 步:查看 SELinux 上下文

[root@S-756 test]# ls -Z ./FrameWorkA*
unconfined_u:object_r:admin_home_t:s0 ./FrameWorkA
unconfined_u:object_r:admin_home_t:s0 ./FrameWorkA_dfitc

发现问题了:

  • 当前目录是 /root/gyb/test/
  • 文件的 SELinux 类型是 admin_home_t
  • 这是 root 主目录下文件的默认类型

核心知识点:systemd 服务能否执行一个文件,取决于文件的 SELinux 类型。

SELinux 类型 说明 systemd 能否执行
bin_t 系统可执行文件 ✅ 允许
usr_t /opt 目录文件 ✅ 允许
admin_home_t root 主目录文件 ❌ 拒绝
user_home_t 普通用户主目录文件 ❌ 拒绝
tmp_t /tmp 临时文件 ❌ 拒绝

真相大白了:程序放在 /root/ 下,SELinux 类型是 admin_home_t,systemd 拒绝执行。


三、尝试修复:chcon 为什么不生效?

既然知道了是类型不对,那就改类型:

[root@S-756 test]# chcon -t bin_t FrameWorkA_dfitc
[root@S-756 test]# ls -Z FrameWorkA_dfitc
unconfined_u:object_r:admin_home_t:s0 FrameWorkA_dfitc  # 没变!

奇怪,为什么 chcon 没生效?

原因 1:chcon 默认行为是跟随软链接

chcon -t bin_t link    # 修改的是 link 指向的目标文件
chcon -h -t bin_t link # 修改 link 本身(需要 -h 参数)

原因 2:软链接上下文固化

软链接在创建时继承了当时父目录的上下文。即使父目录后来改了,软链接本身的上下文也不会自动更新。

# 查看父目录上下文
[root@S-756 test]# ls -Zd /root/gyb/test/
unconfined_u:object_r:admin_home_t:s0 /root/gyb/test/

父目录是 admin_home_t,软链接创建时继承了这个类型,然后"固化"了。


四、解决方案:两招搞定

方案一:先改源文件,再重建链接(推荐)

这个方案的逻辑是:先确保源文件类型正确,然后重建软链接,让新链接继承正确上下文。

# 1. 修改目标文件为 bin_t
chcon -t bin_t FrameWorkA

# 2. 删除旧链接,重建新链接
rm -f FrameWorkA_dfitc
ln -s FrameWorkA FrameWorkA_dfitc

# 3. 验证
[root@S-756 test]# ls -Z FrameWorkA*
unconfined_u:object_r:bin_t:s0 FrameWorkA      ✅
unconfined_u:object_r:bin_t:s0 FrameWorkA_dfitc  ✅

# 4. 恢复 SELinux,启动服务
setenforce 1
systemctl start FrameWorkA_dfitc.service

方案二:彻底规避——放到 /opt 目录

如果不想每次都处理 SELinux 上下文,直接把项目放到系统标准路径下。

# /opt 的 SELinux 类型是 usr_t
[root@S-756 dfitc]# ls -Zd /opt
system_u:object_r:usr_t:s0 /opt

# 程序自动继承 usr_t,systemd 允许执行
[root@S-756 dfitc]# ls -Z FrameWorkA
unconfined_u:object_r:usr_t:s0 FrameWorkA  ✅

完整操作:

cp -r /root/gyb/test /opt/dfitc
cd /opt/dfitc/ && ./pobobash.sh
systemctl start FrameWorkA_dfitc.service

五、脚本改进:自动化处理

如果项目必须留在 /root//home/ 下,可以修改部署脚本,在创建软链接前先处理 SELinux 上下文:

create_symlink_with_context() {
    local src="$1"
    local dst="$2"
    
    # 先修改源文件上下文
    sudo chcon -t bin_t "$src" 2>/dev/null || true
    
    # 创建软链接
    ln -s "$src" "$dst"
    
    # 验证
    echo "Context: $(ls -Z "$dst" | awk '{print $1}')"
}

六、知识点总结

1. SELinux 上下文类型速查

命令 作用
getenforce 查看 SELinux 状态
setenforce 0 临时关闭(测试用,重启失效)
setenforce 1 恢复强制模式
ls -Z <文件> 查看文件 SELinux 上下文
ls -Zd <目录> 查看目录 SELinux 上下文

2. 软链接的 SELinux 坑

  • 软链接有自己的 SELinux 上下文,不与目标文件同步
  • 创建时继承父目录的上下文
  • 删除重建是刷新上下文最可靠的方式
  • chcon 默认修改目标文件,改链接本身需要 -h 参数

3. systemd 执行权限速查

路径 SELinux 类型 systemd 能否执行
/usr/local/bin/ bin_t ✅ 允许
/opt/ usr_t ✅ 允许
/root/ admin_home_t ❌ 拒绝
/home/ user_home_t ❌ 拒绝
/tmp/ tmp_t ❌ 拒绝

4. 排障流程

服务启动失败 (status=203/EXEC)
    ↓
检查文件是否存在 → 否 → 修复路径
    ↓ 是
检查执行权限 → 否 → chmod +x
    ↓ 是
setenforce 0 测试
    ↓
服务启动成功 → SELinux 问题
    ↓
ls -Z 检查上下文
    ↓
方案一:chcon -t bin_t + 重建链接
方案二:移到 /opt 目录
    ↓
setenforce 1 恢复
    ↓
服务正常运行 ✅

七、写在最后

这次排查花了不少时间,根源在于对 SELinux 和软链接交互行为的认知不足。有几个要点值得记住:

  1. setenforce 0 是排障利器,快速确认问题范围,但永远不要用它做永久解决方案
  2. 软链接不是透明的,它有自己独立的 SELinux 上下文
  3. chcon 默认跟随软链接,修改链接本身需要 -h 参数
  4. /opt 是第三方应用的理想归宿,SELinux 类型为 usr_t,天然支持 systemd 执行
  5. 永久关闭 SELinux 是下下策,用正确方式配置它,而不是绕过它

希望这次踩坑记录能帮你少走弯路。


相关文档SELinux 官方文档 | systemd 服务管理

posted @ 2026-06-24 16:59  morty-root  阅读(49)  评论(0)    收藏  举报