Portal:SELinux/WritingPolicy

Jump to: navigation, search

Writing Policy

SELinux policy can be written by anyone - even you! There are many tools to assist with this process.

To ensure you are ready to write selinux policy for your application, install `policycoreutils-devel`.

Setup

Let's assume we have a daemon we want to write policy for called "mydaemon".

First change to your work directory where you are happy to create files, and create template policy files.

   cd ~/policy-devel
   sepolicy generate --init /path/to/binary/mydaemon

This will create multiple files for you. The suffixes are

  • fc - filecontexts, which lists the types that should be applied to named file paths
  • if - interfaces, exportable selinux policy interfaces for other modules to use.
  • te - type enforcement, rules that define rules of type interactions.

Examining the fc file that was generated you should see context like:

   /usr/sbin/mydaemon		--	gen_context(system_u:object_r:mydaemon_exec_t,s0)

By convention, the file type for a daemon named X is to name it X_exec_t.

The other important file here is the te file. It's contents will appear like

   policy_module(mydaemon, 1.0.0)
   ########################################
   #
   # Declarations
   #
   type mydaemon_t;
   type mydaemon_exec_t;
   init_daemon_domain(mydaemon_t, mydaemon_exec_t)
   permissive mydaemon_t;
   ########################################
   #
   # mydaemon local policy
   #
   allow mydaemon_t self:fifo_file rw_fifo_file_perms;
   allow mydaemon_t self:unix_stream_socket create_stream_socket_perms;
   domain_use_interactive_fds(mydaemon_t)
   files_read_etc_files(mydaemon_t)
   miscfiles_read_localization(mydaemon_t)

This defines:

  • Type names for new labels.
  • A rule to allow the init daemon to transition a new process from type mydaemon_exec_t to mydaemon_t
  • permissive rule for mydaemon_t to allow the daemon to work as you develop the policy
  • a number of default rules to broadly allow your process to continue to operate.

You can rebuild and install this policy with:

   make -f /usr/share/selinux/devel/Makefile mydaemon.pp
   /usr/sbin/semodule -i mydaemon.pp

This builds and loads the policy module. Now you can relabel your files that were change from your filecontexts with:

   restorecon -v /path/to/binary/mydaemon

If you wish to reset files in a directory

   restorecon -v -r /path/to/dir

It's important you relabel files to correctly enable the interactions for the next phase! If in doubt run:

   restorecon -v -r /

At this point you can now restart your service, and you will be able to see it running in it's new selinux type.

   systemctl restart mydaemon
   # ps auxZ | grep mydaemon
   ...

If the process is not running as mydaemon_t, then you can investigate this with:

   # Can init_t transition to mydaemon_t?
   sepolicy transition -s init_t -t mydaemon_t
   # Is there a denial during the transition?
   ausearch -ts recent -m avc -u system_u:system_r:init_t:s0
   

Investigating Denials

If the process is running as mydaemon_t, or is not, we need to start to investigate and add rules to authorise our daemon to perform the limited actions it requires. Look at recent denials with `ausearch -ts recent -m avc`. An example is:

   type=AVC msg=audit(1687768006.183:92): avc:  denied  { nnp_transition } for  pid=1206 comm="(dm_unixd)" scontext=system_u:system_r:init_t:s0 tcontext=system_u:system_r:kanidm_unixd_t:s0 tclass=process2 permissive=0

This shows a failure for init_t to transition to kanidm_unix_t due to a denial from nnp_transition. This resulted in kanidm_unixd running as init_t instead of kanidm_unix_t.

There are two approaches to resolve this.

First we can attempt to auto-generate rules with audit2allow.

   # ausearch -ts recent -m avc | audit2allow
   allow init_t kanidm_unixd_t:process2 nnp_transition;

While this works, sometimes audit2allow can "allow too much" and the rules it generates are quite coarse. However, it will create a working policy. You can add the rules you are happy with to your .te file and rebuild and install the policy.

The preferred way to write policy however is to consume interfaces from existing policy. These interfaces exist in `/usr/share/selinux/devel` and can be searched with grep or similar. So we can use our keyword `nnp_transition` to locate related policy.

   # grep -i -r -n -e 'nnp_transition' /usr/share/selinux/devel
   /usr/share/selinux/devel/include/contrib/kpatch.if:38:	allow $1 kpatch_t:process2 { nnp_transition nosuid_transition };
   /usr/share/selinux/devel/include/contrib/mozilla.if:289:	allow $1 mozilla_plugin_t:process2 nnp_transition;
   /usr/share/selinux/devel/include/system/init.if:128:	allow init_t $1:process2 { nnp_transition nosuid_transition };
   /usr/share/selinux/devel/include/system/init.if:155:    allow init_t $1:process2 { nnp_transition nosuid_transition };

