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.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.


  • The policycoreutils-devel package and its dependencies are installed on your system.


  1. For this example procedure, prepare a simple daemon that opens the /var/log/messages file for writing:

    1. Create a new file, and open it in a text editor of your choice:

      $ vi mydaemon.c
    2. Insert the following code:

      #include <unistd.h>
      #include <stdio.h>
      FILE *f;
      int main(void)
      while(1) {
      f = fopen("/var/log/messages","w");
    3. Compile the file:

      $ gcc -o mydaemon mydaemon.c
    4. Create a systemd unit file for your daemon:

      $ vi mydaemon.service
      Description=Simple testing daemon
    5. 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.
    6. 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
  2. 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/ # Setup Script
  3. Rebuild the system policy with the new policy module using the setup script created by the previous command:

    # ./
    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
  4. 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
  5. 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
  6. 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.
    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
  7. Use the audit2allow tool to suggest changes:

    $ ausearch -m AVC -ts recent | audit2allow -R
    require {
    	type mydaemon_t;
    #============= mydaemon_t ==============
  8. 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
  9. Check the definition of the interface:

    $ cat /usr/share/selinux/devel/include/system/logging.if
                    type var_log_t;
            allow $1 var_log_t:dir list_dir_perms;
            write_files_pattern($1, var_log_t, var_log_t)
  10. 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
  11. Reinstall the policy:

    # ./
    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


  1. 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
  2. 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), and restorecon(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.


  • The setools-console and audit packages for verification.


  1. 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.

  2. Insert the custom rules from a Known Issue or a Red Hat Solution.


    Do 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:


    The 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;
  3. Save and close the file.
  4. Install the policy module:

    # semodule -i <local_module>.cil

    When 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, use semodule -r <local_module>.

  5. Restart any services related to the rules:

    # systemctl restart <service-name>


  1. List the local modules installed in your SELinux policy:

    # semodule -lfull | grep "local_"
    400 local_module  cil

    Because local modules have priority 400, you can filter them from the list also by using that value, for example, by using the semodule -lfull | grep -v ^100 command.

  2. 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.

  3. Verify that the relevant service runs confined by SELinux:

    1. Identify the process related to the relevant service:

      $ systemctl status <service-name>
    2. Check the SELinux context of the process listed in the output of the previous command:

      $ ps -efZ | grep <process-name>
  4. Verify that the service does not cause any SELinux denials:

    # ausearch -m AVC -ts recent
    <no matches>

8.4. Additional resources