Translated message

A translation of this page exists in English.

快速开始编写自定义 SELinux 策略

已更新 -

Table of Contents

要在系统上运行受 SELinux 限制的新应用程序,请准备自定义策略文件来补充发行版 SELinux 策略强制执行的规则。

生成策略模板

policycoreutils-devel包提供的sepolicy 生成工具可帮助您执行初始步骤。该工具生成一个基本策略模块、一个 Makefile 和一个安装脚本。该脚本构建并安装策略模块,并重新标记 .fc 文件中定义的路径。您可以作为普通用户使用该命令,例如:

$ sepolicy generate --init <path_to_my_app_binary>

有关更多详细信息,请参阅 RHEL 使用 SELinux 文档中的为自定义应用程序创建和实施 SELinux 策略部分

如果您更喜欢图形界面和向导,可以使用policycoreutils-gui包提供的selinux-polgengui工具。

生成的策略模板包含以下策略文件:

  • <mypolicy>.te - 定义策略规则以及应用程序使用的新类型和域
  • <mypolicy>.fc - 包含文件上下文定义,换句话说,用于标记与应用程序相关的文件的说明
  • <mypolicy>.if - 包含接口,这些接口是其他策略模块用来与此策略交互的策略宏

根据应用程序行为允许访问

在系统上安装初始策略后,您可以根据应用程序所需的访问权限对其进行改进。

要在日志中包含所有访问的路径,请启用完整审核:

# audictl -d never,task
# auditctl -w /etc/ -p w

或者,打开/etc/audit/rules.d/audit.rules文件,删除“-a task,never”(如果存在),添加“-w /etc/ -p w”,然后重新启动审核守护程序。

要记录所有违反 SELinux 策略的行为,请将 SELinux 切换到宽容模式:

# setenforce 0

setenforce 0命令将整个系统上的 SELinux 设置为宽松模式,直到下次重新启动为止。您可以仅将新域切换到宽容模式,并将 SELinux 策略的其余部分保持在强制模式:

# semanage permissive -a <myapp_t>

请注意, sepolicygenerate命令将宽松的 <myapp_t>规则添加到策略模块中。因此,如果您保留了该规则,则可以跳过此步骤。

启用完全审核和宽容模式后,在新的 SELinux 域中运行应用程序。要查看应用程序所需的所有访问权限,请测试尽可能多的用例。系统以AVC (SELinux访问拒绝日志)的形式记录当前策略中不允许的所有访问请求。

处理 AVC

当 SELinux 拒绝某个操作时,系统会将访问向量缓存 (AVC) 消息添加到/var/log/audit/audit.log/var/log/messages文件中,或者 Journal 守护程序会记录拒绝。
您可以使用ausearch命令显示最近的AVC ,例如:

# ausearch -m AVC,USER_AVC -ts recent

-ts 最近参数将搜索限制为最近 10 分钟,但您可以使用时间戳来代替。

Audit2allow工具可以处理 AVC 并为我们的策略生成相应的允许规则。

每当您的自定义规则与其他策略包中定义的资源交互时,如果可能,请使用接口而不是简单的允许规则。Audit2allow -R命令尝试查找覆盖用例的接口。您必须检查此命令的每个结果,以确保生成的策略不会太宽松。

政策宏

策略宏也称为模式,或者在更复杂的情况下称为接口,它生成多个规则,这些规则一起工作以允许某些用例。

示例:

  • 接口:
    • mysql_read_config(<domain>) - 授予指定域(本例中为 httpd_t)对 MySQL 配置文件的读取访问权限
    • init_daemon_domain(<domain>, <file_type>) - 提升作为域的第一个参数给出的类型,而被 init 守护进程用作入口点可执行文件
  • 图案:
    • rw_files_pattern(<domain>, <dir_type>, <file_type>) - 给出对标记的文件进行读写访问位于标记为的目录中
    • domtrans_pattern(<source>, <entrypoint>, <target>) - 设置自动域转换:当进程在域中运行时执行一个标记为的二进制文件,生成的进程在域中运行

Fedora、RHEL 和 CentOS 中可用的接口在 selinux-policy Github 存储库的模块部分中定义。每个策略模块都包含一组接口,作为其他模块访问该模块中定义的资源的手段。

每个宏都有必须指定的固定数量的参数。如果您不确定相应的参数,请检查宏主体中的参数引用,其形式为$字符后跟索引(例如$1 )。或者,检查selinux-policy-doc包提供的/usr/share/doc/selinux-policy/html/interfaces.html文件中的接口索引。

可选政策

每当您使用贡献模块之一中定义的接口时,您必须将其包含在可选策略块中。

例如,Apache 模块使用kerberos 模块中定义的kerberos_read_keytab接口:

optional_policy(`
kerberos_read_keytab(httpd_t)
')

optional_policy块确保您可以在没有可选模块的情况下使用策略模块。在这种情况下, apache模块可以在删除了kerberos模块的系统上使用。如果您省略Optional_policy块,则当您授予其资源访问权限的任何模块丢失时,安装新策略模块会导致错误。

扩展宏

如果名称不言自明, selinux-policy-devel包提供的宏扩展器工具会显示每个宏包含的内容。使用带有完整宏名称(包括所有参数)的Macro-expander来查看宏生成的所有允许规则,例如:

# macro-expander "mysql_read_config(httpd_t)" 
allow httpd_t mysqld_etc_t:dir { getattr search open read lock ioctl };
allow httpd_t mysqld_etc_t:file { open { getattr read ioctl lock } };
allow httpd_t mysqld_etc_t:lnk_file { getattr read };

请注意,宏扩展器列表默认情况下仅允许规则。要查看宏的完整内容,例如 type_transition 规则或属性分配,请使用Macro-expander -M <>并忽略模块头和所有require块:

$ macro-expander -M "domtrans_pattern(<source>, <entrypoint>, <target>)"
module expander 1.0.0;
require {
role system_r;
...
}
allow <source> <entrypoint>:file { getattr open map read execute ioctl execute_no_trans };
allow <source> <target>:process transition;
type_transition <source> <entrypoint>:process <target>;
allow <target> <source>:fd use;
allow <target> <source>:fifo_file { getattr read write append ioctl lock };
allow <target> <source>:process sigchld;

权限集

与其他策略宏不同,权限集不使用任何参数,仅用作通常一起使用的权限集的简写。

示例:

  • rw_file_perms - { 打开 getattr 读写附加 ioctl 锁 }
  • create_dir_perms - { getattr create }
  • append_fifo_file_perms - { getattr 打开附加锁 ioctl }

所有权限集均在obj_perm_sets.spt文件中定义。由于宏扩展器默认仅显示允许规则,因此您必须按如下方式调整命令以扩展独立权限集:

$ macro-expander -M "manage_blk_file_perms" | tail -n 1
{ create open getattr setattr read write append rename link unlink ioctl lock }

在这种情况下,带有-M选项的宏扩展器会生成包含查询的完整模块,您可以忽略模块头和require块。

或者,提供“虚拟”允许规则,例如:

$ macro-expander "allow _ _:_ manage_blk_file_perms"
allow _ _:_ { create open getattr setattr read write append rename link unlink ioctl lock }

缺少接口

如果您想要允许不存在接口的有效访问要求,请使用gen_require语句解决该问题。

在以下示例中, dhcpd策略模块需要基于audit2allow -R命令输出中列出的 AVC 的附加访问权限:

$ audit2allow -R
type=AVC msg=audit(1674838508.590:840): avc: denied { write } for pid=2383 comm="dhcpd" name="bluetooth.conf" dev="vda2" ino=2 scontext=system_u:system_r:dhcpd_t:s0 tcontext=system_u:object_r:bluetooth_conf_t:s0 tclass=file permissive=1

require {
type bluetooth_conf_t;
type dhcpd_t;
class file write;
}

#============= dhcpd_t ==============
allow dhcpd_t bluetooth_conf_t:file write;

不存在用于写入bluetooth_conf_t文件的接口,但快速搜索 selinux-policy Github 存储库上的bluetooth.if文件会显示bluetooth_read_config ,您可以针对此用例进行修改:

interface(`bluetooth_read_config',`
gen_require(`
type bluetooth_conf_t;
')

allow $1 bluetooth_conf_t:file read_file_perms;
')

您可以通过将read_file_perms替换为write_file_perms 1并填写接口参数 ( $1 -> dhcpd_t ) 来修复丢失的访问权限。

您还必须使用optional_policy块包围添加的策略 - 就像您使用蓝牙模块中定义的实际接口一样。

optional_policy(`
gen_require(`
type bluetooth_conf_t;
')

allow dhcpd_t bluetooth_conf_t:file write_file_perms;
')

进程上下文(域)

分配给正在运行的进程的类型通常称为。默认情况下,新进程继承其父进程的上下文。例如,用户在unconfined_t域中运行的 Bash shell 中输入cat命令会导致一个新进程也作为unconfined_t运行。

您可以使用type_transition规则更改此行为,该规则是domtrans_pattern (域转换模式)的一部分:

domtrans_pattern(<source>, <entrypoint>, <target>)

此模式使用type_transition规则定义到<target>域的自动转换,并添加一组允许转换的规则。它通常用作init_daemon_domain(<target>, <entrypoint>)接口的一部分,以促进init_t (init 守护进程域)和新创建的域之间的转换。

文件上下文

新文件继承分配给创建它们的目录的类型。要在不更改应用程序代码的情况下分配自定义类型,请使用filetrans_pattern宏:

filetrans_pattern(<process_domain>, <directory>, <target_type>, <class(es)>, [<name>])

其中:

  • <class(es)> - 规则适用的对象类(文件、目录、套接字等)
  • [ ] - 将规则限制为给定名称的对象(可选)

例如:

filetrans_pattern(httpd_t, var_t, httpd_cache_t, file, "apache_cache")

当以httpd_t身份运行的进程在标记为var_t的目录中创建名为apache_cache的文件时,系统会在生成的文件上设置httpd_cache_t标签。

您可以在更专门的宏中使用filetrans_pattern ,其中<directory>参数是根据特定位置预先填充的,例如:

files_var_filetrans(httpd_t, httpd_cache_t, { file dir })

有关更多详细信息,请参阅《RHEL 7 SELinux 用户和管理员指南》中的文件名转换部分。

默认文件上下文定义

所有策略模块都包含一个文件上下文定义文件<module>.fc ,它确定其资源的默认 SELinux 上下文。此上下文由标记实用程序使用,例如matchpathconRestorecon

每个定义都具有以下格式:

pathname_regexp [file_type] security_context

其中:

  • pathname_regexp - 定义可能采用正则表达式形式的路径名
  • file_type - 表示对象类,可以为空(表示所有对象类),也可以为以下之一:
    • -b - 块设备
    • -c - 字符设备
    • -d - 目录
    • -p - 命名管道 (FIFO)
    • -l - 符号链接
    • -s - 套接字文件
    • ---普通文件
  • security_context - 分配给路径的默认安全上下文

有关更多详细信息,请参阅《RHEL 7 SELinux 用户和管理员指南》中的SELinux 上下文 - 标记文件部分。

CIL 错误故障排除

当您将编译的策略模块加载到系统上时,编译器会将模块代码翻译为通用中间语言 ( CIL )。这意味着加载模块时遇到的大多数错误都会根据策略的 CIL 版本进行报告,如以下简单策略示例所示,该策略的类型定义已存在于系统策略中:

$ sepolicy generate --init /usr/bin/cat

$ echo "type abrt_t;" >> cat.te

$ sudo make -f /usr/share/selinux/devel/Makefile cat.pp
Compiling targeted cat module
Creating targeted cat.pp policy package
rm tmp/cat.mod.fc tmp/cat.mod

$ sudo semodule -i cat.pp
Re-declaration of type abrt_t
Previous declaration of type at /var/lib/selinux/targeted/tmp/modules/400/cat/cil:6
Bad type declaration at /var/lib/selinux/targeted/tmp/modules/400/cat/cil:6
Failed to build AST
semodule: Failed!

由于一些与CIL相关的错误消息不明确,您可能需要检查模块源代码的CIL翻译中的相应行。在本例中,错误与第 6 行有关。使用pp工具将策略二进制文件转换为 CIL,并显示有问题的行:

$ cat cat.pp | /usr/libexec/selinux/hll/pp > cat.cil
$ head -6 cat.cil | tail -1
(type abrt_t)

有关 CIL 及其关键字与源策略语言有何不同的更多信息,请参阅 SELinux 项目 wiki 上的CIL 策略语言部分。

政策示例

通过执行以下示例步骤,您可以为bootupd包创建并测试新的 SELinux 策略。该软件包包含一项现在不受限制的服务。

显示bootupd软件包安装的所有文件:

$ rpm -ql bootupd
/usr/bin/bootupctl
/usr/lib/.build-id
/usr/lib/.build-id/e8
/usr/lib/.build-id/e8/3c00b4a3c33f2858d6730e9ba95d8286ab6dde
/usr/lib/.build-id/e8/3c00b4a3c33f2858d6730e9ba95d8286ab6dde.1
/usr/lib/systemd/system/bootupd.service
/usr/lib/systemd/system/bootupd.socket
/usr/libexec/bootupd
/usr/share/doc/bootupd
/usr/share/doc/bootupd/README.md
/usr/share/licenses/bootupd
/usr/share/licenses/bootupd/LICENSE

在列出的文件中,您可以看到两个单元文件以及两个二进制文件, bootupdbootupctl (远程命令行界面)。您可以忽略其余文件来准备新的 SELinux 策略。

bootupd.socket文件指示该服务使用/var/run/bootupd.sock套接字。由于bootupd服务的单元文件使用 init 系统,因此请将--init参数与sepolicygenerate命令结合使用。根据bootupd.service文件的内容,您知道/usr/libexec/bootupd是服务二进制文件。您使用其余文件(单元和套接字文件)作为--writepath参数的值,以便sepolicy generated命令将它们包含在策略中:

$ sepolicy generate --init /usr/libexec/bootupd -w /usr/lib/systemd/system/bootupd.service /usr/lib/systemd/system/bootupd.socket /var/run/bootupd.sock
Created the following files:
/home/user/bootupd/bootupd.te # Type Enforcement file
/home/user/bootupd/bootupd.if # Interface file
/home/user/bootupd/bootupd.fc # File Contexts file
/home/user/bootupd/bootupd_selinux.spec # Spec file
/home/user/bootupd/bootupd.sh # Setup Script

bootupd.te类型强制文件包含四种新类型:
* bootupd_t - bootupd进程的域
* bootupd_exec_t - bootupd二进制文件类型
* bootupd_var_run_t - /var/run/bootupd.sock套接字的类型
* bootupd_unit_file_t - 两个单元文件的文件类型

init_daemon_domain(bootupd_t, bootupd_exec_t)宏确保当 init 守护进程执行bootupd二进制文件时,生成的进程在bootupd_t域中运行。

生成的模块的其余部分主要是 init 守护进程策略模板,除了授予bootupd_var_run_t访问权限的宏组之外:
* manage_dirs_pattern(bootupd_t, bootupd_var_run_t, bootupd_var_run_t) - 管理标记为bootupd_var_run_t 的目录
* Manage_files_pattern(bootupd_t, bootupd_var_run_t, bootupd_var_run_t) - 管理标记为bootupd_var_run_t 的文件
* manage_lnk_files_pattern(bootupd_t, bootupd_var_run_t, bootupd_var_run_t) - 管理标记为bootupd_var_run_t 的链接文件
* files_pid_filetrans(bootupd_t, bootupd_var_run_t, { dir file lnk_file }) - 如果bootupd_t在标记为var_run_t的目录中创建文件,则生成的文件标记为bootupd_var_run_t

/var/run/bootupd.sock路径不存在,因此sepolicygenerate生成目录、文件和链接文件的访问宏。因为/var/run/bootupd.sock是一个套接字文件,所以您可以删除所有三个manage_*模式。selinux-policy Github 存储库上的file_patterns.spt文件包含多种可供您使用的套接字模式,例如rw_sock_files_pattern

由于套接字文件是由systemd (而不是bootupd )作为使用systemctl start bootupd.socket的结果生成的,因此files_pid_filetrans宏也是多余的。如果您的应用程序二进制文件生成套接字文件,则必须将 { dir file lnk_file } 替换{ sock_file } ,而不是删除带有 files_pid_filetrans` 的行

bootupd.fc文件上下文配置文件还包含提供给sepolicygenerate命令的所有路径:

/usr/lib/systemd/system/bootupd.service         --      gen_context(system_u:object_r:bootupd_unit_file_t,s0)
/usr/lib/systemd/system/bootupd.socket -- gen_context(system_u:object_r:bootupd_unit_file_t,s0)
/usr/libexec/bootupd -- gen_context(system_u:object_r:bootupd_exec_t,s0)
/var/run/bootupd.sock -- gen_context(system_u:object_r:bootupd_var_run_t,s0)

同样, sepolicy generated无法正确识别套接字文件。因此,您必须将对象类从-- (普通文件)更改为-s (套接字文件):

/var/run/bootupd.sock           -s      gen_context(system_u:object_r:bootupd_var_run_t,s0)

有关对象类的完整列表,请参阅默认文件上下文定义部分。

完成此步骤后,新策略将与包匹配。编译并安装它:

$ make -f /usr/share/selinux/devel/Makefile bootupd.pp
Compiling targeted bootupd module
Creating targeted bootupd.pp policy package
rm tmp/bootupd.mod.fc tmp/bootupd.mod

$ sudo semodule -i bootupd.pp

在受新文件上下文定义影响的每个路径上使用Restorecon命令以在系统上应用设置:

$ sudo restorecon -v /usr/libexec/bootupd /usr/lib/systemd/system/bootupd.socket /usr/lib/systemd/system/bootupd.service
Relabeled /usr/libexec/bootupd from system_u:object_r:bin_t:s0 to system_u:object_r:bootupd_exec_t:s0
Relabeled /usr/lib/systemd/system/bootupd.socket from system_u:object_r:systemd_unit_file_t:s0 to system_u:object_r:bootupd_unit_file_t:s0
Relabeled /usr/lib/systemd/system/bootupd.service from system_u:object_r:systemd_unit_file_t:s0 to system_u:object_r:bootupd_unit_file_t:s0

启用完整审核后,运行该服务:

$ sudo systemctl start bootupd.socket

$ sudo systemctl start bootupd

检查文件上下文和服务进程的域。在日志中搜索 AVC:

$ ls -lZ /var/run/bootupd.sock
srw-------. 1 root root system_u:object_r:bootupd_var_run_t:s0 0 Mar 14 10:45 /var/run/bootupd.sock

$ ps -axZ | grep bootup[d]
system_u:system_r:bootupd_t:s0 2508 ? Ss 0:00 /usr/libexec/bootupd daemon -v

$ sudo ausearch -m AVC | tee avc.log
----
time->Tue Mar 14 10:24:55 2023
type=PROCTITLE msg=audit(1678803895.889:613): proctitle=2F7573722F6C6962657865632F626F6F74757064006461656D6F6E002D76
type=SYSCALL msg=audit(1678803895.889:613): arch=c000003e syscall=41 success=yes exit=4 a0=1 a1=80002 a2=0 a3=8080808080808080 items=0 ppid=1 pid=1995 auid=4294967295 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=4294967295 comm="bootupd" exe="/usr/libexec/bootupd" subj=system_u:system_r:bootupd_t:s0 key=(null)
type=AVC msg=audit(1678803895.889:613): avc: denied { create } for pid=1995 comm="bootupd" scontext=system_u:system_r:bootupd_t:s0 tcontext=system_u:system_r:bootupd_t:s0 tclass=unix_dgram_socket permissive=1
----
time->Tue Mar 14 10:24:55 2023
type=PROCTITLE msg=audit(1678803895.889:614): proctitle=2F7573722F6C6962657865632F626F6F74757064006461656D6F6E002D76
type=SYSCALL msg=audit(1678803895.889:614): arch=c000003e syscall=44 success=yes exit=8 a0=4 a1=557718261310 a2=8 a3=4000 items=0 ppid=1 pid=1995 auid=4294967295 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=4294967295 comm="bootupd" exe="/usr/libexec/bootupd" subj=system_u:system_r:bootupd_t:s0 key=(null)
type=AVC msg=audit(1678803895.889:614): avc: denied { sendto } for pid=1995 comm="bootupd" path="/run/systemd/notify" scontext=system_u:system_r:bootupd_t:s0 tcontext=system_u:system_r:kernel_t:s0 tclass=unix_dgram_socket permissive=1
----
time->Tue Mar 14 10:24:55 2023
type=PROCTITLE msg=audit(1674838508.590:840): proctitle=2F7573722F6C6962657865632F626F6F74757064006461656D6F6E002D76
type=PATH msg=audit(1674838508.590:840): item=0 name="/boot/efi" inode=413 dev=fc:02 mode=040755 ouid=0 ogid=0 rdev=00:00 obj=system_u:object_r:boot_t:s0 nametype=NORMAL cap_fp=0 cap_fi=0 cap_fe=0 cap_fver=0 cap_frootid=0
type=CWD msg=audit(1674838508.590:840): cwd="/usr"
type=SYSCALL msg=audit(1674838508.590:840): arch=c000003e syscall=137 success=yes exit=0 a0=7fff3dc6b6a0 a1=7fff3dc6c7c0 a2=9 a3=fff items=1 ppid=1 pid=2383 auid=4294967295 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=4294967295 comm="bootupd" exe="/usr/libexec/bootupd" subj=system_u:system_r:bootupd_t:s0 key=(null)
type=AVC msg=audit(1674838508.590:840): avc: denied { getattr } for pid=2383 comm="bootupd" name="/" dev="vda2" ino=2 scontext=system_u:system_r:bootupd_t:s0 tcontext=system_u:object_r:fs_t:s0 tclass=filesystem permissive=1

即使服务在新域中运行并且套接字文件具有正确的标签,SELinux 仍然报告策略中不允许的三个访问向量。有关 AVC 消息的详细说明,请参阅 RHEL 9 使用 SELinux 文档中审核日志部分中的 SELinux 拒绝

您可以使用audit2allow工具获取新策略中缺少的允许规则的建议

$ audit2allow -i avc.log

#============= bootupd_t ==============
allow bootupd_t fs_t:filesystem getattr;
allow bootupd_t kernel_t:unix_dgram_socket sendto;
allow bootupd_t self:unix_dgram_socket create;

由于您无法编译包含另一个策略模块( fs_tkernel_t )中定义的类型的策略模块,因此您必须对包含必要规则的接口或宏使用带有-R选项的audit2allow

$ audit2allow -R -i avc.log

require {
type bootupd_t;
class unix_dgram_socket create;
}

#============= bootupd_t ==============
allow bootupd_t self:unix_dgram_socket create;
fs_getattr_xattr_fs(bootupd_t)
kernel_dgram_send(bootupd_t)

使用Macro-expander验证建议的接口是否完全涵盖所需的用例后,将它们添加到策略模块中。您还必须将源自贡献模块的任何接口包含在optional_policy块中。首先在宽容模式下部署新策略,并在各种系统配置上测试尽可能多的使用场景。

其他资源


  1. 在 selinux-policy Github 存储库的obj_perm_sets.spt中找到↩︎

Comments