SELinux 决策与日志

切换 SELinux 的运行状态

切换全局运行状态

SELinux 作为一个整体,有三种状态:EnforcingPermissiveDisabled

  • Enforcing:启用 SELinux 的功能,且 SELinux 会阻止记录不符合 SELinux 策略的操作

  • Permissive:启用 SELinux 的功能,但 SELinux 不阻止不符合 SELinux 策略的操作,且记录不符合 SELinux 策略的操作。

  • Disabled:完全关闭 SELinux。注意,在这种状态下,所有为文件设置 SELinux 标签的操作也不会执行,可能会导致文件不具有正确的 SELinux 上下文标签,若需要再次启用 SELinux,则可能需要对(所有)文件执行 relabeling 操作。

修改 /etc/selinux/config 中的 SELINUXTYPE= 行,可以在 enforcing/permissive/disabled 之间切换,需要重启系统
读取 /sys/fs/selinux/enforce 可以获取当前是处于 enforcing 状态还是 permissive 状态。而修改这个文件的值,也可以切换 SELinux 的 enforcing/permissive 状态。

# 查看 SELinux 当前的 enforcing 状态
cat /sys/fs/selinux/enforce
# 将 SELinux 的当前状态切换到 Permissive
echo 0 | sudo tee /sys/fs/selinux/enforce

当然 SELinux 本身也提供了一个工具来查看/切换 enforcing 状态:

# 查看 SELinux 当前的 enforcing 状态
getenforce
# 将 SELinux 的当前状态切换到 Permissive
setenforce 0

既然我们可以动态切换 Enforcing 状态,SELinux 也提供了一些方法限制 Enforcing 切换到 Permissive,除了在编译 Linux Kernel 时进行配置,还可以通过修改 SELinux Boolean 的值来进行配置。
SELinux Boolean 是一些控制 SELinux 部分规则启用与否的“开关值”,比如上面说的“限制 Enforcing 切换到 Permissive”,就可以通过配置 secure_mode_policyload 这个 SELinux Boolean 来限制。

# 获取 secure_mode_policyload SELinux Boolean 状态
getsebool secure_mode_policyload
# 在本次系统运行中禁止 SELinux 从 Enforcing 切换到 Permissive
setsebool secure_mode_policyload 1
# 当然,若你希望这个 SELinux Boolean 在重启之后依旧保持当前状态,
# 可以使用 -P 参数
setsebool -P secure_mode_policyload 1

在我们将 secure_mode_policyload 切换到 true/on/1 后,我们就不可以将已经处于 Enforcing 状态的 SELinux 切换到 Permissive 状态了。

Note即便启用了 secure_mode_policyload,若当前处于 Permissive 状态(比如是从 Permissive 状态启动系统的),那么系统依旧保持 Permissive 状态,且可以从 Permissive 切换到 Enforcing。

若我们希望在启动时控制 SELinux 的状态,那么我们可以修改 Linux Kernel 的命令行来覆盖系统文件上的 SELinux 状态:
以 grub 为例:
修改 /etc/default/grub ,在 linux commandline 对应的行的行尾添加 selinux=0 或者 enforcing=0,并调用 grub2-mkconfig,可以彻底关闭 SELinux 或者将 SELinux 设置为
对于 systemd-boot,则需要修改的文件为 /etc/kernel/cmdline,需要调用的命令为 kernel-install add-all

切换特定域的 SELinux 状态

permissive domain 是一种将特定域设置为 permissive 的方法,这样我们就可以更加细化地控制 SELinux 的运行了。

# 列举当前处于 permissive domain 的域
# 这个命令会同时列举内嵌的 permissive domain 和系统管理员设置的 permissive domain
semanage permissive -l
# 将一个域添加至 permissive domain 中
semanage permissive -a <域名称>
# 将一个域从 permissive domain 中移除
semanage permissive -d <域名称>

SELinux 的日志与审计

