Linux 基础体系 · 第 73/85 篇。示例面向现代主流 Linux 发行版;发行版差异、权限和生产风险会明确说明。

SELinux 完整基础:Label、Type Enforcement、Policy、布尔值和排障

SELinux(Security-Enhanced Linux)是 Linux 内核中的强制访问控制(Mandatory Access Control,MAC)机制。它不替代传统的 Unix 权限,而是在传统权限检查之外,再增加一层由安全策略决定的访问控制。

一个进程即使以 root 身份运行,也不一定能够读取、修改或执行任意文件;反过来,文件的 Unix 权限允许访问,也不代表 SELinux 一定允许访问。

SELinux 的核心可以概括为:

最终允许=DAC 允许SELinux 策略允许其他安全约束允许\text{最终允许} = \text{DAC 允许} \land \text{SELinux 策略允许} \land \text{其他安全约束允许}

其中:

  • DAC(Discretionary Access Control)是传统的用户、组、文件权限和 ACL;
  • SELinux 策略主要通过 Label、Type Enforcement 和规则决定;
  • 在启用 MLS/MCS 等机制时,还可能检查安全级别约束;
  • 内核会在系统调用执行期间实施这些检查。

因此,排查“服务明明有权限却访问失败”时,不能只看 chmodchown


一、SELinux 解决什么问题

传统 Unix 权限通常围绕文件所有者、所属组和其他用户展开。例如:

-rw-r----- 1 app app 1024 /srv/app/config.yml

这表示:

  • 文件所有者 app 可以读写;
  • app 组成员可以读;
  • 其他用户没有权限。

但是,如果某个 Web 服务进程本身以 app 用户运行,那么它可能同时拥有:

  • 读取应用配置的能力;
  • 读取数据库凭据的能力;
  • 修改应用目录的能力;
  • 访问用户上传文件的能力。

传统权限很难表达“这个服务可以读取网站静态文件,但不能读取 SSH 私钥”这样的进程级限制。

SELinux 的基本思路是:不仅看访问者是谁,还看访问者运行在哪个安全域,以及目标对象具有什么安全类型。

例如:

nginx 进程     -> httpd_t
网站静态文件   -> httpd_sys_content_t
应用运行时目录 -> httpd_sys_rw_content_t
SSH 私钥       -> ssh_home_t 或 user_home_t

策略可以规定:

httpd_t 可以读取 httpd_sys_content_t
httpd_t 不可以读取 ssh_home_t
httpd_t 只有在特定条件下可以写入 httpd_sys_rw_content_t

这样,即使 Web 服务进程被入侵,攻击者获得的也只是 httpd_t 这个受限域,而不是整个系统的完整访问能力。


二、SELinux 的组件和访问路径

现代发行版中的 SELinux 通常由以下部分共同组成:

  1. Linux 内核安全模块

    • 执行安全钩子;
    • 检查文件、进程、套接字等对象的访问;
    • 记录被拒绝的访问。
  2. SELinux 用户空间工具

    • 查询和修改运行状态;
    • 管理文件 Label;
    • 加载策略模块;
    • 管理布尔值;
    • 查询审计日志。
  3. SELinux Policy

    • 描述哪些域可以访问哪些类型;
    • 描述对象创建、域转换、继承等行为;
    • 可能包含基于布尔值的条件规则。
  4. 审计系统

    • 通常通过 auditd 或内核审计接口接收 AVC(Access Vector Cache)拒绝事件;
    • 日志常见于 /var/log/audit/audit.log
    • systemd 环境下也可能通过 journalctl 查询。

一次典型访问可以抽象为:

进程发起系统调用
        |
        v
传统 DAC 检查
        |
        | 失败 -> 返回 EACCES/EPERM
        v
SELinux 安全检查
        |
        | 失败 -> 记录 AVC,返回拒绝
        v
文件系统或内核对象执行实际操作

SELinux 不一定是唯一的拒绝来源。还可能存在:

  • 文件 Unix 权限不足;
  • POSIX ACL 拒绝;
  • 只读挂载;
  • 文件系统不支持所需操作;
  • seccomp、容器隔离或 systemd sandbox 限制;
  • AppArmor 或其他 LSM 限制。

因此,不能看到服务报错就直接运行 audit2allow


三、Label:SELinux 的安全上下文

3.1 Label 是什么

SELinux 为进程、文件、目录、端口、设备和其他对象关联安全上下文(security context),通常写成:

user:role:type:level

例如:

system_u:system_r:httpd_t:s0
system_u:object_r:httpd_sys_content_t:s0

四个字段分别表示:

字段 含义
SELinux user SELinux 用户身份,不等同于 Linux 用户
role SELinux 角色,主要用于 RBAC
type 类型,是 Type Enforcement 的核心
level 安全级别,主要用于 MLS/MCS

在大多数启用默认 targeted policy 的服务器上,最需要关注的是 type

例如:

system_u:system_r:sshd_t:s0

这是一个进程上下文,其中:

  • system_u 是 SELinux 用户;
  • system_r 是角色;
  • sshd_t 是进程类型,也称为域(domain);
  • s0 是安全级别。

目录或普通文件通常使用:

system_u:object_r:var_t:s0

其中 object_r 表示对象角色。

3.2 查看进程和文件 Label

查看进程:

