[ << ]
[ < ]
[ Home ]
[ > ]
[ >> ]
5. Modifying the Gentoo Hardened SELinux Policy
Content:
5.a. SELinux Policy Language
Introduction
By default, Gentoo provides a generic, yet tightly controlled policy which is
deemed a good start policy for the majority of users. However, the purpose
behind a Mandatory Access Control system is to put the security administrator in
control. As such, a handbook on SELinux without information on how to write
policies wouldn't be complete.
In this chapter, we'll talk a bit about the language behind SELinux policies and
give some pointers on how to create your own policies, roles, etc.
Building a SELinux Module
First, before we go into the art of SELinux policy writing, let's first make a
small SELinux module with a rule we can test, build the module and see if things
work. Although these steps are fairly easy, they are important nonetheless.
Modifying the SELinux policy as offered by Gentoo is best done through
additional SELinux policy modules. Only when the core policy (the base policy)
is not to your liking should you see on using a totally different policy.
Let's start with a skeleton for a policy module we'll call testmod. You
should use simple names for the modules as the build infrastructure is quite
sensitive to special constructs. Use only letters a-z and numbers, and never
start a module name with a number.
Code Listing 1.1: Policy module skeleton |
policy_module(testmod, 1.0.0)
|
Yes, that's it. But as you can see, it is fairly empty. So let's add a rule that
allows a regular user (in the user_t domain) to read ebuild files (of type
portage_ebuild_t).
Code Listing 1.2: Policy module testmod |
policy_module(testmod, 1.0.0)
require {
type user_t;
type portage_ebuild_t;
class file { read open getattr };
class dir { read search open getattr };
}
allow user_t portage_ebuild_t:file { read open getattr };
allow user_t portage_ebuild_t:dir { read search open getattr };
|
As you can see, something as simple as allowing a user to read a file requires
quite a few privileges. The directory privileges are needed to allow a user to
navigate through the Portage tree structure whereas the file privileges are
needed for a user to be able to access and open the ebuilds. Save this file as
testmod.te.
To build the policy and convert it into the binary module that we can load into
the SELinux policy store, we can use the Makefile available in
/usr/share/selinux/strict/include (substitute strict with the
SELinux policy type you are using).
Code Listing 1.3: Building a binary policy module |
$ make -f /usr/share/selinux/strict/include/Makefile testmod.pp
|
The filename (testmod.pp) is the destination binary SELinux module
name. The Makefile will automatically look for the
testmod.te file you have in the working directory.
As a result, you should now have a file called testmod.pp. This
module file can now be loaded in the SELinux policy store as follows:
Code Listing 1.4: Loading a binary module |
# semodule -i /path/to/testmod.pp
|
Congratulations! You have now build your first SELinux policy module. If you
want to disable it, remove it through semodule -r testmod.
This method of building a policy (using the Makefile and
semodule) is something that you will need to do every time you want to
update the SELinux policy on your system. The contents of the policy however
does change as we will see in the rest of this document.
Getting the SELinux Policy Interfaces
To streamline policy development, the SELinux policy based on the reference
policy uses interfaces to access privileges within a module. If you have built
selinux-base-policy with USE="doc" then this information is
available at
/usr/share/doc/selinux-base-policy-<version>/html. It is
recommended to have this information at hand, since most policy
development/updates will be done through the interfaces offered by the policy.
If you are just interested, you can also find these interface definitions online. Mind you though,
the online resource is only the reference policy and might differ a bit from the
policy available within Gentoo.
Using Policy Interfaces
Using the policy interfaces allows you to update the policy with more readable
functions. For instance, to allow the user_t domain to call and use Portage
applications, the module could look like so:
Code Listing 1.5: Example policy to allow user_t to use portage |
policy_module(testmod, 1.0.0)
require {
type user_t;
role user_r;
}
portage_run(user_t, user_r)
|
Of course, this makes the user_t domain much more privileged than the previously
defined rules to read ebuild files: it allows the user to call portage, update
the system, etc. Of course, the user still requires the proper regular Linux
permissions (so he needs to be part of the portage group or become root).
Needless to say, we do not recommend to grant this to a regular user ;-)
5.b. Full SELinux Policy Modules
Checking Out an Isolated Module
With the above in mind, we can now go one step further and investigate a full
policy module, with both the type enforcement rules (.te file),
file contexts (.fc) and interfaces (.if).
You should know that writing a module requires you to get intimate with the
application. It isn't a matter of just hoping for the best: as a security
administrator, you will be responsible for defining what accesses are allowed
and which not. If you forget one, the application might break under the users'
hands. But if you add too much, you might grant privileges that can be abused
later on. And it will be a lot more difficult to track and remove privileges
later as you will be hesitating if the privilege is needed or not.
In this section, we will not divulge in how to write one. We have an excellent
Gentoo Hardened SELinux
Development resource that guides you in that. However, we will look into
such a full module to explain the other aspects of policy development.
Type Enforcement File
The .te file we wrote earlier is a type enforcement file.
Its purpose is to define the access rules related to the module that you are
building, but also - and more importantly - define new types (or even roles).
The example below is a snippet from a module for the skype application.
Code Listing 2.1: Snippet from skype.te |
policy_module(skype, 1.0.0)
type skype_t;
type skype_exec_t;
application_domain(skype_t, skype_exec_t)
type skype_home_t;
userdom_user_home_content(skype_home_t)
manage_dirs_pattern(skype_t, skype_home_t, skype_home_t)
manage_files_pattern(skype_t, skype_home_t, skype_home_t)
|
In the above example, three new types are declared: skype_t (which will
be used for the application), skype_exec_t (which is the label given to
the application binary) and skype_home_t (which will be used for the
users' ~/.Skype location). Also, the skype_t domain is given
some privileges with respect to the skype_home_t label (manage
directories and files).
File Context File
In the .fc file (which stands for file context file) the
module's resources (files, directories, sockets, ...) are defined. Once the
module is loaded, these rules are added so that file system relabeling will put
the correct context on the files.
The example below is a snippet from the skype modules' file context file.
Code Listing 2.2: Snippet from skype.fc |
HOME_DIR/\.Skype(/.*)? gen_context(system_u:object_r:skype_home_t,s0)
/opt/skype/skype -- gen_context(system_u:object_r:skype_exec_t,s0)
/usr/bin/skype -- gen_context(system_u:object_r:skype_exec_t,s0)
|
The format of the file context file has the following syntax:
-
The regular expression that matches the file(s) and directorie(s) affected
by that line
-
An optional identifier to differentiate the type of files (file, directory,
socket, symbolic link, ...)
-
A gen_context line that contains the context to assign to the file(s)
and directorie(s)
Interface File
In the .if file (for interface file) interfaces are declared
which can be used by other modules. It is through interfaces that a nicely
defined policy can be built on top of other, existing policy modules.
One interface could be to allow users to call and execute an application. For
instance, the following interface can be found in the skype module.
Code Listing 2.3: Snippet from skype.if |
interface(`skype_role',`
gen_require(`
type skype_t, skype_exec_t, skype_tmpfs_t, skype_home_t;
')
role $1 types skype_t;
domtrans_pattern($2, skype_exec_t, skype_t)
allow $2 skype_t:process { ptrace signal_perms };
manage_dirs_pattern($2, skype_home_t, skype_home_t)
manage_files_pattern($2, skype_home_t, skype_home_t)
manage_lnk_files_pattern($2, skype_home_t, skype_home_t)
relabel_dirs_pattern($2, skype_home_t, skype_home_t)
relabel_files_pattern($2, skype_home_t, skype_home_t)
relabel_lnk_files_pattern($2, skype_home_t, skype_home_t)
ps_process_pattern($2, skype_t)
')
|
Through this skype_role, we can then allow users to call skype, as can be
found in the unprivuser.te file (which defines the user_t domain):
Code Listing 2.4: Snippet from unprivuser.te to call skype |
optional_policy(`
skype_role(user_r, user_t)
')
|
The following table shows a few common interfaces that could be in use. We
seriously recommend to look at the available interfaces when enhancing or
creating your own modules - and be sure to pick the interface that adds just
what you need, nothing more.
| Templates |
| Suffix |
Example |
Description |
| _template |
virt_domain_template(prefix) |
Not really an interface, templates create additional domains based on the
information given to them. This is usually done for fine-grained policy
templates with a common (sub)set of privileges.
|
| Transformations |
| Suffix |
Example |
Description |
|
miscfiles_cert_type(resource) |
Transformation interfaces generally add specific attributes to resources or
domains. Attributes "transform" the given resource into something more. In
the given example, the miscfiles_cert_type(resource) assigns the cert_type
attribute to the resource (and also marks it as a file). Interfaces, like
miscfiles_read_all_certs work on these attributes.
|
| Access interfaces |
| Suffix |
Example |
Description |
| _<access>_<resource> |
mta_getattr_spool(domain) |
Grant the specified domain access towards the shown resource. The resource
usually defines the type too (like kudzu_getattr_exec_files: grant getattr
on the kudzu_exec_t files) unless it is obvious from the name, or when the
resource is a more specific term towards the domain. It can also include
dontaudit (like mta_dontaudit_getattr_spool).
|
| _exec |
dmesg_exec(domain) |
Grant one domain the right to execute the given domains' executable file (in
the example, allow "domain" to execute dmesg_exec_t files), but without
implying that the domains transition. In other words, dmesg gets executed
but still confined by the privileges of the source domain.
|
| _domtrans |
dmesg_domtrans(domain) |
Grant one domain execute and transition privileges towards the new domain.
This interface is most commonly used to allow application domains to
transition to another. In the given example, dmesg is ran with the
privileges of the dmesg_t domain.
|
| _run |
netutils_run(domain, role) |
Grant a given role and domain the rights to execute and transition towards
the given domain. This is usually granted to (existing) user roles and
domains and gives them the set of privileges needed to interact safely with
the new (interactive) domain (such as terminal access).
|
| _role |
xserver_role(role, domain) |
Allow the given role and domain the necessary permissions to transition and
interact with the given domain. This interface is enhanced with the
privileges to interact with the domain (and its underlying files) more
thoroughly, and is usually assigned to newly created users or roles within
the policy (rather than enhance existing user domains and roles).
|
| _admin |
aide_admin(domain) |
Grant the given domain the rights to administer the target domains'
environment. This usually involves privileges to manage and relabel all
affiliated files, directories, sockets, etc.
|
5.c. Using audit2allow
Introduction
When reading online resources on SELinux, you will notice that there are many
references to a tool called audit2allow. This tools' purpose is to read
AVC denial messages from the audit log file and transform them into a policy
module that you can load. The advantage is that it makes it a lot easier to
write policies. The downside is that the output (unless you use the -R
option) is not usable for the Makefile we used earlier to build
modules.
Another disadvantage is that the tool does not intelligently cope with changes.
It blindly accepts denials and treats them as if they need to be allowed, rather
than investigate if no other context should be given to the file, etc.
Using audit2allow
Using audit2allow is pretty straightforward. You send it the denials you
want to fix and store the result in a .te file. You then convert it
into an intermediary format which can then be translated into a .pp
file for final loading by semodule.
For instance, to catch all denials and transform them into allowed statements
from firefox-related denials:
Code Listing 3.1: Generate a new policy using audit2allow |
# grep firefox /var/log/avc.log | audit2allow -m firefoxmod > firefoxmod.te
# checkmodule -m -o firefoxmod.mod firefoxmod.te
# semodule_package -o firefoxmod.pp -m firefoxmod.mod
# semodule -i firefoxmod.pp
|
Keep the module name (given through the -m option) simple: only use
characters ([a-z]) and numbers ([0-9]), and start the module name
with a character.
5.d. Creating Additional Roles
Create the Role and Domain
If you want to create additional roles on the system, you first need to create a
SELinux module that creates that role, together with its main domain. Below is a
very simple example for a database administration role, which we will offer
through the sec-policy/selinux-dbadm package later:
Code Listing 4.1: Example dbadm_r and dbadm_t definition |
policy_module(dbadm, 1.0.0)
#######################################
#
# Declarations
#
role dbadm_r;
# Create a user domain (dbadm_t)
userdom_base_user_template(dbadm)
#######################################
#
# dbadm local policy
#
allow dbadm_t self:capability { dac_override dac_read_search sys_ptrace };
files_dontaudit_search_all_dirs(dbadm_t)
files_delete_generic_locks(dbadm_t)
files_list_var(dbadm_t)
selinux_get_enforce_mode(dbadm_t)
logging_send_syslog_msg(dbadm_t)
userdom_dontaudit_search_user_home_dirs(dbadm_t)
optional_policy(`
# Grant the user admin rights on PostgreSQL (process-wise)
postgresql_admin(dbadm_t, dbadm_r)
')
|
Build and load the module and verify that the role is now available:
Code Listing 4.2: Build and verify |
# make -f /usr/share/selinux/strict/include/Makefile dbadm.pp
# semodule -i dbadm.pp
# seinfo -r | grep dbadm
dbadm_r
|
Assign Users the Role
Next, assign users the role you just created. You can do this to existing
SELinux users, but here we'll create an additional SELinux user for the role.
Code Listing 4.3: Grant a user the role |
# semanage user -a -R "staff_r dbadm_r system_r" dbadm_u
|
Next map the user(s) to this SELinux user.
Code Listing 4.4: Mapping a set of users to the dbadm_u user |
# semanage login -a -s dbadm_u david
# semanage login -a -s dbadm_u laura
|
Also load in the following policy to support transitioning to the dbadm_r role
for the staff_r role:
Code Listing 4.5: Allow transitioning to dbadm_r |
dbadm_role_change(staff_r)
|
In the first semanage command, where we create the dbadm_u user, you
might have noticed that we grant it the system_r role. This is because
the user is granted the postgresql_admin role, allowing the user to
manipulate the postgresql init script:
Code Listing 4.6: User restarting postgresql service |
$ ls -Z /etc/init.d/postgresql
system_u:object_r:postgresql_initrc_exec_t /etc/init.d/postgresql
$ sudo -r dbadm_r -t dbadm_t /etc/init.d/postgresql restart
|
Thanks to the SELinux support by sudo, you can also mention the role and
type in the sudoers file:
Code Listing 4.7: Using the sudoers file for specific roles |
david ALL=(ALL) ROLE="dbadm_r" TYPE="dbadm_t" NOPASSWD: /etc/init.d/postgresql [a-z]*
|
With this setting, the user can just run sudo /etc/init.d/postgresql
restart.
By using sudo correctly, you can grant the user full administrative
rights over a daemon (including editing files) with a much lower risk of the
user elevating his rights to a more system-wide administrative role.
The idea behind this approach is that the user stays within its main domain
(like staff_t) but is allowed to perform administrative actions through
sudo which allow him to (temporarily) transition to the dbadm_r
role.
[ << ]
[ < ]
[ Home ]
[ > ]
[ >> ]
The contents of this document, unless otherwise expressly stated, are licensed under the CC-BY-SA-2.5 license. The Gentoo Name and Logo Usage Guidelines apply.
|