默认情况,SELinux 会将信息发送给 Linux 的审计子系统(audit subsystem)。这些信息会被 Linux 审计守护程序(auditd)捕获,并记录在 /var/log/audit/audit.log 文件中。若系统中没有安装审计守护程序,则这些事件会存放在 Linux 内核事件缓存中。另外,还可以配置额外的审计调用进程(audisp),将特定的审计事件发送给其它程序。
除了上面两种流程,当系统上存在 SELinux 排错守护程序(setroubleshootd)时,auditd 还会调用 sedispatch 插件。后者会通过 D-Bus 将 SELinux 事件发送给 setroubleshootd。后者会捕获、记录 SELinux 事件,并提供可能的解决方案。
若内核没有配置 audit,或者系统上没有安装 auditd,或者 auditd 没有启动,则 SELinux 事件会记录至 Linux kernel message buffer 中,我们可以通过 dmesg 查看。
当 SELinux 检验特定的访问的时候,不会每次都查询策略。它会维护一个访问向量缓存(access vector cache, AVC),该缓存会记录已经发生过的访问操作的判定结果。之后若出现相同的访问操作,就可以使用缓存中的内容快速判定是否放行该操作。
注意到 AVC 这个词常常出现在 audit.log 的 type= 字段,它指的就是这条 log 来自于 access vector cache。

Tipauditd 是可以接受 SIGUSR1 执行日志轮转的。
<br>kill -s USR1 $(pidof auditd)<br># 当然,auditctl 除了接受 USR1 以外,还可以使用更人性化的信号名:<br>auditctl --signal rotate<br>

微调 AVC

AVC 本身是可以微调的。
若我们希望查看/调整 AVC 的大小,我们可以访问 /sys/fs/selinux/avc/cache_threshold
我们也可以查看 AVC 当前的运行统计:读取 /sys/fs/selinux/avc/hash_status
要查看 AVC 实时的命中状态,可以使用 avcstate <刷新间隔秒数> 查看。

显示更多的日志

SELinux 策略作者会通过 dontaudit 明确表示一个 SELinux 阻止不会被记录。一般来说,这些不被记录的阻止记录,都是不影响系统安全的阻止。

Notedontaudit 是 don’t audit 的缩写,而非 do not audit 的缩写

seinfo 命令可以统计这些特殊的规则。而且它还会通过 auditallow 标记出,即便是允许但也要记录的规则数量。

seinfo | grep -i audit

列举所有 dontauditauditallow 规则

sesearch --dontaudit
sesearch --auditallow
Tip在 Fedora 系统上,默认是记录 su 引起的 AVC 阻止,但不记录 sudo 引起的 AVC 阻止的。
因此,若你希望使用 sudo 来测试 AVC 阻止日志,则可以禁用所有的 dontaudit 规则。

我们可以通过下面的命令,在生成策略的时候,不将任何 dnotaudit 规则编译至策略中。

semodule --disable_dontaudit --build

# 或

semanage donotaudit off

若我们要恢复原始状态,则不加任何参数,再次编译一边策略。

semodule --build

# 或

semanage donotaudit off

配置 Linux 审计

/etc/audit/auditd.conf 可以配置 auditd 的运行参数,比如审计日志的输出位置(log_file=),以及 auditd 的各种插件(比如发送远程日志 audisp-remote)以及插件的相关配置。

NoteFedora 默认没有安装任何 auditd 插件,要安装 auditd 自带的插件,可以安装 audispd-plugins

阅读 SELinux 阻止

注意,SELinux 是在 DAC 之后执行的,若一个访问已经被 DAC 阻止了,那么就轮不到 SELinux 的执行任何动作。
我们可以使用 ausearch 提取 auditd 日志(包含已轮转日志)中有关 AVC 的条目:

ausearch --message avc --start recent --interpret

其中,
--message avc会从众多 audit 日志中筛选出具有 AVC 信息类型(message type)的日志。

Tip如果想查看所有类型的 audit 日志,则需要将 avc 修改为 all

--start recent仅罗列给定的时间之后的日志,recent 表示近十分中的日志
--interpret将返回的数值值解析为人类可读的形式,比如将 epoch timestamp 转换为日期和时间
这里列举两例返回值:

Note请参阅官方说明:Auditing SELinux Events