ps -eZ | grep -E 'sshd|nginx|httpd'

可能得到:

system_u:system_r:sshd_t:s0  812 ?  00:00:00 sshd
system_u:system_r:httpd_t:s0 1250 ?  00:00:00 nginx

查看文件:

ls -lZ /etc/ssh/sshd_config
ls -ldZ /var/www/html

示例输出:

-rw-------. root root system_u:object_r:sshd_config_t:s0 /etc/ssh/sshd_config
drwxr-xr-x. root root system_u:object_r:httpd_sys_content_t:s0 /var/www/html

查看当前登录 Shell 的上下文:

id -Z

查看当前 SELinux 状态:

getenforce
sestatus

典型状态是:

Enforcing

三种模式含义如下:

  • Enforcing:执行策略,违规访问会被拒绝并通常记录日志;
  • Permissive:不实际阻止访问,但记录可能的拒绝;
  • Disabled:内核不加载 SELinux。

setenforce 0setenforce 1 通常只改变当前运行状态:

sudo setenforce 0
sudo setenforce 1

它们不会自动永久修改启动配置。生产环境不应把 Permissive 当作长期解决方案;它可以用于短时诊断,但会让被攻击的进程获得策略本应阻止的访问能力。

3.3 Label 不等于 Linux 文件权限

下面两条命令操作的是不同层次:

chown nginx:nginx /srv/site/index.html
chmod 0644 /srv/site/index.html

它们修改传统 DAC 属性。

chcon -t httpd_sys_content_t /srv/site/index.html

它修改 SELinux Label。

即使文件最终显示为:

-rw-r--r--. nginx nginx system_u:object_r:httpd_sys_content_t:s0

也只能说明两层属性分别满足某些条件,不能直接推断任意进程都能访问。


四、Type Enforcement:SELinux 的主要授权模型

4.1 域、类型和访问向量

Type Enforcement(TE,类型强制)把主体和客体都抽象为类型:

  • 进程类型通常称为域,例如 sshd_thttpd_t
  • 文件类型例如 httpd_sys_content_tetc_t
  • 网络套接字、目录、进程等对象也具有类型。

一条典型 TE 规则可以抽象为:

allow <source_type> <target_type>:<object_class> <permissions>;

例如:

allow httpd_t httpd_sys_content_t:file { open read getattr };

含义是:

类型为 httpd_t 的进程,可以对类型为 httpd_sys_content_t 的普通文件执行 openreadgetattr

目录访问还需要目录本身的权限,例如:

allow httpd_t httpd_sys_content_t:dir {
    search
    getattr
    open
    read
};

实际策略通常通过宏和接口生成,不一定直接出现这样的底层规则,但这个形式有助于理解访问决策。

4.2 访问不只是“读文件”

一个程序读取:

/var/www/html/index.html

至少可能涉及:

  1. / 执行 search
  2. /var 执行 search
  3. /var/www 执行 search
  4. /var/www/html 执行 searchgetattr
  5. index.html 执行 openreadgetattr

因此,文件本身 Label 正确,并不保证访问成功。父目录缺少搜索权限、传统权限不足或挂载参数异常,都可能导致失败。

4.3 允许条件的形式化表达

对一次访问,可以使用下面的抽象条件表示:

Permit(s,t,c,p)=DAC(s,t,c,p)TE(stype,ttype,c,p)Constraint(s,t,c,p)Permit(s,t,c,p) = DAC(s,t,c,p) \land TE(s_type,t_type,c,p) \land Constraint(s,t,c,p)

变量含义:

  • ss:主体,例如一个进程;
  • tt:目标对象,例如一个文件;
  • cc:对象类别,例如 filedirtcp_socket
  • pp:具体权限,例如 readwriteconnect
  • DACDAC:传统权限、ACL 等是否允许;
  • TETE:SELinux TE 规则是否允许;
  • ConstraintConstraint:角色、级别或其他约束是否允许。

假设:

进程类型 = httpd_t
文件类型 = httpd_sys_content_t
对象类别 = file
权限     = read

如果策略存在:

allow httpd_t httpd_sys_content_t:file read;

并且 DAC 也允许该进程读取路径,那么访问才可能成功。

反例是:

文件类型 = default_t

即使文件权限是:

-rw-r--r--. root root

也不代表 httpd_t 能读取。默认策略通常不会允许 Web 服务读取任意 default_t 文件。

4.4 为什么改成 777 仍然失败

下面的操作经常被误用:

chmod -R 777 /srv/site

它最多改变 DAC:

所有本地用户都可能通过传统权限访问

但如果 SELinux 规则没有允许:

httpd_t -> 目标类型:file read

访问仍然会被 SELinux 拒绝。

更糟的是,chmod -R 777 会扩大所有本地用户和被入侵服务的写权限,可能造成:

  • Web Shell 或恶意文件被植入;
  • 配置文件被篡改;
  • 密钥和凭据泄露;
  • 攻击者通过其他服务继续横向利用。

正确做法是先判断访问者进程域、目标路径 Label 和实际操作,再决定是否需要调整 Label、DAC 或策略。


五、文件 Label 的来源、继承和持久化

5.1 默认 Label 与文件上下文规则

SELinux 不仅保存当前文件的 Label,还可以根据路径匹配规则推导“这个路径应该具有什么 Label”。

