Project:SELinux/Development

From Gentoo Wiki
Jump to:navigation Jump to:search

When planning to help Gentoo Hardened in the development of SELinux policies, or when trying to debug existing policies, this document should help you get acquainted with the necessary resources, trips and tricks to get along.

Introduction

About this document...

Dealing with Mandatory Access Control is never easy. SELinux might be available by default with Linux, enabling it can provide serious headaches - let alone developing policies for it. Within Gentoo Hardened, we strive to offer a default policy that is flexible enough to match the requirements of most of you (our users) yet remain manageable by the limited number of developers that we have. To ensure that the policy we offer is up to date, we definitely need help from end users and other developers, because developing policies requires intimate knowledge of the products they are written for. With over several thousand packages, this is just not feasible for a handful of us. Hence, this Gentoo Hardened SELinux Development guide.

Within this document, we will try to explain how to set up an environment ready to build policies yourself and provide patches to Gentoo Hardened. We also cover how to deal with malfunctioning domains and even how to create your own, new domains from scratch (if we need to). Further down, we give an overview of the guidelines that we try to follow during the policy developments and finally talk about how to properly create patches and submit them to our bugzilla service.

For those who want to run Gentoo Hardened with their own policies, we've also added a chapter on just that. We know that our policy does not match everyone's requirements, so we definitely want to help you run your own too.

Intended audience

This document is a must-read for everyone willing to provide patches or develop the Gentoo Hardened SELinux policies.

Other SELinux advanced users might find this document interesting as well.

What you need to know

This document does assume prior knowledge on SELinux policies and the way the reference policy works. For those that need a quick recap, here are the highlights...

  • SELinux uses domains and types to differentiate its various security objects. A domain is usually referred to as the security context of a process (or group of processes) whereas a type is usually referred to as the label given to a particular resource (file, directory, network interface, socket, network port, ...).
  • SELinux policies describe what interaction is allowed between a domain and the other domains and types it needs to work with. If no policy allows for a particular activity, then the activity is denied.
  • The structure in which policies are written are called SELinux policy modules which contain three parts: a type enforcement file (with suffix .te ) that contains the intra-module permissions, an interface file (with suffix .if ) that contains the inter-module permissions and a file contexts file (with suffix .fc ) that contains the file context definitions for all file resources that are labeled with the type or types defined in the module
  • Inter-domain privileges must be declared through functions in the interface file which can then be called by other modules. This includes the necessary permissions to allow domain transitions
  • SELinux uses attributes to make multiple domains manageable. Domains can have certain permissions against all domains or types that are given a particular attribute. Be aware of this when you start assigning attributes to your own types or domains.

Setting Up Your Environment

Patching the reference policy

Gentoo Hardened builds its policy upon the reference policy as provided by Tresys and managed through a reasonably active community. For Gentoo, the policy is based on a git repository which you can clone and work on. This repository is started with a fresh reference policy snapshot and then updated accordingly. When the reference policy makes a new release, we will rebuild the changes on top of the new release. Although this might be resource intensive, it gives us reasonable assurance that our repository doesn't deviate too much from the reference policy, and it also urges developers to send patches upstream as soon as possible.

Let's create the workplace in which you can develop policies:

As result, you now have a directory called hardened-refpolicy in which you'll find the policies ( policy/modules ) currently in use by Gentoo Hardened. Changes in this repository are reflected in patches loaded into a patchbundle-selinux-base-policy-<version> . We currently generate (support) two types of patches:

  1. The majority of patches are directly loaded into an archive (called patchbundle-selinux-base-policy-<version> ). This archive of patches is applied to the reference policy download (used by our ebuilds) to get it to the state of Gentoo's policy repository.
  2. When a patch is needed on the permissions inside a .te file, or on the context inside a .fc file, but no immediate release of a new patchbundle is scheduled, the patch might be applied to a single ebuild. Unlike the patches for the patchbundle, which are relative to the root of the repository, patches on a single module are taken inside the policy/modules location.

You don't need to do anything specific for these - the Gentoo developers will update any diffs or patches they receive to be compliant to the above. But at least you now know why some developers might have edited your patch before committing ;-)

Navigating the policy workspace

The main location you will work with is hardened-refpolicy/policy/modules . This location is subdivided in categories:

admin Core administrative SELinux policy modules (sudo, bootloader, usermanage, ...)
contrib Non-core (most often application) SELinux policy modules (evolution, mozilla, screen, ...)
kernel Kernel specific SELinux policy domains (corenetwork, kernel, ...)
roles Domains specific to SELinux roles (sysadm, user, staff, ...)
services Core daemon SELinux policy modules (postgresql, ssh, xserver, ...)
system Core SELinux system policy modules (selinuxutil, mount, iptables, ...)

The categorization is arbitrary and serves little purpose other than keeping the modules a bit separated. Each module must have a unique name, regardless of the category! The contrib/ location has a specific meaning when committing patches to the reference policy as it is a subrepository. However, for Gentoo's hardened-refpolicy this isn't the case.

Inside the categories, the modules are available using their three files

user $cd hardened-refpolicy/policy/modules/admin
user $ls sudo.*
sudo.fc    sudo.if     sudo.te

Building a module

To build a module, go to the location where the module code is. You might want to make a copy and work in the copy as the following step will add files and perhaps even directories in the work location. If you don't, then using .gitignore can help you not commit any unneeded files.

Next, run make with the development Makefile offered by your installation.

Note
You can ignore warnings about duplicate interface definitions and such. That is because the Makefile will include both the existing interfaces as well as the current working directory - which of course contains the same interfaces.
user $cd hardened-refpolicy/policy/modules/admin
user $make -f /usr/share/selinux/strict/include/Makefile sudo.pp

You now have a sudo.pp file available which you can load (using semodule -i sudo.pp ).

Note
In the above case, we used the Makefile as provided by our own installation. This Makefile is the same as in hardened-refpolicy/support/Makefile.devel.

Building the base policy

If you want to build the base policy, run make base .

user $cd dev/hardened/refpolicy.local
user $make base

The result should be a base.pp file that you can load using semodule -b base.pp . However, if you intend to do a bit more than just test this base policy quickly, it is seriously recommended to create your own Gentoo overlay for your own selinux-base-policy and install that one as installing a base policy is not only about the policy module itself, but also about the include files that will then be stored in /usr/share/selinux/strict/include .

A Domain Does Not Function Properly

Introduction

The most likely problem that you are hitting is that a domain does exist in Gentoo Hardened SELinux, but that it isn't functioning as it should. To solve this problem, it is adviseable to use the following sequence of investigations:

  1. Is it really SELinux that is restraining your system?
  2. Is the problem related to wrong resource labels / security contexts?
  3. Is the problem related to intra-module permissions?
  4. Is the problem related to inter-module permissions?

Check if SELinux is to blame

Make sure that the problem you are seeing is a SELinux-triggered problem. An easy way to find out is to run SELinux in permissive mode and try again:

root #setenforce 0

This only works if the problem is not to do with a SELinux-aware application (unlike init or sudo which are linked to the libselinux library). SELinux-aware applications might alter their behavior if SELinux is set on the system regardless of it running in permissive mode or not. A prime example is vixie-cron (as can be seen in bug #257111 ). But for applications that are not SELinux aware, this is the easiest method to find out if SELinux is to blame or not.

If running your system in permissive mode works around the problem, read on. If it doesn't, check the regular permissions ( strace 'ing the application might be a good idea too) as the problem is most likely not SELinux related.

Get the proper AVC denials

Assuming that we now know that SELinux is to blame, we need to make sure that we get the proper AVC denials. Either locate the proper denials in /var/log/avc.log (or audit.log ) around the time that you encountered the issue, or run tail -f /var/log/avc.log and reproduce the problem.

root #tail -f /var/log/avc.log
Apr 22 15:03:33 www1 kernel: [16053.303739] type=1400 audit(1303477413.188:283):
avc:  denied  { dac_read_search } for  pid=21758 comm="rm" capability=2
scontext=root:sysadm_r:portage_t tcontext=root:sysadm_r:portage_t
tclass=capability

Analyzing the meaning of the AVC denial is covered by Reading Audit Logs in the Gentoo Hardened SELinux handbook. The denial should give you a pointer where to look for. However, it is possible that no denial is occurring, or at least no relevant ones.

SELinux supports a dontaudit statement, which tells the policy not to log a denial if it occurs. These statements are used by SELinux policy developers when they believe a privilege is attempted by software which isn't needed. To make sure users aren't confused, these denials are hidden from regular sight.

You can disable all dontaudit statements easily though:

root #semodule -D -B

Retry getting the proper AVC denials.

