Enhancing an SELinux policy with file transitions

Up until now, we've only handled the configuration part on file contexts: if we would ask SELinux utilities to relabel files, then the changes we made would come into effect. However, unless you run with the restorecond daemon checking out all possible file modifications (which would really be a resource hog) or run restorecon manually against all files regularly, the newly defined contexts will not be applied to the files.

What we need to do is modify the local SELinux policy so that, upon creation of these files, the Linux kernel automatically assigns the right label to those files. This is handled through file transitions, which is a specific case of a type transition.

In a type transition, we configure a policy so that if a given domain creates a file (or other resource class) inside a directory with a specified label, then the created object should automatically get a specific label. Policy-wise, this is written as follows:

type_transition <domain> <directory_label>:<resource_class> <specific_label>

SELinux has also added in support for named file transitions (from Linux 2.6.39 onwards, and available in Gentoo, Fedora 16+, and Red Hat Enterprise Linux 7+). In that case, such a transition only occurs if the created resource matches a particular filename exactly (so no regular expressions):

type_transition <domain> <directory_label>:<resource_class> <specific_label> <filename>

Through the reference policy macro's, this is supported with the filetrans_pattern definition.

Getting ready

In order to properly define file transitions, we need to know what the source domain is that is responsible for creating the resource. For instance, a /var/run/snort/ directory might be created by an init script, but if there is no file transition, then this directory will be created with the type of the parent directory (which is var_run_t) instead of the proper type (snort_var_run_t).

So make sure to write down all the involved labels (as an example, we will use initrc_t for an init script, var_run_t for the parent directory, and snort_var_run_t for the target directory) before embarking on this recipe.

How to do it…