查看文件上下文规则:

matchpathcon /var/www/html/index.html
semanage fcontext -l | grep -E '/var/www|httpd_sys_content_t'

matchpathcon 查询的是策略对某路径的默认判断;ls -Z 查看的是文件当前实际 Label。

二者可能不同:

matchpathcon: system_u:object_r:httpd_sys_content_t:s0
ls -Z:       system_u:object_r:default_t:s0

这通常表示文件曾被复制、解压、移动或手工修改,当前 Label 已经偏离默认规则。

5.2 restorecon:根据规则恢复 Label

如果路径本来就属于系统默认目录,可以执行:

sudo restorecon -Rv /var/www/html

参数含义:

  • -R:递归处理;
  • -v:显示修改过程。

预期可能看到:

Relabeled /var/www/html/index.html from ...:default_t to ...:httpd_sys_content_t

restorecon 的关键特点是:它根据已有的文件上下文规则恢复 Label,而不是任意猜测一个类型。

5.3 semanage fcontext:为自定义路径建立持久规则

如果网站目录放在 /srv/site,直接运行:

sudo chcon -R -t httpd_sys_content_t /srv/site

可能暂时有效,但后续执行 restorecon、重新打包恢复或某些系统维护操作时,Label 可能恢复为默认值。

更持久的做法是:

sudo semanage fcontext -a -t httpd_sys_content_t '/srv/site(/.*)?'
sudo restorecon -Rv /srv/site

这里:

  • -a 添加规则;
  • -t 指定目标类型;
  • '/srv/site(/.*)?' 匹配目录本身及其所有后代;
  • restorecon 把规则应用到已有文件。

检查规则:

sudo semanage fcontext -l | grep '/srv/site'

删除错误规则:

sudo semanage fcontext -d -t httpd_sys_content_t '/srv/site(/.*)?'
sudo restorecon -Rv /srv/site

实际生产环境中,semanage 可能来自发行版的额外软件包,例如某些 Red Hat 系发行版需要安装 policycoreutils-python-utils。包名在发行版之间不同,应使用本发行版的软件包管理器查询。

5.4 内容类型和可写类型必须区分

以 Web 服务为例,常见策略会区分:

httpd_sys_content_t     可由 Web 服务读取的内容
httpd_sys_rw_content_t  允许 Web 服务读写的内容

不要为了让上传功能工作,就把整个站点目录改成可写类型:

sudo semanage fcontext -a -t httpd_sys_rw_content_t '/srv/site(/.*)?'

这会扩大 Web 服务被入侵后的写入范围。更合理的模型是:

/srv/site/public      -> httpd_sys_content_t
/srv/site/uploads     -> httpd_sys_rw_content_t

例如:

sudo semanage fcontext -a -t httpd_sys_content_t '/srv/site/public(/.*)?'
sudo semanage fcontext -a -t httpd_sys_rw_content_t '/srv/site/uploads(/.*)?'
sudo restorecon -Rv /srv/site

这里的安全边界不是“能否访问整个站点”,而是“Web 服务在哪些最小目录中拥有写权限”。

5.5 创建、复制和移动的差异

Label 处理与文件操作方式有关:

  • 在已正确标记的目录中创建文件,通常会继承目录的类型;
  • cp 创建目标文件时,可能依据目标目录或复制选项产生不同结果;
  • mv 在同一文件系统内移动文件时通常保留原有 inode 的 Label;
  • 从其他文件系统移动过来、解压归档或使用特殊工具恢复文件时,Label 可能不符合目标路径规则。

因此,部署后执行:

sudo restorecon -Rv /srv/site

往往比单独依赖 cpmv 的行为更可靠。


六、Label 不只用于普通文件

6.1 进程域转换

服务启动时,进程可能从一个初始域转换到服务专用域。例如:

init_t -> sshd_t
init_t -> httpd_t

子进程也可能通过执行特定程序发生域转换。域转换规则决定了:

  • 哪个进程可以执行某个程序;
  • 执行后进入哪个域;
  • 新域具有什么访问能力。

如果一个服务错误地在通用域运行,或者没有正确发生域转换,排障方向就不能只看目标文件 Label,还要检查:

ps -eZ
systemctl status <service>
systemctl cat <service>

6.2 端口 Label

SELinux 也可以限制服务绑定哪些端口。查看端口类型:

sudo semanage port -l | grep http_port_t

典型结果可能包括:

http_port_t  tcp  80, 443, 8080

假设 Web 服务要绑定 8443,而该端口没有标记为 http_port_t,即使程序拥有普通 Unix 权限,也可能无法绑定。

添加端口:

sudo semanage port -a -t http_port_t -p tcp 8443

如果端口已经存在但类型不同,应使用修改:

sudo semanage port -m -t http_port_t -p tcp 8443

检查:

sudo semanage port -l | grep 8443

这与开放防火墙端口是两件事:

  • semanage port 解决 SELinux 对服务端口类型的判断;
  • firewall-cmd 或其他防火墙工具解决网络包是否到达主机。

两者都满足,服务才可能从外部正常访问。

6.3 网络连接和文件访问是不同权限

Web 服务读取文件成功,不表示它可以连接数据库或外部 API。网络访问可能受到:

  • SELinux 网络类权限;
  • 防火墙;
  • 路由和 DNS;
  • 远端服务 ACL;
  • 应用配置。