The moment you get the denials you are looking for, isolate them and then undo the changes you made earlier (to ensure that your audit or avc log isn't filled with these cosmetic denials):

root #semodule -B

If you still do not see any denials, then check out the dmesg output for other problems. It is possible that SELinux is not even getting to the point of the policy, which you will not notice by looking at the AVC denials alone. However, the chance of this to happen is very slim - most of the time, you'll find the AVC denials you are looking for.

Deducing the correct security contexts

The next step is to see if we are dealing with the right security contexts. This does require a bit of insight in how both the application (that is failing) and the policy relate to each other. In essence, you want to make sure that the process is running in the right domain and is trying to work on the right target type.

Say you are having issues with SELinux (re)labeling and you notice the following AVC denial:

CODE AVC denial for setfiles
Apr 16 14:39:57 testsys kernel: [  115.778484] type=1400
audit(1302957597.827:224): avc:  denied  { create } for  pid=3584
comm="setfiles" scontext=root:sysadm_r:sysadm_t tcontext=root:sysadm_r:sysadm_t
tclass=netlink_audit_socket

In this case, setfiles is running in the sysadm_t domain even though it should run in setfiles_t . So check the security context of the setfiles binary as well as the transition rules:

root #ls -lZ /sbin/setfiles
-rwxr-xr-x. 1 root root system_u:object_r:bin_t 26464 Apr  9 22:22 /sbin/setfiles
root #sesearch -s sysadm_t -t setfiles_t -c process -p transition -A -d
Found 1 semantic av rules:
    allow sysadm_t setfiles_t : process transition ;
root #sesearch -s sysadm_t -t setfiles_exec_t -c file -p execute -A -d
root #sesearch -s setfiles_t -t setfiles_exec_t -c file -p entrypoint -A -d

In the above (forced) situation, the problem is with the security context of the binary - it should have been setfiles_exec_t instead of bin_t . Usually, entry points are named similarly (like portage_exec_t or sudo_exec_t ). If you are not certain about which domain it should be, use sesearch

root #sesearch -s setfiles_t -c file -p entrypoint -A -d
Found 1 semantic av rules:
   allow setfiles_t setfiles_exec_t : file { ioctl ... execute entrypoint open } ;

The sesearch utility is extremely powerful to query the SELinux policy (which is currently in memory). I also advise you to use the -C switch to see which rules are trigged by certain SELinux booleans:

root #sesearch -s named_t -t named_zone_t -c file -A -d -C
Found 2 semantic av rules:
   allow named_t named_zone_t : file { ioctl read getattr lock open } ; 
DT allow named_t named_zone_t : file { ioctl read write create getattr setattr lock append unlink link rename open } ; [ named_write_master_zones ]

In the above example, the named_t domain only has write privileges on files labeled named_zone_t if the named_write_master_zones boolean is set (which it currently isn't, otherwise the line would stat with ET instead of DT).

To gain a bit of insight in the various, available domains, use seinfo :

root #seinfo -t | grep named
   named_var_run_t
   named_checkconf_exec_t
   named_conf_t
   named_initrc_exec_t
   named_log_t
   named_exec_t
   named_zone_t
   named_t
   named_cache_t
   named_tmp_t

To gain a bit of insight in the (current) file context rules, use semanage :

root #semanage fcontext -l | grep named
/etc/bind(/.*)?                                    all files        system_u:object_r:named_zone_t 
/etc/bind/named\.conf                              regular file     system_u:object_r:named_conf_t 
/etc/rc\.d/init\.d/named                           regular file     system_u:object_r:named_initrc_exec_t 
/etc/rc\.d/init\.d/unbound                         regular file     system_u:object_r:named_initrc_exec_t 
/etc/rndc.*                                        regular file     system_u:object_r:named_conf_t 
/etc/unbound(/.*)?                                 all files        system_u:object_r:named_conf_t 
/usr/sbin/lwresd                                   regular file     system_u:object_r:named_exec_t 
/usr/sbin/named                                    regular file     system_u:object_r:named_exec_t 
/usr/sbin/named-checkconf                          regular file     system_u:object_r:named_checkconf_exec_t 
/usr/sbin/unbound                                  regular file     system_u:object_r:named_exec_t 
/var/bind(/.*)?                                    all files        system_u:object_r:named_cache_t 
/var/bind/pri(/.*)?                                all files        system_u:object_r:named_zone_t 
/var/log/named.*                                   regular file     system_u:object_r:named_log_t 
/var/run/bind(/.*)?                                all files        system_u:object_r:named_var_run_t 
/var/run/named(/.*)?                               all files        system_u:object_r:named_var_run_t 
/var/run/ndc                                       socket           system_u:object_r:named_var_run_t 
/var/run/unbound(/.*)?                             all files        system_u:object_r:named_var_run_t 

Most of the time, fixing domain issues is a matter of relabeling files (or updating the configuration to match the contexts already defined - both work).

Intra-module permissions are missing

It is possible that you get a denial between correct security contexts, but that the permission is just never granted. In this case, you can choose between two things:

  1. Enhance the module so that the particular permission is granted, or
  2. Enhance the module with an additional type where the permission is granted, and assign this type/label to the related resources

In both cases you will need to edit the module files (most likely the .te file), build the module, load it, perhaps even relabel the files or the package and retry. It is also a good idea to take a look at upstream (latest refpolicy repository or the repositories of Fedora and co) and see if they have already solved this problem or not.

Granting additional permissions between existing domains is the easiest, but might introduce additional problems: if this permission is only needed in a particular case yet you grant it for all files and resources related to those domains, then you are opening up the policy beyond what is necessary. Often, creating an additional domain or type can be beneficial.

A noticeable example is Portage' support for CVS/SVN/GIT ebuilds (the so-called live ebuilds). These ebuilds get their repository and store it in the distfiles/svn+src location, which was by default labeled portage_ebuild_t with only read-access for the portage_sandbox_t domain. However, with those live ebuilds, the portage_sandbox_t domain also needs write privileges to this location. Rather than allowing portage_sandbox_t write privileges to portage_ebuild_t , a new type was created called portage_svnsrc_t for just this location and the rights are copied and enhanced on this additional type.

Inter-module permissions are needed

If the solution for the problem requires permissions between modules, then you need to create the proper interface functions in the target domain and call these functions from the source domain.

Interface functions are the APIs that a module provides towards other SELinux modules when they need to interact with the domains. For instance, the mysql module provides, amongst other functions, the mysql_stream_connect interface:

CODE mysql_stream_connect interface
########################################
## <summary>
##      Connect to MySQL using a unix domain stream socket.
## </summary>
## <param name="domain">
##      <summary>
##      Domain allowed access.
##      </summary>
## </param>
## <rolecap/>
#
interface(`mysql_stream_connect',`
        gen_require(`
                type mysqld_t, mysqld_var_run_t, mysqld_db_t;
        ')
  
        stream_connect_pattern($1, mysqld_var_run_t, mysqld_var_run_t, mysqld_t)
        stream_connect_pattern($1, mysqld_db_t, mysqld_var_run_t, mysqld_t)
')

The interface declares that the domain passed on as its first (and only) argument gets the rights offered by stream_connect_pattern , which is a macro (defined in policy/support/ipc_patterns.spt ) that looks like so:

CODE stream_connect_pattern
define(`stream_connect_pattern',`
        allow $1 $2:dir search_dir_perms;
        allow $1 $3:sock_file write_sock_file_perms;
        allow $1 $4:unix_stream_socket connectto;
')

Modules that need to interact with MySQL through a Unix domain stream socket ( /var/run/mysqld/mysqld.sock ) will need the proper permissions to work with the target type ( mysqld_var_run_t ). Modules cannot just set allow statements towards mysqld_var_run_t as they do not know this type. Instead, they call the mysql_stream_connect interface, like the postfix.te file does:

CODE Postfix module calling mysql_stream_connect
optional_policy(`
        mysql_stream_connect(postfix_master_t)
        mysql_stream_connect(postfix_cleanup_t)
        mysql_stream_connect(postfix_local_t)
')

If the change you need is adding statements (towards existing interface calls) in the .te file, then you should be able to test it easily by building the changed module and loading it. However, if you need to change the interface of your (or another) module itself (in the .if file) you will eventually need to rebuild the base policy and even provide and install a new sec-policy/selinux-base-policy package as this package is responsible for placing the interfaces in /usr/share/selinux/strict/include .

This is one of the reasons why the sec-policy/selinux-base-policy package in Gentoo Hardened is often updated. Updates on existing interface files (i.e. for already existing modules) always need to go through this package.

No Domain Exists (Yet)

Reuse existing domains

If you are facing problems because you run an application which has no domain itself (and hence is probably running in the user_t , staff_t or sysadm_t domains - or even tries to run in the initrc_t domain), you will need to create one. But before we do that, it might be possible that the application can work within the domain definition of a different application.

One example here is lighttpd. This lightweight HTTPd service "uses" the definitions offered by the apache module. By marking its executable file httpd_exec_t it runs in the httpd_t domain and uses the same policy like Apache. By labeling the files according to the apache.fc definitions (but now for lighttpd) it might Just Work (TM).

Reusing existing domains requires that you at least consider the following aspects:

  • Will the application run on the same system as the application for which the domain is originally intended? If so, then both might run in the same domain (and as such have more privileges towards each other than intended) which might not be what you want.
  • Do you need to enhance (read: add additional privileges) the master domain? If so, make sure that you don't add more privileges than the original domain would ever need to the extend that these privileges become a security risk.

(Do Not) Copy from existing domains

If reusing existing domains introduces too many risks, you'll need to create a new domain for the application. Many people would be inclined to copy the domain definition of a similar application and work from there. Although this is a viable approach, it is considered a bad practice because you start by providing privileges to the domain that are never needed, and removing privileges from a domain later is very difficult. Even more, if you are not the author of the modules, most developers will not even try to remove them as they assume that the author of the domain had a good reason to add it in the first place. This is one of the reasons why upstream takes great care in accepting patches - they must be properly documented before they are accepted.

Instead, create a domain from scratch but take a close eye on the domain you belief is very similar. Issues that arise during the module development might be quickly resolved by looking at how the original domain is defined.

Starting from scratch

To start the development of a new module from scratch, first identify the domain(s) you want to have. An application that, in its entire lifespan only constitutes of a single process, will most likely only have one domain. For instance, the Skype client will have just skype_t . However, applications that have multiple processes running might need multiple domains too. For instance, the Postfix application runs a master ( postfix_master_t ), queue manager ( postfix_qmgr_t ) and pickup service ( postfix_pickup_t ), but depending on the commands you execute, it will also have (short-lived) processes running as postfix_cleanup_t , postfix_bounce_t , etc.) It is considered a best practice to start with a fine-grained model for domains and only later decide if merging multiple domains into one is beneficial. Splitting domains later is more difficult. Don't forget to look at the client-side aspect too!

Next, define the types that each domain interacts with. This of course includes the binary (like skype_exec_t ) but do not forget resources like

  • The configuration file(s) in /etc (f.i. postfix_etc_t )
  • PID files (f.i. sshd_var_run_t )
  • Spool files (f.i. postfix_spool_t )
  • Variable data files (f.i. snmpd_var_lib_t )
  • Log files (f.i. zebra_log_t )
  • Cache files (f.i. squid_cache_t )
  • (User) content files (f.i. httpd_sys_content_t and httpd_user_content_t )

Also, try to separate types that are used by other domains as well. This way, the other domains can only interact with those files or resources that are labeled accordingly, rather than interact with a broad spectrum of files. The distinction that the apache module makes between system-provided content (like phpmyadmin files) and user-provided content (in the public_html directory in the users' home directories) seems (and is) very logical, but one could wrongly say that for Apache itself, the access controls are the same. Although that might be true, both types are clearly used in different ways so this mandates the use of different domains.

Once you have defined those types too, start writing down the intra-domain permissions. Right now is a good time to look at other modules to see how they do things. Start with defining the accesses towards the domains.

CODE Snippet from the spamassassin module
type spamassassin_t;
type spamassassin_exec_t;
application_domain(spamassassin_t, spamassassin_exec_t)
ubac_constrained(spamassassin_t)

This small snippet defines many things. The first two lines just mention the new types (the spamassassin_t domain and spamassassin_exec_t type). The application_domain interface marks spamassassin_t as an application domain type (it gets the application_domain_type and domain attributes and a few default permissions (like allowing that it sends SIGCHLD and SIGNULL to init). It also marks spamassassin_exec_t as an applications' executable type ( application_exec_type and exec_type attributes) so that it can be executed by regular users (these domains have execute rights against all resources that have the application_exec_type attribute set. Finally, it marks the spamassassin_t domain as a constrained domain for user-based access controls. In other words, if SELinux users user_u and staff_u launch the application in spamassassin_t domains, then the domains are segregated from each other (the intra-domain rules inside spamassassin_t are only valid for communication within the same SELinux user, not between SELinux users).

Attributes are an important aspect in SELinux policy development. They make managing the domains easier, but you should always consider the implications when you add an attribute to one of your types. It usually means that a whole lot of permissions are suddenly granted between other domains and yours.

Next, set the proper intra-domain permissions. For instance, allow your domain to read its configuration files as well as more access inside its own /var/lib location:

CODE Snippet from openca module
allow openca_ca_t openca_etc_t:file read_file_perms;
allow openca_ca_t openca_etc_t:dir list_dir_perms;
  
manage_dirs_pattern(openca_ca_t, openca_var_lib_t, openca_var_lib_t)
manage_files_pattern(openca_ca_t, openca_var_lib_t, openca_var_lib_t)

The majority of work in developing SELinux policy modules is using and choosing the right interfaces. Having a few functions available to browse through all the available information is always interesting, so you might want to use the following function definitions (definitely not mandatory - this is only to help people skim through the policy definitions):

CODE SELinux policy development function definitions
POLICY_LOCATION="/path/to/your/refpolicy";
  
# sefindif - Find interface definitions that have a string that matches the
# given regular expression
sefindif() {
  REGEXP="$1";
  pushd ${POLICY_LOCATION}/policy/modules > /dev/null 2>&1;
  for FILE in */*.if;
  do
    awk "/(interface\(|template\()/ { NAME=\$NF; P=0 }; /${REGEXP}/ { if (P==0) {P=1; print NAME}; print };" ${FILE} | sed -e "s:^:${FILE}\: :g";
  done
  popd > /dev/null 2>&1;
}
  
# seshowif - Show the interface definition
seshowif() {
  INTERFACE="$1";
  pushd ${POLICY_LOCATION}/policy/modules > /dev/null 2>&1;
  for FILE in */*.if;
  do
    grep -A 9999 "\(interface(\`${INTERFACE}'\|template(\`${INTERFACE}'\)" ${FILE} | grep -B 9999 -m 1 "^')";
  done
  popd > /dev/null 2>&1;
}
  
# sefinddef - Find macro definitions that have a string that matches the given
# regular expression
sefinddef() {
  REGEXP="$1";
  grep -H "define(\`.*${REGEXP}.*" ${POLICY_LOCATION}/policy/support/* | sed -e 's:.*\/\([^(]*\):\1:g'
}
  
# seshowdef - Show the macro definition
seshowdef() {
  MACRONAME="$1";
  pushd ${POLICY_LOCATION}/policy/support > /dev/null 2>&1;
  for FILE in *.spt;
  do
    grep -A 9999 "define(\`${MACRONAME}'" ${FILE} | grep -B 999 -m 1 "')";
  done
  popd > /dev/null 2>&1;
}

These functions can then be used to find the information / interfaces you are looking for. Perhaps you need the application to read the postfix configuration files, so look for potential interfaces that use the postfix_etc_t type:

user $sefindif postfix_etc_t
services/postfix.if: template(`postfix_domain_template',`
services/postfix.if:    allow postfix_$1_t postfix_etc_t:dir list_dir_perms;
services/postfix.if:    read_files_pattern(postfix_$1_t, postfix_etc_t, postfix_etc_t)
services/postfix.if:    read_lnk_files_pattern(postfix_$1_t, postfix_etc_t, postfix_etc_t)
services/postfix.if: interface(`postfix_read_config',`
services/postfix.if:            type postfix_etc_t;
services/postfix.if:    read_files_pattern($1, postfix_etc_t, postfix_etc_t)
services/postfix.if:    read_lnk_files_pattern($1, postfix_etc_t, postfix_etc_t)
services/postfix.if: interface(`postfix_config_filetrans',`
services/postfix.if:            type postfix_etc_t;
services/postfix.if:    filetrans_pattern($1, postfix_etc_t, $2, $3)
user $seshowif postfix_read_config
interface(`postfix_read_config',`
        gen_require(`
                type postfix_etc_t;
        ')
  
        read_files_pattern($1, postfix_etc_t, postfix_etc_t)
        read_lnk_files_pattern($1, postfix_etc_t, postfix_etc_t)
        files_search_etc($1)
')

Same thing if you want to look for the correct macro definition (usually, if you notice something but you cannot find it as an interface, then it is most likely a macro):

Suppose you need to read, write, connect, ... to a socket:

user $sefinddef connect
ipc_patterns.spt:define(`stream_connect_pattern',`
obj_perm_sets.spt:define(`rw_socket_perms', `{ ioctl read getattr write setattr append bind connect getopt setopt shutdown }')
obj_perm_sets.spt:define(`connected_socket_perms', `{ create ioctl read getattr write setattr append bind getopt setopt shutdown }')
obj_perm_sets.spt:define(`connected_stream_socket_perms', `{ connected_socket_perms listen accept }')

To see what the ps_process_pattern is about:

user $seshowdef ps_process_pattern
define(`ps_process_pattern',`
        allow $1 $2:dir list_dir_perms;
        allow $1 $2:file read_file_perms;
        allow $1 $2:lnk_file read_lnk_file_perms;
        allow $1 $2:process getattr;
')

As we strive to bring most of our patches upstream, please do consider the contribution guidelines of the reference policy project. The project has a documented style guide, naming convention and an online API reference (for the various interfaces).

Note that, the moment you create a new module, you'll need to create the proper role interfaces (if it is an application that is directly called from a user domain). Take a look at tvtime_role and how it is used in the staff.te and sysadm.te role definitions.

Testing new modules

When you test your application, test it in as many ways as possible. If your application is a command-line application, run it both from a regular terminal (tty) as well as a virtual one (in an xterm). See if it still works if you run it in a screen session. Try out all functions and features that the application supports.

This rigorous testing is necessary because SELinux denies everything that isn't explicitly allowed. If you do not test certain features, chances are that the module does not provide the necessary permissions and as such, users will be impacted.

To test out a new module, load it ( semodule -i modulename.pp ) and relabel the files affiliated with the application (either through rlpkg or using restorecon ). Consider the following testing activities if applicable (not all domains are interactive domains, so please read the activities with your domain definition in mind):

  • Sending signals to the application (if you need to be able to kill it, try killing it)
  • Run it both as a regular user ( user_u ) as well as administrative users (if applicable). If your domain needs to support unconfined domains/users, run it from an unconfined user domain too.
  • Run it from a terminal, console, screen, sudo, ...
  • Change the applications' configuration file (including rendering it useless with syntax errors) and look at the applications' behavior. Especially syntax failures as that might trigger the application to log things at places that you haven't discovered earlier.

Policy Guidelines

Gentoo Hardened SELinux policy

To streamline the policy development efforts, Gentoo Hardened has a SELinux Policy document explaining the principles used during policy development and the implementation guidelines we strive to follow during development.

Such a policy is important because we want to have a consistent security policy that users and developers can relate to. By following the policy, we hope that other developers can quickly jump in and work on it further.

Submitting Patches

File context patches

If you are able to fix a problem by adding the proper file contexts (using semanage fcontext -a ), please consider the following:

  • If the location for which you set the context deviates from the standard location as either intended by the project or Gentoo itself, it might be best to document it in the forums or elsewhere. We will not change file contexts to match every ones configuration, unless the file context change is apparent for each installation.
  • Developers might not immediately push file context changes in new policy module packages to keep the amount of policy module changes low. Instead, these changes can be stacked and pushed when other changes occur as well.

If you believe that the change is needed for everyone using Gentoo Hardened with SELinux, create a bugreport and assign it to selinux@gentoo.org . In the bugreport, mention the file context you think is necessary and why.

Policy patches

Policy patches are best generated through the git format-patch approach.

user $cd hardened-refpolicy/policy/modules

Edit the file(s), like openct.te


user $git add openct.te
user $git commit -m 'Openct does not need to read /proc'
user $git format-patch -n -s master
0001-openct-does-not-need-to-read-proc.patch

Attach this patch to the bugreport explaining why it is needed. If you think the patch itself is not obvious, make sure that the necessary comments are in place inside the patch for future reference. If your patch contains many steps, it might be best to use separate commits in the repository so that the logic of the patch can be easily followed.

Please have a separate patch file per module (do not combine multiple modules in a single patch).

Fully Running Your Own Policy

To run a different policy than the one Gentoo Hardened providers, there are two methods to do so.

The first method is to use a different git repository and use live ebuilds. In this case, you point to your own git repository that contains the SELinux policy (which still has to be a reference policy clone) and use the live ebuilds without further adjustements.

The second method is to build versioned packages, overriding the Gentoo ones.

Using a different repository

To use a different repository, point the SELINUX_GIT_REPO variable to your git repository.

FILE /etc/portage/make.confUsing a different git repository
SELINUX_GIT_REPO="https://my.glorious.git/repository"

If you use different branches, set the SELINUX_GIT_BRANCH to the branch name you want to use.

Next, make sure that your system uses live ebuilds. This is accomplished by setting the /etc/portage/package.accept_keywords appropriately:

FILE /etc/portage/package.accept_keywords/selinuxEnabling live ebuilds
sec-policy/* **

Now run:

root #emerge -av @selinux-rebuild

Building versioned packages

Creating a local overlay

If you want to use your own policy rather than Gentoo's, we seriously recommend to use a local overlay which uses the same package names and constructs. This allows your policy to integrate properly with the other Gentoo packages (which might depend on the SELinux packages). For instance, when you install openldap, it will still properly depend on the sec-policy/selinux-ldap package even if you provide it completely.

To do so, first create a local overlay and copy the content of the sec-policy category inside it.

user $mkdir dev/overlay
user $cp -r /usr/portage/sec-policy dev/overlay

Next, tell Portage to not synchronise the sec-policy category of the main tree anymore. To do so, create the file /etc/portage/rsync_excludes with the following content:

CODE Rsync exclusion information
sec-policy/

Finally, add your current overlay by editing /etc/make.conf :

CODE Editing make.conf
PORTDIR_OVERLAY="${PORTDIR_OVERLAY} /home/user/dev/overlay"

From now onwards, Gentoo Portage will only use your local overlay (you can remove /usr/portage/sec-policy if you don't want Portage to even reuse the current set of packages.

Updating module packages

To create or update a module package, you can use the following skeleton for the ebuilds:

CODE Skeleton for ebuilds, example for postfix
# Copyright 1999-2012 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
# $Header: /var/cvsroot/gentoo/xml/htdocs/proj/en/hardened/selinux-development.xml,v 1.7 2013/05/11 11:51:23 swift Exp $
EAPI="4"
  
IUSE=""
# Set the MODS variable to the refpolicy name used, so services/postfix.te gives "postfix"
MODS="postfix"
# BASEPOL is optional, set it to the selinux-base-policy version which
# includes the latest patch (or interface you use in the policy)
BASEPOL="2.20120215-r9"
  
inherit selinux-policy-2
  
DESCRIPTION="SELinux policy for postfix"
KEYWORDS="~amd64 ~x86"
  
# POLICY_PATCH is optional (only when you have a module patch which is not included
# in the patchbundle of the base policy you referred to in BASEPOL.
POLICY_PATCH="${FILESDIR}/fix-services-postfix-r3.patch"

The patch(es) that you can put in the files/ location (and referred to in the POLICY_PATCH ) should be made as defined earlier in this document. You can put multiple patches in this variable if you want.

Don't forget to run pkgdev manifest with every change, and run pkgcheck scan to check for potential mistakes.

Updating base package

To provide updates on the base policy, it is recommended to keep all patches you made centrally in a directory (say dev/hardened/base-patches ). When you want to create a new sec-policy/selinux-base-policy and sec-policy/selinux-base release, create a patchbundle from your patch directory, put the bundle online somewhere, create the updated ebuild and try it out.

However, the moment you modify the base package(s), you will also need to change the selinux-policy-2.eclass file and the base policy ebuild. This is because, by default, the ebuilds will look for their files in the Gentoo download locations instead of wherever you would like to store the patches and sources.

Put a copy of the eclass inside your overlay and edit the SRC_URI definition to point to your new location. Then edit the selinux-base and selinux-base-policy ebuilds (inside your overlay again) and change their SRC_URI too.

Once this is done, you can go onwards with building a new base policy package.

user $cd dev/hardened/base-patches

Add the patches you want to include, cfr Submitting Patches

Then, create a new patch bundle

user $tar cjvf ../overlay/sec-policy/selinux-base-policy/files/patchbundle-selinux-base-policy-2.20120215-r10.tar.bz2 *

Finally, bump the revision of the ebuilds in the overlay:

user $cd ../overlay/sec-policy/selinux-base-policy
user $cp selinux-base-policy-2.20120215-r9.ebuild selinux-base-policy-2.20120215-r10.ebuild
user $vim selinux-base-policy-2.20120215-r10.ebuild

Update references to the patchbundle and dependencies (like BASEPOL):

user $cd ../selinux-base
user $cp selinux-base-2.20120215-r9.ebuild selinux-base-2.20120215-r10.ebuild

Don't forget to run pkgdev manifest with every change, and run pkgcheck scan to check for potential mistakes. You can then install sec-policy/selinux-base-policy-2.20120215-r10 and test it out.

Adding Third Party Policies

What is a Third Party Policy?

Most users who are responsible for handling the SELinux policies on one or more systems will most likely be writing their own policies too. Be it because the policy offered by Gentoo doesn't provide a policy for the package yet, or because the policy is needed for a local, non-Gentoo-provided package, it is still important for the administrator to easily manage these added policies on the systems.

An administrator can perfectly build and load the policy once and then let it be. After all, once a SELinux policy is loaded, it will be reloaded after every (re)boot. But within Gentoo, we support simple ebuilds that install the policy on the system for you.

Creating an Ebuild

Assuming you already have an overlay, all you need to do is create a package for your third party policy. For instance, a policy for an Oracle database might be called oracledb.pp so you would then create a package sec-policy/selinux-oracledb .

Then create the following ebuild:

CODE Third party policy ebuild
# Copyright 1999-2012 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
# $Header: /var/cvsroot/gentoo/xml/htdocs/proj/en/hardened/selinux-development.xml,v 1.7 2013/05/11 11:51:23 swift Exp $
EAPI="4"
  
IUSE=""
MODS="oracledb"
BASEPOL="2.20120215-r10"
POLICY_FILES="oracledb.te oracledb.if oracledb.fc"
  
inherit selinux-policy-2
  
DESCRIPTION="SELinux policy for Oracle DB"
  
KEYWORDS="~amd64 ~x86"

Finally, put the three policy-related files (assuming you have three) in the files directory of the package.

With this in place, you can now install the package and it will install the SELinux policy module. If the package provided an interface, it will also be added to the /usr/share/selinux/strict/include location (or targeted or whatever your SELINUXTYPE is).