Lab: Finish a Custom SELinux Policy Module for a Confined Domain
Estimated reading time: 7 minutes.
- Objective
-
Resolve the latest AVC errors from a custom policy module and ensure that it works in enforcing mode.
All activities in this chapter require the results of the previous activities. You are not required to perform all of them at once, but it’s certainly easier. If you must pause between activities, ensure you can continue using the same RHEL machine. |
Before you Begin
You need a RHEL machine to which you have either root password or unrestricted sudo, and also access to Red Hat Enterprise Linux package repositories, to install any missing packages. That machine must be configured with:
-
SELinux enabled and enforced, using the targeted policy.
-
The
setools-console
package. -
The
policycoreutils-python-utils
package. -
The
selinux-policy-devel
package. -
The
rpm-build
package.
These instructions were tested on RHEL 9.3 but should work with minimal change on older releases (since RHEL 7) and newer releases.
Instructions
-
This activity is a continuation of the previous activity. It requires that the third-party application be already installed as a system service, with a custom policy which runs the application in a permissive domain and allows network connections to remote HTTP servers and access to system files.
1.1. Check that the test application service is still active. It will terminate when it’s done retrieving the weather from all cities, and in that case you need to restart it. Remember to record the timestamp so you can filter audit events.
$ systemctl is-active testapp inactive $ TIME=$(date +%T) ; sudo systemctl restart testapp
1.2. Find the PID of the test application service and check that it’s running in a permissive domain:
$ systemctl show --property MainPID --value testapp 39521 $ ps -Z 39521 LABEL PID TTY STAT TIME COMMAND system_u:system_r:testapp_t:s0 39521 ? S 0:00 /usr/local/sbin/testapp -d $ sudo semanage permissive -l | grep -c testapp_t 1
1.3. If you need, enter the directory with the custom policy module from the previous activity:
$ cd selinux-testapp $ ls noarch testapp.fc testapp.if testapp.pp testapp.sh testapp.te testapp_selinux-1.0-1.el9.src.rpm testapp_selinux.8 testapp_selinux.spec tmp
-
Review what should be the latest AVC error from the third-party application.
2.1. The AVC error relates to another system configuration file:
$ sudo ausearch -m AVC -x /usr/local/sbin/testapp --start $TIME --just-one ---- time->Thu Jul 25 20:01:13 2024 type=PROCTITLE msg=audit(1721937673.666:665): proctitle=2F7573722F6C6F63616C2F7362696E2F74657374617070002D64 type=SYSCALL msg=audit(1721937673.666:665): arch=c000003e syscall=262 success=yes exit=0 a0=ffffff9c a1=7f9e947bbab9 a2=7f9e93cda340 a3=0 items=0 ppid=1 pid=6537 auid=4294967295 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=4294967295 comm="testapp" exe="/usr/local/sbin/testapp" subj=system_u:system_r:testapp_t:s0 key=(null) type=AVC msg=audit(1721937673.666:665): avc: denied { getattr } for pid=6537 comm="testapp" path="/etc/resolv.conf" dev="vda4" ino=67109244 scontext=system_u:system_r:testapp_t:s0 tcontext=system_u:object_r:net_conf_t:s0 tclass=file permissive=1
Did you know that, in Linux, it’s not the operating system, but it’s user applications, that perform DNS searches? All those details are hidden from developers by library code.
2.2. Find an interface that matches the AVC error:
$ sudo ausearch -m AVC -x /usr/local/sbin/testapp --start $TIME --just-one | audit2allow -R ... #============= testapp_t ============== sysnet_read_config(testapp_t)
Remember that you should review the comments on the source of this interface and also expand it to review all allow statements it generates. In the interest of time, let’s move on, knowing this is the expected interface for network clients that perform DNS queries.
2.3. Add the interface to the end of the
testapp.te
file:... logging_send_syslog_msg(testapp_t) miscfiles_read_localization(testapp_t) # Before this line, all rules were auto-generated kernel_read_system_state(testapp_t) miscfiles_read_certs(testapp_t) miscfiles_search_generic_cert_dirs(testapp_t) allow testapp_t self:tcp_socket { connect create getattr getopt setopt }; allow testapp_t self:udp_socket { connect create getattr setopt }; corenet_tcp_connect_http_port(testapp_t) # Before this line, all rules come from the previous activities sysnet_read_config(testapp_t)
-
Verify that the updated policy module fixes all remaining AVC errors from the third-party application.
3.1. Build and reload the policy module:
$ sudo ./testapp.sh Building and Loading Policy + make -f /usr/share/selinux/devel/Makefile testapp.pp Compiling targeted testapp module Creating targeted testapp.pp policy package ... + exit 0
3.3. Restart the test application, recording a timer so you can filter AVC errors from before and after the operation, and check that there are no more AVC errors related to UDP and TCP sockets:
$ TIME=$(date +%T) ; sudo systemctl restart testapp $ sudo ausearch -m AVC -x /usr/local/sbin/testapp --start $TIME | grep -c resolv.conf <no matches> 0
Notice the string "no matches" in the previous output. It means that the
ausearch
command found no audit events matching the search criteria. There was nothing for thegrep
command to search in. -
Remove the permissive flag and perform a final check for no remaining AVCs.
4.1. Delete the following line from the
testapp.te
file:permissive testapp_t;
4.2. Build and reload the policy module one last time:
$ sudo ./testapp.sh Building and Loading Policy + make -f /usr/share/selinux/devel/Makefile testapp.pp Compiling targeted testapp module Creating targeted testapp.pp policy package ... + exit 0
4.3. Restart the test application, recording a timer so you can filter AVC errors from before and after the operation, and check that there are no more AVC errors:
$ TIME=$(date +%T) ; sudo systemctl restart testapp $ sudo ausearch -m AVC -x /usr/local/sbin/testapp --start $TIME <no matches>
4.4. Ensure the test application service is not running in a permissive domain anymore:
$ sudo semanage permissive -l | grep -c testapp_t 0
Good work! Now the third-party applications runs fully confined by SELinux.
-
Review the generated RPM package containing the policy module, for redistribution to other machines running the third-party application.
5.1. The autogenerated script
testapp.sh
already includes commands to build RPM packages and source RPM packages. We are interested in the RPM package:$ ls noarch testapp_selinux-1.0-1.el9.noarch.rpm
5.2. Review the contents of the generated RPM package:
$ rpm -ql -p noarch/testapp_selinux-1.0-1.el9.noarch.rpm /usr/share/man/man8/testapp_selinux.8.gz /usr/share/selinux/devel/include/contrib/testapp.if /usr/share/selinux/packages/testapp.pp
Notice that it includes the binary policy module, its interface definition file (in case it provides reusable interfaces for other modules), and an autogenerated manual page which is not very useful as-is but could be if you add comments to your interface file.
5.3. Review the scriptlets of the generated RPM package:
$ rpm -q --scripts -p noarch/testapp_selinux-1.0-1.el9.noarch.rpm postinstall scriptlet (using /bin/sh): semodule -n -i /usr/share/selinux/packages/testapp.pp if /usr/sbin/selinuxenabled ; then /usr/sbin/load_policy restorecon -R /usr/local/sbin/testapp; restorecon -R /var/run/testapp.pid; fi; exit 0 ...
The postuninstall scriplet was omitted from the previous output, but it just reverses the effects of the postinstall scriptlet: it unloads the binary policy module and relabels all files affected by it.
Next Steps
This is the final activity of this course. For a real-world application you would require more iterations, as you exercise different features of the application and review the AVCs errors it generates, but you would still follow this same process and grow your policy module incrementally.