SELinux Cookbook
上QQ阅读APP看书,第一时间看更新

Setting resource-sensitivity labels

When an SELinux policy is MLS-enabled and supports multiple sensitivities (which is not the case with MCS, as MCS only has a single sensitivity), then SELinux can govern information flow and access between a domain and one or more resources based on the clearance of the domain and the sensitivity level of the resource. But even with a single sensitivity (as is the case with MCS), SELinux has additional constraint support to ensure that domains cannot access resources that have one of the categories assigned that the domain doesn't have clearance for.

A sensitivity level consists of a sensitivity (s0 is generally being used for the lowest sensitivity and s15—which is a policy build-time constant and thus can be configured—is the highest sensitivity) together with a category set (which can be a list such as c0,c5,c8.c10).

A security clearance is similar to a sensitivity level but shows a sensitivity range (such as s0-s3) instead of a single sensitivity level. A security clearance can be seen as a range going from the lowest sensitivity level to the highest sensitivity level allowed by the domain.

When policies are being developed for such systems, context definitions and policy rules can take sensitivities into account. In this recipe, we will do the two most common operations for MLS-enabled systems:

  • Define a context with a higher-level sensitivity
  • Set the clearance of a process policy-wise on a domain transition

To accomplish this, we will use the snort intrusion detection system as an example, forcing it to be always executed with the s3 sensitivity and all possible categories.

This example will also show us how to substitute an existing policy rather than enhance it, as we are going to update a definition that would otherwise collide with the existing definition.

How to do it…

To modify an existing domain to support specific sensitivity levels, execute the following steps:

  1. Copy the snort.te and snort.fc files from the distribution policy repository to the local environment:
    ~$ cp ${POLICY_LOCATION}/policy/modules/contrib/snort.* ${DEVROOT}/local
    
  2. Rename the files to mysnort (or customsnort), so we always know this is a customized policy. Don't forget to update the policy_module call in the .te file.
  3. Open the mysnort.te file and look for the init_daemon_domain call. Substitute the call with the following:
    init_ranged_daemon_domain(snort_t, snort_exec_t,  s3:mcs_allcats)
  4. In mysnort.fc, label the snort resources with the s3 sensitivity. For instance, for the snort binary, label it as follows:
    /usr/bin/snort  --  gen_context(system_u:object_r:snort_exec_t,s3)
  5. Build the mysnort policy, remove the currently loaded snort SELinux policy module, and load the mysnort one:
    ~# /etc/init.d/snort stop
    ~# semodule –r snort
    ~# semodule –i mysnort.pp
    
  6. Relabel all files related to snort and then start snort again.

How it works…

There are three important aspects to this recipe:

  1. We replace the entire policy rather than create an enhancement.
  2. We update the policy to use a ranged daemon domain.
  3. We update the file contexts to use the right sensitivity.

The file context update is obvious but the reason for fully replacing the policy might not be.

Full policy replacement

In the example, we copied the existing policy for the snort SELinux module and made the updates in the copy, rather than trying to enhance the policy by creating an additional module.

This is needed because we are making changes to the SELinux policy that are mutually exclusive to the already running SELinux policy. For instance, the file context changes would confuse SELinux as it would then have two fully matching definitions through policy modules, but each with a different resulting context.

In the example, we only copied the type enforcement declarations (snort.te) and file context declarations (snort.fc). If we would copy the interface definitions as well (snort.if), the policy build would give us a warning that there are duplicate interface definitions—the ones provided by the Linux distribution are still on the system after all.

Ranged daemon domain

In the SELinux policy itself, we substituted the init_daemon_domain(snort_t, snort_exec_t) entry with the following:

init_ranged_daemon_domain(snort_t, snort_exec_t, s3:mcs_allcats)

Let's take a look at the contents of this interface:

~$ seshowif init_ranged_daemon_domain
interface(`init_ranged_daemon_domain',`
 gen_require(`
 type initrc_t;
 ')
 init_daemon_domain($1, $2)
 ifdef(`enable_mcs',`
 range_transition initrc_t $2:process $3;
 ')
 ifdef(`enable_mls',`
 range_transition initrc_t $2:process $3;
 mls_rangetrans_target($1)
 ')
')

The newly called interface calls the original init_daemon_domain, but enhances it with MCS- and MLS-related logic. In both cases, it calls range_transition so that when the snort init script (running as initrc_t) transitions to the snort_t domain, then the active sensitivity range is also changed to the third parameter.

In our case, the third parameter is s3:mcs_allcats, where mcs_allcats is a definition that expands to all categories supported by the policy (such as c0.c255 if the policy supports 256 categories).

In case of MLS, it also calls mls_rangetrans_target, which is an interface that sets an attribute to the snort_t domain, which is needed for the MLS constraints enabled in the policy.

From the expanded code, we can see that there are ifdef() statements. These are blocks of SELinux policy rules that are enabled (or ignored) based on build-time parameters. The enable_mcs and enable_mls parameters are set if an MCS or MLS policy is enabled. Other often used build-time parameters are distribution selections (such as distro_redhat if the SELinux policy rules are specific for Red Hat Enterprise Linux and Fedora systems) and enable_ubac (which is when user-based access control is enabled).

Constraints

Most, if not all, SELinux policy development focuses on type enforcement rules and context definitions. SELinux does support various other statements, one of which is the constrain statement used to implement constraints.

A constraint restricts permissions further based on a set of expressions that cover not only the type of the object or subject, but also SELinux role and SELinux user. The constraint that is related to the mlsrangetrans attribute (which is set by the mls_rangetrans_target interface) looks like the following:

mlsconstrain process transition
  (( h1 dom h2 ) and
   (( l1 eq l2 ) or ( t1 == mlsprocsetsl ) or
    (( t1 == privrangetrans ) and ( t2 == mlsrangetrans ))));

The constraint tells us the following things about a transition:

  • The transition can occur only when the highest sensitivity level of the subject (domain/actor) dominates the highest sensitivity level of the object
  • The lowest sensitivity level of the subject is the same as the lowest sensitivity level of the object
  • If not, then the type of the subject has to have the mlsprocsetsl attribute set
  • If not, then both of the following statements have to be true:
    • The type of the subject has the privrangetrans attribute set
    • The type of the object has the mlsrangetrans attribute set

Domination means that the sensitivity level of the first security level is equal to or higher than the sensitivity level of the second security level, and the categories of the first security level are the same or a superset of the categories of the second security level.

Constraints in the SELinux policy are part of the base policy set—this means that we are not able to add constraints through loadable SELinux policies. If we want to include additional constraints, we would need to build the entire policy ourselves, patching the constraints, mls, and mcs files inside the policy repository's policy/ subdirectory.

Knowing about constraints is important, but we probably never need to write constraints ourselves.

See also

The SELinux project site is a good start for learning about constraints and their related statements: