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.
- 1 Introduction
- 2 Setting Up Your Environment
- 3 A Domain Does Not Function Properly
- 4 No Domain Exists (Yet)
- 5 Policy Guidelines
- 6 Submitting Patches
- 7 Fully Running Your Own Policy
- 8 Adding Third Party Policies
- 9 Acknowledgements
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.
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:
- 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.
- 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 developmers might have edited your patch before committing ;-)
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
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.
make with the development Makefile offered by your installation.
You now have a sudo.pp file available which you can load (using
semodule -i sudo.pp ).
Building the base policy
If you want to build the base policy, run
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
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:
- Is it really SELinux that is restraining your system?
- Is the problem related to wrong resource labels / security contexts?
- Is the problem related to intra-module permissions?
- 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:
This only works if the problem is not to do with a SELinux-aware application (unlike
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.
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:
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):
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:
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:
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 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:
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
To gain a bit of insight in the (current) file context rules, use
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:
- Enhance the module so that the particular permission is granted, or
- 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
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:
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:
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.
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:
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):
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:
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:
To see what the ps_process_pattern is about:
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.
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.
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
email@example.com . In the bugreport, mention the file context you think is necessary and why.
Policy patches are best generated through the
git format-patch approach.
Edit the file(s), like openct.te
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
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.
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:
Finally, add your current overlay by editing /etc/make.conf :
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:
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
repoman manifest with every change, and run
repoman 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
Once this is done, you can go onwards with building a new base policy package.
Add the patches you want to include, cfr Submitting Patches
Then, create a new patch bundle
Finally, bump the revision of the ebuilds in the overlay:
Update references to the patchbundle and dependencies (like BASEPOL):
Don't forget to run
repoman manifest and
repoman scan . 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:
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).
We would like to thank the following authors and editors for their contributions to this guide:
- Sven Vermeulen