Defining a file transition can be done as follows:

  1. Search through the SELinux policies to see if there is an interface that will provide a file transition from a given domain to snort_run_t:
    ~$ sefindif filetrans.*snort_var_run_t
    
  2. Assuming that none have been found, search for interfaces that allow initrc_t created resources to transition to a given type:
    ~$ sefindif filetrans.*initrc_t
    system/init.if: interface(`init_daemon_pid_file',`
    system/init.if: files_pid_filetrans(initrc_t, $1, $2, $3)
    
  3. Bingo! Now, let's create an enhancement for the snort SELinux module (through a mysnort policy file) with the following declaration in it:
    policy_module(mysnort, 0.1)
    gen_require(`
      type snort_t;
      type snort_var_run_t;
    ')
    # If initrc_t creates a directory called "snort" in a var_run_t dir,
    # make sure this one is immediately labeled as snort_var_run_t.
    init_daemon_pid_file(snort_var_run_t, dir, "snort")
  4. Build the new policy and load it. Then check with sesearch if a type transition is indeed declared:
    ~$ sesearch –s initrc_t –t var_run_t –T | grep "snort"
    type_transition initrc_t var_run_t : dir snort_var_run_t "snort"
    

How it works…

Linux distributions that support SELinux already provide an SELinux policy that works in a majority of deployments. The default policy is extensive and works mostly out of the box. If specific changes are needed, chances are that these particular SELinux rules are already defined (as part of policy interfaces) and only need to be instantiated and loaded.

Policy interfaces usually exist in the following two types:

  • Interfaces whose subject is delivered through an argument, and where the object (against which operations are performed) and perhaps target (in our case, to which a transition should occur) are hardcoded
  • Interfaces whose subject is hardcoded and where the object, target, or both are arguments to the interface

An example of the first interface type that can be used in our example would look like the following code:

interface(`snort_generic_pid_filetrans_pid',`
  gen_require(`
    type snort_var_run_t;
  ')
  files_pid_filetrans($1, snort_var_run_t, dir, $2)
')

We could then call this interface like this:

snort_generic_pid_filetrans_pid(initrc_t, "snort")

However, such interfaces would be a burden to maintain. For every daemon support added to the system, the init policy would need to be changed with a named file transition together with the newly added policy rules for the daemon. Considering the amount of daemons that can run on a system, the init policy would literally be filled with a massive amount of named file transitions—at least one for every daemon.

The interface declaration that we encountered in the example is much more manageable. The interface is meant to be called by the daemon policy itself and immediately ensures that the initrc_t type can create directories of the given type (snort_var_run_t) inside the generic run directory (var_run_t). New additions to the policy leave the init policy at rest, making maintenance of the policies easier.

Finding the right search pattern

To find the right pattern, we use the sefindif interface to search through the available interfaces. Finding the right expression is a matter of experience.

As we know, we want to search for file transitions, the line we are looking for will contain filetrans_pattern. Then, one of the arguments involved is the type we are going to transition to (snort_var_run_t). So the expression we used in the example was changed to filetrans.*snort_var_run_t. As that didn't result in anything, the next search involved the domain from which a transition has to be made (initrc_t) so that the expression was changed to filetrans.*initrc_t.

However, let's assume we don't know that filetrans_pattern needs to be searched for. The type itself (snort_var_run_t) or domain (initrc_t) might be sufficient to search through, like in the following searches:

~$ sefindif snort_var_run_t
~$ sefindif initrc_t

From the resulting list of interfaces, we can then see if an interface is available that suits our needs.

Patterns

Patterns such as filetrans_pattern are important supporting definitions inside the reference policy. They bundle a set of permissions related to a functional approach (such as read files, which are handled through a read_files_pattern) and are not tied to a particular type (unlike interfaces).

The need for patterns comes from the very fine-grained access controls that SELinux has on Linux activities. Reading a file is a nice example: it is not sufficient to just allow a type to perform the read action:

allow initrc_t snort_var_run_t:file read;

Most applications first check the attributes of the file (getattr) and open the file before they can read the file. Depending on the purpose, they might also want to lock the file or perform I/O operations on it through ioctl. So instead of just the preceding access vector, the rule was changed to:

allow initrc_t snort_var_run_t:file { getattr lock open read ioctl }

The reference policy provides a single permission set for this called read_file_perms, which turns the access vector into the following:

allow initrc_t snort_var_run_t:file read_file_perms;

Second, the policy developers often want to allow a domain to read a file inside a directory that is labeled similarly. For instance, a snort_var_run_t file can be at /var/run/snort/snort.pid with the /var/run/snort/ directory also being labeled as snort_var_run_t. So we would also need to grant the initrc_t type search rights inside the directory—which again is a set of permissions as can be seen from the search_dir_perms definition:

~$ seshowdef search_dir_perms
define(`search_dir_perms',`{ getattr search open }')

Instead of creating multiple rules for this, a pattern is created, called read_files_pattern, which looks like the following:

~$ seshowdef read_files_pattern
define(`read_files_pattern',`
 allow $1 $2:dir search_dir_perms;
 allow $1 $3:file read_file_perms;
')

This allows policy developers to use a single call:

read_files_pattern(initrc_t, snort_var_run_t, snort_var_run_t)

To see the various patterns supported for policy development, use sefinddef with the 'define.*_pattern' expression:

~$ sefinddef define.*_pattern

Using patterns allows developers to create readable policy rules using a functional approach rather than a full sum-up of each individual access vector.

There's more...

In the snort_generic_pid_filetrans_pid interface presented earlier, we used a named file transition: the transition occurs only if the filename passed on as the last argument matches the filename of the file created.

Named file transitions take precedence over normal file transitions. A good example for this are the file transitions supported for the initrc_t domain:

~# semanage –s initrc_t –t var_run_t –T
Found 2 semantic te rules:
 type_transition initrc_t var_run_t : file initrc_var_run_t;
 type_transition initrc_t var_run_t : dir initrc_var_run_t;
Found 16 named file transition rules:
type_transition initrc_t var_run_t : dir udev_var_run_t "udev";
type_transition initrc_t var_run_t : dir tor_var_run_t "tor";

In this case, if an init script creates a directory called udev or tor (or any of the other transition rules that are not shown in the example), then a proper file transition occurs. If the filename doesn't match, then a transition occurs to the initrc_var_run_t type.

File transitions on regular files and directories are the most common, but transitions can also occur on various other classes, such as sockets, FIFO files, symbolic links, and more.

See also

  • Domain transitions (which assign a different context to a process rather than a file) are covered in Chapter 3, Confining Web Applications in more detail and are used in Chapter 4, Creating a Desktop Application Policy and Chapter 5, Creating a Server Policy