We can see that policy for kpatch and mozilla here both reference nnp_transition, however they appear to be related to other types transitioning into kpatch and mozilla since allow rules are `allow source dest`. However in init.if we see some possible rules that match our keywords and appear to allow init_t to nnp_transition to a variable. Viewing these files we see an interface called `init_daemon` and another called `init_nnp_daemon_domain`

Ultimately you need to review these interfaces and determine which you want to use in your rules. In this example I chose to use `init_nnp_daemon_domain` which updates our policy to:


   policy_module(mydaemon, 1.0.0)
   ########################################
   #
   # Declarations
   #
   type mydaemon_t;
   type mydaemon_exec_t;
   init_daemon_domain(mydaemon_t, mydaemon_exec_t)
   init_nnp_daemon_domain(mydaemon_t, mydaemon_exec_t)
   permissive mydaemon_t;
   ########################################
   #
   # mydaemon local policy
   #
   allow mydaemon_t self:fifo_file rw_fifo_file_perms;
   allow mydaemon_t self:unix_stream_socket create_stream_socket_perms;
   domain_use_interactive_fds(mydaemon_t)
   files_read_etc_files(mydaemon_t)
   miscfiles_read_localization(mydaemon_t)


Refining The Policy

While audit2allow will work, and the policy above will give us a minimal working confined binary, we want to have stricter rules and types so that our daemon can only access it's resources - and so that other processes are unable to access our daemons resources.

To achieve this we need to label our filesystem resources so that the denials we see are specific to those resources.

First, add the new types we will need to our .te file. There are many "conventions" for how these type names should exist. If in doubt, ls -lZ other files and see what others have done!

   type mydaemon_conf_t;
   files_config_file(mydaemon_conf_t)
   type mydaemon_var_run_t;
   files_type(mydaemon_var_run_t)
   files_pid_filetrans(mydaemon_t, mydaemon_var_run_t, { dir file sock_file })
   type mydaemon_var_cache_t;
   files_type(mydaemon_var_cache_t)

Once these types exist, we need to add file contexts to our fc files. These rules are regexes so be careful!

   /etc/mydaemon(/.*)?                 gen_context(system_u:object_r:mydaemon_conf_t,s0)
   /var/run/mydaemon(/.*)?             gen_context(system_u:object_r:mydaemon_var_run_t,s0)
   /var/cache/mydaemon(/.*)?           gen_context(system_u:object_r:mydaemon_var_cache_t,s0)
   /usr/sbin/mydaemon		    --	    gen_context(system_u:object_r:mydaemon_exec_t,s0)

HINT notice in the rules that apply to directories we remove the --? This allows the context to change other file types such as directories.

Now, we rebuild and reinstall our policy. We also need to restorecon on the file paths that we updated.

   restorecon -r -v /etc/mydaemon /var/run/mydaemon /var/cache/mydaemon

You can now restart your daemon and refine your policy with these new types.

From here you iteratively add rules with audit2allow or manually adding interfaces from searching the policy headers.

You also need to exercise your daemon and it's features in order to find all your areas where denials may occur and how your daemon interacts.

It can be useful to limit your ausearch queries to your daemon:

   ausearch -ts timestamp -su system_u:system_r:mydaemon_t:s0

Investigating Non-Denial Behaviours

Sometimes your policy appears correct but behaviours may not be occuring as you expect. For example a file may not be relabeled correctly, or a type not being used in a process transition.

There are sometimes many subtle edge cases in SELinux that aren't always obvious.

It can be a useful reference to look at other existing policy to see how they handle the same situations. This is especially true of files in locations like `/var/run` or `/etc`.

Finalising your policy

Once you are happy with the policy and have no denials for a period of time, you should remove the line:

   permissive mydaemon_t

Then rebuild and reinstall your policy. If you encounter denials you can temporarily add permissive status to the domain with

   semanage permissive -a mydaemon_t

And you can reset the type to enforcing with

   semanage permissive -d mydaemon_t