Red Hat Training

A Red Hat training course is available for RHEL 8

Chapter 8. Writing a custom SELinux policy

To run your applications confined by SELinux, you must write and use a custom policy.

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

  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");
              sleep(5);
              fclose(f);
          }
      }
    3. Compile the file:

      $ gcc -o mydaemon mydaemon.c
    4. 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
    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/mydaemon.sh # Setup Script
  3. 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
  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.
    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
    …
  7. 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)
  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. Inspect the logging_write_generic_logs(mydaemon_t) macro with the macro-expander tool, to see all allow rules the macro provides:

    $ 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 };
  9. 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:

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

  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

8.3. Additional resources