Chapter 8. Writing a custom SELinux policy
Chapter 8. Writing a custom SELinux policy To run your applications confined by SELinux, you must write and use a custom policy. 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: 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: 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 RHEL 9 and are provided by packages in its repositories. Applications not described 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 RHEL 9, 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) and getfattr(1) man pages on your system Quick start to write a custom SELinux policy Knowledgebase article 8.2. Creating and enforcing an SELinux policy for a custom application You can confine applications by SELinux to increase the security of host systems and users' data. Because each application has specific requirements, modify this example procedure for creating an SELinux policy that confines a simple daemon according to your use case. Prerequisites The selinux-policy-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: 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: Create a systemd unit file for your daemon: Install and start the daemon: Check that the new daemon is not confined by SELinux: Generate a custom policy for the daemon: Rebuild the system policy with the new policy module using the setup script created by the command: Note that the setup script relabels the corresponding part of the file system using the restorecon command: Restart the daemon, and check that it now runs confined by SELinux: Because the daemon is now confined by SELinux, SELinux also prevents it from accessing /var/log/messages . Display the corresponding denial message: You can get additional information also using the sealert tool: Use the audit2allow tool to suggest changes: Because rules suggested by audit2allow can be incorrect for certain cases, use only a part of its output to find the corresponding policy interface. Inspect the logging_write_generic_logs(mydaemon_t) macro with the macro-expander tool, to see all allow rules the macro provides: In this case, you can use the suggested interface, because it only provides read and write access to log files and their parent directories. Add the corresponding rule to your type enforcement file: Alternatively, you can add this rule instead of using the interface: Reinstall the policy: Verification Check that your application runs confined by SELinux, for example: Verify that your custom application does not cause any SELinux denials: Additional resources sepolgen(8) , ausearch(8) , audit2allow(1) , audit2why(1) , sealert(8) , and restorecon(8) man pages on your system Quick start to write a custom SELinux policy Knowledgebase article 8.3. Additional resources SELinux Policy Workshop (Red Hat | Public Sector)
ALLOW apache_process apache_log:FILE READ; ls -Z /etc/passwd system_u:object_r:passwd_file_t:s0 /etc/passwd vi mydaemon.c #include <unistd.h> #include <stdio.h> FILE *f; int main(void) { while(1) { f = fopen("/var/log/messages","w"); sleep(5); fclose(f); } } gcc -o mydaemon mydaemon.c vi mydaemon.service [Unit] Description=Simple testing daemon [Service] Type=simple ExecStart=/usr/local/bin/mydaemon [Install] WantedBy=multi-user.target 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. 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 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 ./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 restorecon -v /usr/local/bin/mydaemon /usr/lib/systemd/system 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 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 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 ... ausearch -m AVC -ts recent | audit2allow -R require { type mydaemon_t; } #============= mydaemon_t ============== logging_write_generic_logs(mydaemon_t) macro-expander "logging_write_generic_logs(mydaemon_t)" allow mydaemon_t var_t:dir { getattr search open }; allow mydaemon_t var_log_t:dir { getattr search open read lock ioctl }; allow mydaemon_t var_log_t:dir { getattr search open }; allow mydaemon_t var_log_t:file { open { getattr write append lock ioctl } }; allow mydaemon_t var_log_t:dir { getattr search open }; allow mydaemon_t var_log_t:lnk_file { getattr read }; echo "logging_write_generic_logs(mydaemon_t)" >> mydaemon.te echo "allow mydaemon_t var_log_t:file { open write getattr };" >> mydaemon.te ./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 ps -efZ | grep mydaemon system_u:system_r:mydaemon_t:s0 root 8150 1 0 17:18 ? 00:00:00 /usr/local/bin/mydaemon ausearch -m AVC -ts recent <no matches>