Red Hat Training
A Red Hat training course is available for RHEL 8
Chapter 8. Writing a custom SELinux policy
This section guides you on how to write and use a custom policy that enables you to run your applications confined by SELinux.
8.1. Custom SELinux policies and related tools
An SELinux security policy is a collection of SELinux rules. A policy is a core component of SELinux and is loaded into the kernel by SELinux user-space tools. The kernel enforces the use of an SELinux policy to evaluate access requests on the system. By default, SELinux denies all requests except for requests that correspond to the rules specified in the loaded policy.
Each SELinux policy rule describes an interaction between a process and a system resource:
ALLOW apache_process apache_log:FILE READ;
You can read this example rule as: The Apache process can read its logging file. In this rule, apache_process
and apache_log
are labels. An SELinux security policy assigns labels to processes and defines relations to system resources. This way, a policy maps operating-system entities to the SELinux layer.
SELinux labels are stored as extended attributes of file systems, such as ext2
. You can list them using the getfattr
utility or a ls -Z
command, for example:
$ ls -Z /etc/passwd
system_u:object_r:passwd_file_t:s0 /etc/passwd
Where system_u
is an SELinux user, object_r
is an example of the SELinux role, and passwd_file_t
is an SELinux domain.
The default SELinux policy provided by the selinux-policy
packages contains rules for applications and daemons that are parts of Red Hat Enterprise Linux 8 and are provided by packages in its repositories. Applications not described in a rule in this distribution policy are not confined by SELinux. To change this, you have to modify the policy using a policy module, which contains additional definitions and rules.
In Red Hat Enterprise Linux 8, you can query the installed SELinux policy and generate new policy modules using the sepolicy
tool. Scripts that sepolicy
generates together with the policy modules always contain a command using the restorecon
utility. This utility is a basic tool for fixing labeling problems in a selected part of a file system.
Additional resources
-
sepolicy(8)
andgetfattr(1)
man pages
8.2. Creating and enforcing an SELinux policy for a custom application
This example procedure provides steps for confining a simple daemon by SELinux. Replace the daemon with your custom application and modify the example rule according to the requirements of that application and your security policy.
Prerequisites
-
The
policycoreutils-devel
package and its dependencies are installed on your system.
Procedure
For this example procedure, prepare a simple daemon that opens the
/var/log/messages
file for writing:Create a new file, and open it in a text editor of your choice:
$ vi mydaemon.c
Insert the following code:
#include <unistd.h> #include <stdio.h> FILE *f; int main(void) { while(1) { f = fopen("/var/log/messages","w"); sleep(5); fclose(f); } }
Compile the file:
$ gcc -o mydaemon mydaemon.c
Create a
systemd
unit file for your daemon:$ vi mydaemon.service [Unit] Description=Simple testing daemon [Service] Type=simple ExecStart=/usr/local/bin/mydaemon [Install] WantedBy=multi-user.target
Install and start the daemon:
# cp mydaemon /usr/local/bin/ # cp mydaemon.service /usr/lib/systemd/system # systemctl start mydaemon # systemctl status mydaemon ● mydaemon.service - Simple testing daemon Loaded: loaded (/usr/lib/systemd/system/mydaemon.service; disabled; vendor preset: disabled) Active: active (running) since Sat 2020-05-23 16:56:01 CEST; 19s ago Main PID: 4117 (mydaemon) Tasks: 1 Memory: 148.0K CGroup: /system.slice/mydaemon.service └─4117 /usr/local/bin/mydaemon May 23 16:56:01 localhost.localdomain systemd[1]: Started Simple testing daemon.
Check that the new daemon is not confined by SELinux:
$ ps -efZ | grep mydaemon system_u:system_r:unconfined_service_t:s0 root 4117 1 0 16:56 ? 00:00:00 /usr/local/bin/mydaemon
Generate a custom policy for the daemon:
$ sepolicy generate --init /usr/local/bin/mydaemon Created the following files: /home/example.user/mysepol/mydaemon.te # Type Enforcement file /home/example.user/mysepol/mydaemon.if # Interface file /home/example.user/mysepol/mydaemon.fc # File Contexts file /home/example.user/mysepol/mydaemon_selinux.spec # Spec file /home/example.user/mysepol/mydaemon.sh # Setup Script
Rebuild the system policy with the new policy module using the setup script created by the previous command:
# ./mydaemon.sh Building and Loading Policy + make -f /usr/share/selinux/devel/Makefile mydaemon.pp Compiling targeted mydaemon module Creating targeted mydaemon.pp policy package rm tmp/mydaemon.mod.fc tmp/mydaemon.mod + /usr/sbin/semodule -i mydaemon.pp ...
Note that the setup script relabels the corresponding part of the file system using the
restorecon
command:restorecon -v /usr/local/bin/mydaemon /usr/lib/systemd/system
Restart the daemon, and check that it now runs confined by SELinux:
# systemctl restart mydaemon $ ps -efZ | grep mydaemon system_u:system_r:mydaemon_t:s0 root 8150 1 0 17:18 ? 00:00:00 /usr/local/bin/mydaemon
Because the daemon is now confined by SELinux, SELinux also prevents it from accessing
/var/log/messages
. Display the corresponding denial message:# ausearch -m AVC -ts recent ... type=AVC msg=audit(1590247112.719:5935): avc: denied { open } for pid=8150 comm="mydaemon" path="/var/log/messages" dev="dm-0" ino=2430831 scontext=system_u:system_r:mydaemon_t:s0 tcontext=unconfined_u:object_r:var_log_t:s0 tclass=file permissive=1 ...
You can get additional information also using the
sealert
tool:$ sealert -l "*" SELinux is preventing mydaemon from open access on the file /var/log/messages. Plugin catchall (100. confidence) suggests * If you believe that mydaemon should be allowed open access on the messages file by default. Then you should report this as a bug. You can generate a local policy module to allow this access. Do allow this access for now by executing: # ausearch -c 'mydaemon' --raw | audit2allow -M my-mydaemon # semodule -X 300 -i my-mydaemon.pp Additional Information: Source Context system_u:system_r:mydaemon_t:s0 Target Context unconfined_u:object_r:var_log_t:s0 Target Objects /var/log/messages [ file ] Source mydaemon ...
Use the
audit2allow
tool to suggest changes:$ ausearch -m AVC -ts recent | audit2allow -R require { type mydaemon_t; } #============= mydaemon_t ============== logging_write_generic_logs(mydaemon_t)
Because rules suggested by
audit2allow
can be incorrect for certain cases, use only a part of its output to find the corresponding policy interface:$ grep -r "logging_write_generic_logs" /usr/share/selinux/devel/include/ | grep .if /usr/share/selinux/devel/include/system/logging.if:interface(`logging_write_generic_logs',`
Check the definition of the interface:
$ cat /usr/share/selinux/devel/include/system/logging.if ... interface(`logging_write_generic_logs',` gen_require(` type var_log_t; ') files_search_var($1) allow $1 var_log_t:dir list_dir_perms; write_files_pattern($1, var_log_t, var_log_t) ') ...
In this case, you can use the suggested interface. Add the corresponding rule to your type enforcement file:
$ echo "logging_write_generic_logs(mydaemon_t)" >> mydaemon.te
Alternatively, you can add this rule instead of using the interface:
$ echo "allow mydaemon_t var_log_t:file { open write getattr };" >> mydaemon.te
Reinstall the policy:
# ./mydaemon.sh Building and Loading Policy + make -f /usr/share/selinux/devel/Makefile mydaemon.pp Compiling targeted mydaemon module Creating targeted mydaemon.pp policy package rm tmp/mydaemon.mod.fc tmp/mydaemon.mod + /usr/sbin/semodule -i mydaemon.pp ...
Verification
Check that your application runs confined by SELinux, for example:
$ ps -efZ | grep mydaemon system_u:system_r:mydaemon_t:s0 root 8150 1 0 17:18 ? 00:00:00 /usr/local/bin/mydaemon
Verify that your custom application does not cause any SELinux denials:
# ausearch -m AVC -ts recent <no matches>
Additional resources
-
sepolgen(8)
,ausearch(8)
,audit2allow(1)
,audit2why(1)
,sealert(8)
, andrestorecon(8)
man pages
8.3. Creating a local SELinux policy module
Adding specific SELinux policy modules to an active SELinux policy can fix certain problems with the SELinux policy. You can use this procedure to fix a specific Known Issue described in Red Hat release notes, or to implement a specific Red Hat Solution.
Use only rules provided by Red Hat. Red Hat does not support creating SELinux policy modules with custom rules, because this falls outside of the Production Support Scope of Coverage. If you are not an expert, contact your Red Hat sales representative and request consulting services.
Prerequisites
-
The
setools-console
andaudit
packages for verification.
Procedure
Open a new
.cil
file with a text editor, for example:# vim <local_module>.cil
To keep your local modules better organized, use the
local_
prefix in the names of local SELinux policy modules.Insert the custom rules from a Known Issue or a Red Hat Solution.
ImportantDo not write your own rules. Use only the rules provided in a specific Known Issue or Red Hat Solution.
For example, to implement the SELinux denies cups-lpd read access to cups.sock in RHEL solution, insert the following rule:
NoteThe example solution has been fixed permanently for RHEL in RHBA-2021:4420. Therefore, the parts of this procedure specific to this solution have no effect on updated RHEL 8 and 9 systems, and are included only as examples of syntax.
(allow cupsd_lpd_t cupsd_var_run_t (sock_file (read)))
Note that you can use either of the two SELinux rule syntaxes, Common Intermediate Language (CIL) and m4. For example,
(allow cupsd_lpd_t cupsd_var_run_t (sock_file (read)))
in CIL is equivalent to the following in m4:module local_cupslpd-read-cupssock 1.0; require { type cupsd_var_run_t; type cupsd_lpd_t; class sock_file read; } #============= cupsd_lpd_t ============== allow cupsd_lpd_t cupsd_var_run_t:sock_file read;
- Save and close the file.
Install the policy module:
# semodule -i <local_module>.cil
NoteWhen you want to remove a local policy module which you created by using
semodule -i
, refer to the module name without the.cil
suffix. To remove a local policy module, usesemodule -r <local_module>
.Restart any services related to the rules:
# systemctl restart <service-name>
Verification
List the local modules installed in your SELinux policy:
# semodule -lfull | grep "local_" 400 local_module cil
NoteBecause local modules have priority
400
, you can filter them from the list also by using that value, for example, by using thesemodule -lfull | grep -v ^100
command.Search the SELinux policy for the relevant allow rules:
# sesearch -A --source=<SOURCENAME> --target=<TARGETNAME> --class=<CLASSNAME> --perm=<P1>,<P2>
Where
<SOURCENAME>
is the source SELinux type,<TARGETNAME>
is the target SELinux type,<CLASSNAME>
is the security class or object class name, and<P1>
and<P2>
are the specific permissions of the rule.For example, for the SELinux denies cups-lpd read access to cups.sock in RHEL solution:
# sesearch -A --source=cupsd_lpd_t --target=cupsd_var_run_t --class=sock_file --perm=read allow cupsd_lpd_t cupsd_var_run_t:sock_file { append getattr open read write };
The last line should now include the
read
operation.Verify that the relevant service runs confined by SELinux:
Identify the process related to the relevant service:
$ systemctl status <service-name>
Check the SELinux context of the process listed in the output of the previous command:
$ ps -efZ | grep <process-name>
Verify that the service does not cause any SELinux denials:
# ausearch -m AVC -ts recent <no matches>
Additional resources