Example 1. SELinux 禁止访问某文件
type=AVC msg=audit(09/29/2024 23:11:20.188:262) : avc: denied { read } for pid=1428 comm=cat name=test.txt dev="sda2" ino=43993 scontext=user_u:user_r:user_t:s0 tcontext=user_u:object_r:httpd_sys_content_t:s0 tclass=file permissive=0
type=AVC记录的类型,当值为 AVC 则表示内核事件
还有一种为 USER_AVC,表示用户空间对象管理器事件(user-space object manager events)
msg=audit(09/29/2024 23:11:20.188:262)这里主要是时间戳和一个序列号,形式为 audit(timestamp:serial_number)
如上面解释的,由于我们调用 ausearch 命令时使用了 --interpret,这里的时间戳以日期时间的形式显示
avc表明这个字段之后的内容都来自于 AVC
denied这是一条阻止某操作而产生的日志
{ read }该条记录阻止的操作,这里是阻止了一次包含“读取”的操作
pid=1428操作的发起方的进程 ID
comm=cat发起方的命令(command)
注意,该条目不含命令行参数
name=test.txt目标(资源)名称,若目标为文件系统中的文件,则显示文件名
dev="sda2"目标资源所在的设备名称
ino=43993目标资源的 inode 号
scontext=user_u:user_r:user_t:s0操作发起方(subject)的 SELinux 上下文
tcontext=user_u:object_r:httpd_sys_content_t:s0目标的 SELinux 上下文
tclass=file目标的对象类别(object class)
permissive=0在该记录上,SELinux 是否处于 Permissive 状态
Example 2. SELinux 禁止使用某内核能力
type=AVC msg=audit(09/29/2024 23:14:37.116:269) : avc: denied { setuid } for pid=1450 comm=sudo capability=setuid scontext=user_u:user_r:user_t:s0 tcontext=user_u:user_r:user_t:s0 tclass=capability permissive=0
这里比较特殊的就是 capability=setuidtclass=capability。由于我们在使用 ausearch 的时候使用了 --interpret,“capability=7”被翻译为了“capability=setuid”。这里的 capability 直接对应 Linux kernel 中的 capability 的定义。具体某个 capability 对应的什么内核能力,参见 man 7 capabilities

其它 SELinux 相关的事件类型

 

Tip查看 /usr/include 下的 linux/audit.h 来获取全部审计事件(audit event)的列表

USER_AVC

部分用户空间程序也会对特定的 SELinux class 和/或 特定的权限执行审计,这些审计可能会产生 typeUSER_AVC 类型的审计日志。这种审计日志会包含执行审计的程序(被称为 user space object manager)的信息,以及被审计的操作的信息(包含在后一个 "msg=" 字段中)。

SELINUX_ERR

SELinux 内部错误。
比如某个 SELinux 操作要求某个进程从原 SELinux domain 迁移到新的 SELinux domain,但这个新的 SELinux domain 和现有的 SELinux role 不匹配(比如 SELinux role 并不允许具有该 SELinux domain)。这类错误并不能通过简单允许该操作来修正,就会产生 SELINUX_ERR

MAC_POLICY_LOAD

向内存中载入 SELinux 策略时,就会产生这个事件。常见的导致 SELinux 载入的操作有:载入新 SELinux module 或更新 SELinux module,重建策略,以永久(比如 setsebool -P)方式修改 SELinux Boolean。
而且,该事件发生后,可能会跟随 USER_MAC_POLICY_LOAD 事件,这是 user space object manager 触发的规则重载。

MAC_CONFIG_CHANGE

当 SELinux Boolean 以非永久模式修改时,会触发的事件

MAC_STATUS

当 SELinux 从 on 切换到 off,或在 enforcing 和 permissive 之间切换时,产生该事件

使用 ausearch

这里仅有一点需要额外提及:
ausearch 提供了一个 checkpoint 功能,可以将本次搜索的结果作为下次搜索的基点。在执行 SELinux 策略的排错和调试的时候,我们就可以更加准确的判定某个修改是否影响了特定的 SELinux 判定。

ausearch --checkpoint <checkpoint 路径> --start checkpoint

--checkpoint <checkpoint 路径>提供一个 checkpoint 文件的存放路径。
注意,若不提供这个文件,那么参数中有关 checkpoint 的设置不会生效。
另外,一旦提供了这个路径,则 ausearch 会尝试 读取 & (更新 or 创建) 这个文件。
目前 ausearch 还不能仅读取或创建新文件,但不修改现有 checkpoint 文件。
因此,若我们想反复使用同一个 checkpoint,那么手动备份可能是比较好的选择。
--start checkpoint以 checkpoint 中的记录作为搜索起始点