SELinux 策略通常通过布尔值控制一部分可选的网络能力,例如:

getsebool httpd_can_network_connect

如果业务确实需要 Web 服务主动连接其他服务,可以考虑:

sudo setsebool -P httpd_can_network_connect on

但是,这个布尔值往往允许更广泛的网络连接,而不是只允许某一个 IP 和端口。生产上应先确认发行版策略提供的更细粒度类型或专用布尔值,不能把“能联网”理解为“只连接数据库”。


七、Policy:策略由什么组成

7.1 Policy 是规则集合,不是单个配置文件

SELinux Policy 是一组描述安全关系的规则,通常包括:

  • 类型和域定义;
  • allow 授权规则;
  • 域转换规则;
  • 文件上下文规则;
  • 端口和设备类型;
  • 条件规则;
  • 角色和安全级别约束;
  • dontaudit 规则;
  • 策略模块及其依赖。

现代发行版通常使用模块化策略。管理员安装或启用某个服务时,相关策略模块可能随软件包提供。

查看已加载模块:

sudo semodule -l

查看某个模块的状态:

sudo semodule -lfull | grep <module-name>

不同发行版的输出格式和模块名称可能不同,因此不应依赖某个固定列表。

7.2 allow 不是完整策略语义

策略中的 allow 规则说明某种访问可以被允许,但实际决策还可能受到:

  • DAC;
  • neverallow 编译约束;
  • 角色约束;
  • MLS/MCS 约束;
  • 条件布尔值;
  • dontaudit 对日志的抑制;
  • 文件和进程实际 Label;
  • 对象类别和具体权限。

因此,“我查到了一个 allow 规则”不能直接推出“访问一定成功”。

7.3 查看某个访问是否有策略支持

可以使用:

sesearch -A -s httpd_t -t httpd_sys_content_t -c file -p read

参数含义:

  • -A:查询 allow 规则;
  • -s:源类型;
  • -t:目标类型;
  • -c:对象类别;
  • -p:权限。

可能得到类似:

allow httpd_t httpd_sys_content_t:file { getattr map open read };

如果要查看是否受布尔值条件控制,可以使用更完整的策略查询方式;具体输出会随 setools 版本和发行版策略不同而变化。

安装 sesearch 的软件包也因发行版不同而不同,常见来源是 setools-console

7.4 自定义策略模块的正确边界

当默认策略确实不覆盖某个合法业务行为时,可以开发自定义策略模块,而不是直接关闭 SELinux。

一个常见流程是:

  1. 在测试环境重现最小失败操作;
  2. 收集 AVC;
  3. 判断拒绝是否来自错误 Label、DAC、端口类型、布尔值或配置错误;
  4. 只有确认是合法且默认策略未覆盖时,才设计新规则;
  5. 在最小范围内编写和测试模块;
  6. 验证没有扩大无关访问能力;
  7. 记录模块来源、版本和回滚方法。

简单的自定义模块可能通过 audit2allow 生成初稿:

sudo ausearch -m AVC -ts recent | audit2allow -M local_service

安装前先查看:

cat local_service.te
cat local_service.fc 2>/dev/null

安装:

sudo semodule -i local_service.pp

删除:

sudo semodule -r local_service

audit2allow 只是把观察到的拒绝转换为可能的 allow 规则,它不知道业务意图。下面这种做法风险很高:

sudo ausearch -m AVC -ts recent | audit2allow -M fix
sudo semodule -i fix.pp

因为日志中的拒绝可能是:

  • 攻击者正在尝试越权;
  • 文件 Label 配错;
  • 服务启动参数错误;
  • 被错误配置触发的无效路径;
  • 本来就应该被拒绝的行为。

生成规则前应先理解每个 AVC 的主体、目标、对象类别、权限和调用路径。


八、布尔值:条件化启用策略能力

8.1 布尔值是什么

SELinux 布尔值是策略中的条件开关。策略可以表达:

如果 boolean_X 为 on:
    允许 httpd_t 访问某类网络对象
否则:
    不允许

查询当前值:

getsebool httpd_can_network_connect

示例:

httpd_can_network_connect --> off

查看所有布尔值:

getsebool -a

查询布尔值说明:

semanage boolean -l | grep httpd

说明通常会同时显示:

  • 当前运行时状态;
  • 永久配置状态;
  • 布尔值的文字描述。

8.2 临时与永久修改

临时修改:

sudo setsebool httpd_can_network_connect on

系统重启后通常会恢复原值。

永久修改:

sudo setsebool -P httpd_can_network_connect on

-P 会更新持久策略配置,并可能触发策略重新生成或加载,因此在生产系统上应安排变更窗口并验证服务状态。

回滚:

sudo setsebool -P httpd_can_network_connect off

8.3 布尔值不是任意权限开关

布尔值名称和含义取决于发行版策略。不能因为在一台系统上看到:

httpd_can_network_connect_db

就假设所有发行版都提供同名且同义的开关。

正确流程是:

getsebool -a | grep -E 'httpd|ssh|named|container'
semanage boolean -l

然后结合当前服务的实际需求选择。

布尔值的安全取舍是:

启用布尔值在一个预定义策略集合中扩大权限\text{启用布尔值} \Rightarrow \text{在一个预定义策略集合中扩大权限}

它通常比关闭 SELinux 更精确,但未必是最小权限。比如允许 Web 服务联网,并不一定能限制为“只访问数据库的 5432 端口”。如果业务需要更细粒度隔离,可能需要专门的服务域、端口类型、代理架构或网络层控制。


九、AVC:理解拒绝日志

9.1 AVC 日志包含哪些信息

典型 AVC 事件可以抽象为:

avc:  denied  { read } for
pid=1234 comm="nginx"
name="config.yml"
dev="vda1" ino=123456
scontext=system_u:system_r:httpd_t:s0
tcontext=system_u:object_r:default_t:s0
tclass=file

关键字段:

字段 含义
denied { read } 被拒绝的权限
pid 进程 PID
comm 进程名
name 目标名称
scontext 主体上下文
tcontext 目标上下文
tclass 目标对象类别
devino 文件系统设备和 inode 信息

把这条记录翻译成自然语言:

类型为 httpd_t 的进程,尝试对类型为 default_t 的普通文件执行 read,该访问被拒绝。

这比单纯看到“权限 denied”提供了更多因果信息。

9.2 查询审计日志

优先使用结构化查询:

sudo ausearch -m AVC -ts recent

查看最近十分钟:

sudo ausearch -m AVC -ts recent -i

-i 会把部分数值字段转换为更易读的形式。

systemd 环境还可以:

sudo journalctl -k | grep -i avc
sudo journalctl --since "10 minutes ago" | grep -i 'avc.*denied'

如果查询不到日志,不能直接得出“没有 SELinux 拒绝”。需要检查:

getenforce
systemctl is-active auditd
ls -l /var/log/audit/audit.log

还可能存在:

  • 审计服务未运行;
  • 日志已轮转;
  • 拒绝发生在更早时间;
  • dontaudit 抑制了某些日志;
  • 访问实际被 DAC、应用或其他组件拒绝。

9.3 audit2why 的作用和局限

可以使用:

sudo ausearch -m AVC -ts recent | audit2why

它会尝试解释:

  • 是否可能由布尔值控制;
  • 是否存在策略规则;
  • 是否可能是 Label 问题;
  • 是否建议创建本地策略。

但它是辅助解释工具,不是自动修复工具。输出“建议允许”不等于这就是正确的生产修复。


十、完整排障流程

下面以“Web 服务无法读取自定义目录”为例。

假设:

Web 根目录:/srv/site
服务进程:nginx
现象:访问页面返回 403

第一步:确认实际运行域

ps -eZ | grep nginx

预期类似:

system_u:system_r:httpd_t:s0  2100 ? 00:00:00 nginx

如果进程不在预期域中,先检查服务启动方式、二进制路径和域转换,而不是急于修改文件 Label。

第二步:检查当前强制状态

getenforce

如果是:

Enforcing

SELinux 会实际阻止访问。

如果是:

Permissive

服务可能暂时成功,但仍会产生 AVC。这种状态适合短时间收集证据,不适合作为生产修复。

第三步:检查完整路径的 Label

ls -ldZ /srv /srv/site
find /srv/site -maxdepth 2 -printf '%p\n' -exec ls -Zd {} \;

假设发现:

/srv/site/index.html  system_u:object_r:default_t:s0

这说明文件类型不符合 Web 服务通常使用的内容类型。

还需要检查父目录,因为 Web 服务必须对每一级目录拥有 search 能力。

第四步:检查路径预期类型

matchpathcon /srv/site/index.html

如果系统没有为 /srv/site 建立自定义规则,matchpathcon 可能不会返回 httpd_sys_content_t。这时应先建立规则:

sudo semanage fcontext -a -t httpd_sys_content_t '/srv/site(/.*)?'
sudo restorecon -Rv /srv/site

第五步:再次确认结果

ls -lZ /srv/site/index.html
matchpathcon /srv/site/index.html

两者的类型应一致或至少符合预期。

第六步:检查传统权限

namei -l /srv/site/index.html
getfacl /srv/site/index.html

namei -l 可以逐级显示路径权限,适合发现父目录缺少执行权限的问题。

例如:

drwx------ root root /srv

即使文件 Label 完全正确,httpd_t 对应的 Linux 用户仍可能无法穿过 /srv。此时需要修复 DAC 或路径设计,而不是添加 SELinux 规则。

第七步:复现并查询新 AVC

sudo ausearch -m AVC -ts recent -i

如果仍有拒绝,重点比较:

  • scontext 是否为预期服务域;
  • tcontext 是否为预期文件类型;
  • tclass 是否为 filedir 或其他类别;
  • 被拒绝的权限是否为 readsearchwriteexecute
  • 发生时间是否对应刚才的复现操作。

第八步:检查策略和布尔值

如果问题是网络连接:

getsebool httpd_can_network_connect
semanage boolean -l | grep httpd

如果问题是端口绑定:

sudo semanage port -l | grep http_port_t

如果问题是文件读取:

sesearch -A -s httpd_t -t httpd_sys_content_t -c file -p read

第九步:验证应用行为,而不是只验证命令

修复后测试:

curl -I http://127.0.0.1/
systemctl status nginx
journalctl -u nginx --since "5 minutes ago"

同时观察:

sudo ausearch -m AVC -ts recent -i

“页面返回 200”说明这一条请求成功,但仍应确认没有把整个目录错误地标记为可写、没有启用过宽的布尔值,也没有产生新的拒绝。


十一、常见失败方式及其原因

11.1 只修改 chmod

chmod -R 755 /srv/site

如果问题是 default_t 或其他不匹配的类型,DAC 修改不会解决 SELinux 拒绝,还可能改变大量无关文件的权限。

11.2 直接使用 chcon

chcon -R -t httpd_sys_content_t /srv/site

chcon 修改当前 Label,但通常不增加持久的路径映射规则。后续 restorecon 可能覆盖它。

适合:

  • 临时验证假设;
  • 实验环境快速测试。

持久配置应优先使用:

semanage fcontext
restorecon

11.3 把全部目录标记为可写

semanage fcontext -a -t httpd_sys_rw_content_t '/srv/site(/.*)?'

这会使整个站点成为 Web 服务潜在的写入区域。更小的写入范围通常更容易审计,也能降低服务被入侵后的持久化风险。

11.4 一看到 AVC 就运行 audit2allow

AVC 说明“某次访问被策略拒绝”,不说明“应该允许这次访问”。

如果攻击者正在尝试:

httpd_t -> ssh_home_t:file read

生成 allow 规则会把防护失败伪装成修复成功。

11.5 误把 dontaudit 当成没有拒绝

某些策略会使用 dontaudit 抑制预期噪声。日志中没有 AVC 并不等于所有访问都被允许,也不等于没有 SELinux 参与。

在测试环境中,可以使用策略调试工具检查被抑制的规则;具体命令和影响取决于发行版策略版本。不要在生产环境长期关闭审计抑制,因为日志量可能显著增加。

11.6 只检查文件,不检查进程和端口

以下三类问题都可能表现为“服务启动失败”:

进程域不正确
配置文件类型错误
监听端口类型错误

排查时应同时查看:

ps -eZ
ls -lZ <配置文件>
sudo semanage port -l

十二、SELinux 与 SSH 的关系

OpenSSH 本身负责 SSH 协议、认证和会话管理;SELinux 负责限制 sshd 及其子进程访问本机对象。

查看 SSH 进程:

ps -eZ | grep sshd

查看 SSH 配置文件:

ls -lZ /etc/ssh/sshd_config

检查 SSH 服务状态:

systemctl status sshd
journalctl -u sshd

发行版服务名可能是 sshdssh,例如:

systemctl status sshd
systemctl status ssh

不要把 OpenSSH 配置错误和 SELinux 拒绝混为一谈:

  • sshd_config 语法错误通常会出现在 sshd -t 或服务日志中;
  • 端口冲突会表现为 bind 失败;
  • SELinux 端口类型错误可能伴随 AVC 或服务启动拒绝;
  • 文件权限不安全可能被 OpenSSH 直接拒绝;
  • 用户登录后的目录访问还可能受到 SELinux 用户、角色和目录类型影响。

验证 OpenSSH 配置:

sudo sshd -t

这个命令只验证配置语法和基本合法性,不会替代 SELinux 排查。


十三、域转换与“执行”权限

SELinux 对“读取脚本”和“执行程序”可以进行不同控制。

假设某服务读取一个脚本文件:

httpd_t -> script_t:file read

不等于它一定可以执行:

httpd_t -> script_t:file execute

即便允许执行,执行后还可能发生:

httpd_t -> cgi_t

或其他域转换。

这使策略能够表达:

Web 服务可以读取静态内容;
Web 服务只能执行经过策略定义的 CGI;
执行 CGI 后进入更受限的域;
CGI 域不能直接读取 SSH 私钥。

因此,看到进程访问某文件时,应区分:

  • read:读取内容;
  • execute:执行文件;
  • entrypoint:作为进入目标域的程序;
  • transition:执行后改变域;
  • opengetattrmap 等辅助权限。

不要简单地把某个程序文件改成可执行权限,就认为 SELinux 会允许它运行。


十四、挂载、网络文件系统和容器边界

14.1 NFS 和 CIFS

网络文件系统可能无法像本地文件系统一样保存 SELinux 扩展属性,或者使用特殊的 Label 映射方式。

遇到 NFS/CIFS 上的访问问题,应检查:

mount
findmnt -T /path/to/file
ls -Zd /path/to/file

不同发行版和挂载方案可能使用:

  • NFS 服务端提供的 SELinux Label;
  • context= 挂载选项;
  • nfs_tcifs_t 等策略类型;
  • 多级安全标签映射。

不要把本地 ext4 上的 chcon 操作直接套到网络文件系统上。先确认文件系统是否支持扩展属性以及当前挂载策略。

14.2 容器

容器环境中,SELinux 还可能限制:

  • 容器进程域;
  • 容器访问宿主机路径;
  • 容器之间的数据目录隔离;
  • Podman、CRI-O 或 Kubernetes 的挂载 Label。

常见容器 Label 包括:

container_t
container_file_t

在支持的工具中,挂载卷时可能使用自动重新标记选项,例如 Podman 的 :Z:z。它们的语义和风险必须结合具体容器工具确认:

  • :Z 通常用于私有、专属容器卷;
  • :z 通常用于多个容器共享的卷;
  • 错误使用可能改变宿主机路径的 Label,影响其他服务。

容器排障不能只在容器内执行 ls -Z,还要在宿主机检查:

ps -eZ
ls -ldZ /host/path
podman inspect <container>

十五、Enforcing、Permissive 和 Disabled 的生产取舍

15.1 Enforcing

这是实际执行强制策略的状态。优点是:

  • 违规访问被阻止;
  • 可以限制服务入侵后的横向能力;
  • 策略错误能够尽早暴露。

风险是:

  • 未充分测试的自定义服务可能启动失败;
  • 部署流程遗漏 Label 时会产生运行故障;
  • 需要建立审计和变更流程。

15.2 Permissive

Permissive 会记录潜在拒绝,但不阻止访问。它适合:

  • 迁移或首次启用 SELinux 时收集行为;
  • 测试新服务;
  • 短时定位是否存在 SELinux 相关问题。

它不适合:

  • 长期生产运行;
  • 作为“解决问题”的替代方案;
  • 在不理解日志的情况下批量生成策略。

15.3 Disabled

Disabled 会让 SELinux 完全不参与。重新启用时,文件系统可能需要重新标记;如果系统长期处于 Disabled,直接切换到 Enforcing 可能造成大量服务故障。

在支持的发行版中,永久状态通常通过启动配置管理,例如检查:

grep -E '^SELINUX=' /etc/selinux/config

具体配置文件、引导参数和重新标记流程存在发行版差异,修改前应准备控制台访问、备份和回滚方案。不能仅执行一次 setenforce 1 就认为重启后仍是 Enforcing。


十六、一个最小实验:观察 Label 如何影响访问

以下实验应在测试虚拟机或专用环境执行。

创建测试目录:

sudo mkdir -p /srv/selinux-demo
echo 'selinux demo' | sudo tee /srv/selinux-demo/index.html

先查看 Label:

ls -lZ /srv/selinux-demo/index.html
matchpathcon /srv/selinux-demo/index.html

为目录建立 Web 内容规则:

sudo semanage fcontext -a -t httpd_sys_content_t '/srv/selinux-demo(/.*)?'
sudo restorecon -Rv /srv/selinux-demo

再次查看:

ls -lZ /srv/selinux-demo/index.html

预期类型变为:

httpd_sys_content_t

现在用一个服务域进行实际测试比直接用当前用户更有意义。例如,系统安装并运行 Web 服务后,可以把该目录配置为站点根目录,重新加载服务,再通过本机请求验证。

测试写入时,不要只看是否能创建文件,还要观察目录和文件的 Label:

ls -ldZ /srv/selinux-demo
find /srv/selinux-demo -maxdepth 1 -printf '%p\n' -exec ls -Zd {} \;

如果服务需要上传,应该只把上传子目录标记为:

sudo mkdir -p /srv/selinux-demo/uploads
sudo semanage fcontext -a -t httpd_sys_rw_content_t \
  '/srv/selinux-demo/uploads(/.*)?'
sudo restorecon -Rv /srv/selinux-demo/uploads

这样可以验证一个重要边界:

静态内容目录:读取
上传目录:读取和写入
其他目录:不自动获得写入权限

十七、排障决策树

可以把一次 SELinux 访问失败简化为以下路径:

flowchart TD
    A[服务访问失败] --> B{SELinux 是否启用?}
    B -- Disabled --> C[检查 DAC、ACL、应用和系统调用错误]
    B -- Enforcing/Permissive --> D[确认实际进程域]
    D --> E[检查目标对象和父目录 Label]
    E --> F{Label 是否符合预期?}
    F -- 否 --> G[使用 semanage fcontext 和 restorecon]
    F -- 是 --> H[检查 DAC、ACL、挂载和路径权限]
    G --> H
    H --> I{是否有 AVC?}
    I -- 是 --> J[分析 scontext、tcontext、tclass、权限]
    I -- 否 --> K[检查审计服务、日志时间和其他拒绝来源]
    J --> L{是否已有布尔值或端口类型?}
    L -- 是 --> M[按最小范围调整并验证]
    L -- 否 --> N[确认业务合法后设计最小自定义策略]
    M --> O[回归测试并观察新日志]
    N --> O
    K --> O

关键路径是:

  1. 先确认 SELinux 是否真的参与;
  2. 再确认进程域;
  3. 再确认目标 Label 和父目录;
  4. 再区分 DAC 与 SELinux;
  5. 最后才考虑布尔值或自定义策略。

这个顺序可以避免把路径错误、文件权限错误和策略错误混在一起。


十八、如何区分“策略问题”和“业务问题”

一个访问失败至少可以按以下证据分类:

SELinux Label 问题

特征:

tcontext=...:default_t:...

或者:

matchpathcon <path>
ls -Z <path>

二者不一致。

优先动作:

semanage fcontext
restorecon

DAC 或 ACL 问题

特征:

  • 没有对应 AVC;
  • namei -l 显示父目录无搜索权限;
  • getfacl 显示 ACL 拒绝;
  • 用相同 Linux 用户复现也失败。

优先检查:

namei -l /path/to/object
getfacl /path/to/object

SELinux 许可缺失

特征:

  • scontexttcontext 都符合预期;
  • 仍有明确 AVC;
  • 访问行为确实属于合法业务需求;
  • 没有可用布尔值、端口类型或现有策略接口。

这时才考虑自定义策略。

应用自身错误

特征:

  • SELinux 无拒绝;
  • DAC 和 Label 正确;
  • 应用日志显示配置解析失败、协议错误、超时或认证失败。

例如数据库连接失败可能是:

数据库凭据错误
DNS 失败
防火墙阻断
数据库拒绝来源地址
SELinux 禁止网络连接

不能只因为服务日志出现 Permission denied,就断言是 SELinux。


十九、Label、Policy 和布尔值之间的关系

三者承担不同职责:

Label 回答“对象属于哪一类”

例如:

/var/www/html/index.html
    -> httpd_sys_content_t

Policy 回答“哪些类型之间允许哪些操作”

例如:

httpd_t 可以读取 httpd_sys_content_t

Boolean 回答“某组预定义规则是否启用”

例如:

httpd_can_network_connect = on

它们的关系不是:

改 Label = 修改策略

也不是:

打开 Boolean = 任意访问

更准确的推导是:

实际进程域 + 实际目标类型 + 对象类别 + 权限
        |
        v
策略中的无条件规则或条件规则
        |
        v
访问是否得到 SELinux 允许

如果对象类型错误,正确策略可能无法匹配;如果类型正确但策略没有允许,修改 Label 也不会凭空创造访问权限;如果策略规则受布尔值控制,布尔值可能改变结果,但其范围由策略定义。


二十、生产环境中的变更、验证和恢复

20.1 变更前记录状态

修改前记录:

getenforce
sestatus
ps -eZ > /tmp/selinux-process-contexts.txt
sudo semanage fcontext -l > /tmp/selinux-fcontexts.txt
sudo semanage boolean -l > /tmp/selinux-booleans.txt
sudo semanage port -l > /tmp/selinux-ports.txt
sudo semodule -lfull > /tmp/selinux-modules.txt

这些信息用于:

  • 变更审计;
  • 故障对比;
  • 回滚判断;
  • 确认修改是否真的生效。

20.2 变更后验证三层结果

至少验证:

  1. 配置层

    semanage fcontext -l | grep /srv/site
    getsebool httpd_can_network_connect
    semanage port -l | grep 8443
    
  2. 运行层

    ps -eZ | grep nginx
    ls -lZ /srv/site/index.html
    systemctl status nginx
    
  3. 业务层

    curl -I http://127.0.0.1/
    

同时查询:

sudo ausearch -m AVC -ts recent -i

20.3 回滚要区分不同对象

回滚文件上下文规则:

sudo semanage fcontext -d '/srv/site(/.*)?'
sudo restorecon -Rv /srv/site

回滚布尔值:

sudo setsebool -P httpd_can_network_connect off

回滚端口类型:

sudo semanage port -d -t http_port_t -p tcp 8443

回滚策略模块:

sudo semodule -r local_service

如果规则由配置管理系统、软件包或自动化平台下发,还必须从源头回滚,否则下一次部署可能再次覆盖手工修改。


二十一、与 AppArmor 的概念差异

SELinux 和 AppArmor 都属于 Linux 强制访问控制方案,但模型不同。

SELinux 主要围绕:

进程域 + 对象类型 + 策略规则

AppArmor 主要围绕:

程序路径对应的 Profile + 路径规则

因此:

  • SELinux 排障重点通常是 ps -eZls -Zsemanage fcontext、AVC、布尔值和类型规则;
  • AppArmor 排障重点通常是 Profile、模式、路径规则和内核审计日志;
  • 两者不能直接把命令、规则语法和排障方法互换;
  • 某些发行版默认启用 SELinux,另一些发行版默认使用 AppArmor,实际状态必须通过系统命令确认。

无论采用哪一种机制,核心目标都是让服务获得最小必要能力,而不是让服务拥有“能工作就行”的广泛权限。


二十二、核心判断原则

理解 SELinux 后,可以用以下因果链分析问题:

谁在访问?
    -> 查看进程域和 Linux 用户

访问什么?
    -> 查看目标对象类型、路径和父目录

进行什么操作?
    -> 区分 read、write、execute、search、connect、bind 等权限

传统权限是否允许?
    -> 检查 owner/group/mode/ACL/挂载

SELinux 策略是否允许?
    -> 检查 AVC、sesearch、布尔值和端口类型

访问是否属于合法业务?
    -> 不是所有拒绝都应该被放行

修复是否持久且最小?
    -> 优先正确 Label,再考虑布尔值,最后才设计自定义策略

最重要的不是记住某个命令,而是保持对象模型:

进程运行在域中;
文件和其他对象拥有类型;
策略决定域对类型执行哪些操作;
布尔值条件化启用一组预定义规则;
AVC 记录实际被拒绝的访问;
restorecon 和 semanage 管理持久 Label;
自定义策略只应覆盖确认合法且确实缺失的最小行为。

当 Label、Type Enforcement、Policy、布尔值和审计证据能够对应起来时,SELinux 就不再是“随机挡住服务的黑盒”,而是一套可以观察、推导、验证和回滚的主机安全控制机制。


系列导航与关联阅读

官方资料

本文依据 Linux 内核、systemd 与主流发行版官方文档重新梳理;正文与实验由 WR BLOG 编写。