Tip当然,除了使用 checkpoint,还有另一种方法,那就是结合 auditd 的日志轮转功能,与 ausearch 可以指定 audit 日志文件的功能,来达成类似于捕获特定范围内日志的效果:
<br># 在触发 AVC 阻止前进行 audit 日志轮转<br>auditctl --signal rotate<br># 触发 AVC 阻止<br># 【可选】触发后再次进行 audit 日志轮转<br>auditctl --signal rotate<br># 使用指定文件的方式查询 AVC 错误<br>ausearch --input /var/log/audit.log.1 --message avc --interpret<br># 或者你没有做最后的日志轮转<br>ausearch --input /var/log/audit.log --message avc --interpret<br>
相较于直接使用 checkpoint,我们就不担心 ausearch 会自动更新 checkpoint 文件了。

为拒绝寻求帮助

 

Important遇到了 SELinux 问题,必然是要查询 SELinux 帮助的。有很多域是有自己的 manpage 的,这些 manpage 中包含了与该域相关的各种类型的说明。而这些 manpage 的名称均具有如下的形式 <域名称>_selinux,比如 httpd_selinux 就是介绍 httpd 这个域相关的 selinux 信息的。
这些文档都是由包 selinux-policy-doc 提供的。(很可惜,这个包不是任何包的依赖,若系统在安装的时候没有额外安装这个包,就只能管理员自己安装了)。
我们可以使用 man -k selinux 模糊搜索所有提及 selinux 的文档。

使用 setroubleshoot

setroubleshoot 是 RHEL 相关的发行版本具有的额外的 SELinux 策略排错软件包。这个软件包包含两大部分:setroubleshootdsealert。其中 setroubleshootd 用来接受来自 D-bus 的 SELinux denial,通过分析插件分析这个信息,并给出用户友好的错误分析和建议。之后它会记录这个分析,并提示其它客户端程序产生了一个 alert。sealert 就是上文提到的客户端之一,它既是一个 GUI 程序,也是一个 commandline 程序,它的作用就是告知用户 setroubleshootd 的分析结果。
整个流程为,SELinux denial 触发 auditd 记录事件,auditd 触发 audispd,而 audispd 会使用来自 setroubleshoot 的插件 sedispatch,后者会通过 D-Bus 将这个 denial 发送到 setroubleshootd 中,setroubleshootd 分析,之后让 seapplet 发送桌面通知,用户就可以通过这个桌面通知启动(或自行启动)sealert 来查看分析结果。

Tip使用 sealert -l "*" 在命令行中检索信息

其它方法

setroubleshoot 不仅可以在本地触发 alert,它可以将分析结果通过邮件发送出去。具体配置见 /etc/setroubleshoot/setroubleshoot.conf 中的配置
audit2why 可以读取 ausearch 返回的结果,并给出简略的提示信息。
journalctl 可以使用 _SELINUX_CONTEXT= 过滤具有指定 SELinux 上下文的日志。注意,journalctl 具有强大的 Tab 补齐功能,因此过滤 SELinux 上下文的工作没有看起来那么废脑子。
假设我们不知道要查找的 SELinux 上下文是什么,那我们还有另一种过滤方法,通过 _TRANSPORT=audit 过滤器,并配合 --grep <正则表达式>journalctl 筛选信息文本。
比如下面这行就执行了从 audit 内核子模块信息中筛选出包含 test.txt 信息的文本

journalctl '_TRANSPORT=audit' --grep 'test\.txt'

就常规的系统运维来说,大部分的 SELinux denial 主要是由于文件的 SELinux 上下文不正确导致的。因此,实际上我们做的最多的事情就是修改文件的 SELinux 上下文。而 matchpathcon 这个工具就可以读取一个路径文本(不要求真实存在),然后工具会从 SELinux 策略中查找该路径应该具有的 SELinux 上下文。

作者:SteveChen  创建时间:2025-04-06 14:02
最后编辑:SteveChen  更新时间:2025-04-06 14:02
上一篇:
下一篇: