Building SELinux Policy Modules

{i} The procedure shown here is for SELinux Policy based on the SELinux Reference Policy, which for Fedora means Fedora Core 5 and later.


It is quite often necessary to make local changes to SELinux policy, for instance:

  • if your usage of some package is particularly unusual, or
  • if there is something missing from, or a bug in, the policy provided in Fedora Core

Making such local changes isn't particularly difficult, and is certainly better than disabling SELinux altogether, which is often the first option people consider. This article provides a brief introduction to building local policy modules.

Get Ready for Local Policy Development

It's convenient to set aside a directory to use for local policy modules, and make sure that you have the necessary packages installed. I tend to use the /root/selinux.local directory. Set up your system as follows:

# yum install make selinux-policy-devel
# cd /root
# mkdir selinux.local
# cd selinux.local
# chcon -R -t usr_t .
# ln -s /usr/share/selinux/devel/Makefile . 

Identifying the Problem

The first step should be to decide whether or not any problem you are having is caused by SELinux. You can do this by running SELinux in permissive mode, where SELinux denials are logged but the action is not actually denied. Use the setenforce command to put SELinux in permissive mode:

# setenforce 0

This command takes effect immediately. If the problem you were having has now gone away, you can safely assume that SELinux is the issue. You can put SELinux back in enforcing mode as follows:

# setenforce 1

In order to decide how to fix an SELinux problem, you first need to find out what exactly the problem is. SELinux denials are logged with "type=AVC" messages to either /var/log/audit/audit.log if you are running auditd, or /var/log/messages otherwise.

Supposing you were having problems with squid and found the following messages in your logs:

type=AVC msg=audit(1152671338.823:21775): avc:  denied { name_connect } for  pid=2371 comm="squid" dest=8080  scontext=system_u:system_r:squid_t:s0 tcontext=system_u:object_r:http_cache_port_t:s0 tclass=tcp_socket
type=SYSCALL msg=audit(1152671338.823:21775): arch=40000003 syscall=102 success=no exit=-13 a0=3 a1=bf9eb1a0 a2=52e1c4 a3=b7f1ca2c  items=0 pid=2371 auid=4294967295 uid=23 gid=23 euid=23 suid=0 fsuid=23 egid=23 sgid=23 fsgid=23 tty=(none) comm="squid" exe="/usr/sbin/squid" subj=system_u:system_r:squid_t:s0
type=SOCKADDR msg=audit(1152671338.823:21775): saddr=02001F907F0000010000000000000000
type=SOCKETCALL msg=audit(1152671338.823:21775): nargs=3 a0=12 a1=bbdd8f8 a2=10

Even without being able to understand the log messages above, it should be fairly evident that they are related to squid. What is actually being denied by SELinux is the squid dæmon's ability to connect to the Internet Cache Protocol V2 port of another squid dæmon.

Deciding How to Fix the Problem

There may in fact be several options for fixing the problem.

The first thing to check is that you actually have the current versions of SELinux-related packages. SELinux policy is undergoing rapid development and the problem you are seeing might already be fixed in a policy update:

# yum update selinux-policy\* libse\* policycoreutils

Failing that, it's possible that there is an existing SELinux boolean that will, if enabled, allow the action that is currently being denied. This is quite likely to be the case if what you're trying to do is a normal action for the program concerned, but not everybody would need to be able to do that action (e.g. it may not be needed for a default install). Some SELinux booleans are described in manual pages (e.g. see the httpd_selinux manual page). However, some of the manual pages can be out of date and some booleans aren't actually documented anywhere. You can get a list of the booleans available using the getsebool command:

# getsebool -a

This will produce a long list of available booleans that can be set, and their current states.

In some cases it's fairly obvious what a particular boolean is for. For instance, use_samba_home_dirs allows you to have home directories mounted from a samba server (this is explained in the samba_selinux manual page). In other cases you may need to look at the policy sources to see exactly what is being allowed by a particular boolean.

Considering the squid problem above, one option would be to enable the squid_connect_any boolean. This would allow squid to connect to any port on remote servers. The boolean could be set as follows:

# setsebool -P squid_connect_any=1

{i} The -P option makes the change permanent so that the boolean will remain set after a reboot (see the setsebool manual page)

In this case, although this would fix the problem, it's not the ideal solution since it would allow the squid dæmon to make outbound connections to any port; a compromised squid server could then be used to send spam, connecting to the SMTP port on victims' mail servers.

In the absence of an appropriate boolean, local policy changes will be needed. If the issue is likely to affect other people (for instance if what you're doing would be a common configuration), it will also be useful to report the issue either in Fedora Bugzilla or on fedora-selinux-list.

Examining Policy Sources

When making modifications to SELinux Policy, it's useful to be able to look at the policy sources to see what's already in policy, make comparisons with the policy for other services etc. The Fedora selinux-policy package usually has a number of significant patches so it's well worth getting and unpacking the selinux-policy source RPM for the sources. The yumdownloader tool from the yum-utils package is useful here:

$ yumdownloader --source selinux-policy

Before unpacking the SRPM, you will to create an RPM build environment for your account (see CreateRPMBuildEnvironment).

$ rpm -Uvh selinux-policy-*.src.rpm
$ cd ~/rpmbuild/SPECS
$ rpmbuild -bp selinux-policy.spec

You should now have the patched SELinux policy in the ~/rpmbuild/BUILD directory.

Creating a Local Policy Module

The audit2allow tool is invaluable when it comes to creating local policy modules. It can take a file containing AVC messages and generate policy rules to allow the currently-denied action. So for instance, with a file called avcs containing the squid issues above, the output would be as follows:

# audit2allow -i avcs -R

#allow squid_t http_cache_port_t:tcp_socket name_connect;
optional_policy(`corenetwork', `

The output of audit2allow will often include multiple options for rules that can be used, with all but one commented out. In cases like that, it's useful to know

  • what the "problem" program is actually needing to do, and
  • what the various options presented by audit2allow will allow it to do

The first of these needs some understanding of the "problem" program, and the second involves looking at the policy source for the interface concerned. These are outside the scope of this article, but the rule to go for is the one that allows everything that's needed, and as little as possible of what isn't needed.

So, the rule needed in this case is:


{i} The optional_policy wrapper around this rule can be dropped since corenetwork is always included in the base policy.

Adding the necessary preamble for a local policy module called localmisc, we end up with a policy rules file (which should be saved as localmisc.te in the /root/selinux.local directory created earlier:

policy_module(localmisc, 0.1.0)

require {
        type squid_t;

# Squid connecting to and communicating with other caches


  • The number 0.1.0 in the policy_module declaration is a module version number. Use anything you like, but increment it every time you edit the module.

  • The require clause should list any SELinux types referenced in the policy module, which in this case is just squid_t.

  • The additional corenet_tcp_sendrecv_http_cache_port(squid_t) rule allows the squid dæmon to communicate with other squid dæmons after connecting to them. The need for this rule would have become apparent after creating a local policy module containing just the corenet_tcp_connect_http_cache_port(squid_t) rule, or by running squid with SELinux in permissive mode.

To compile the policy module, just run make in the /root/selinux.local directory:

# cd ~/selinux.local
# make
Compiling targeted localmisc module
/usr/bin/checkmodule:  loading policy configuration from tmp/localmisc.tmp
/usr/bin/checkmodule:  policy configuration loaded
/usr/bin/checkmodule:  writing binary representation (version 5) to tmp/localmisc.mod
Creating targeted localmisc.pp policy package

The module can then be loaded using semodule:

# semodule -i localmisc.pp

Running semodule -l should show that module localmisc version 0.1.0 (amongst others) is loaded:

# semodule -l
amavis  1.0.5
clamav  1.0.3
dcc     1.0.1
localmisc       0.1.0
pyzor   1.0.4
razor   1.0.1

At this point, the SELinux issues affecting squid should be resolved. If not, it's possible that additional rules are needed, as with the corenet_tcp_sendrecv_http_cache_port(squid_t) rule mentioned earlier.

Module Longevity

Local policy modules loaded using semodule will survive a reboot; there is nothing further to do to ensure this. They will also survice most base policy updates, unless there is a major policy format change or something in the new policy conflicts with your policy module. This is only likely to happen with more advanced modules that define their own types or file contexts, and not with modules that just contain rules generated using audit2allow.

For instance, I had a local policy module for proftpd that supported use of the ftpdctl program to do run-time configuration changes to the FTP dæmon. This module defined a new type, ftpdctl_exec_t. I submitted this policy change for inclusion in the upstream reference policy, and eventually it was inoorporated into the Fedora selinux-policy package when that package updated to the reference policy version with my change in it. When the new selinux-policy-targeted package was installed, the rpm post-install script for the package failed as follows:

libsepol.scope_copy_callback: proftpd: Duplicate declaration in module: type/attribute ftpdctl_exec_t
libsemanage.semanage_link_sandbox: Link packages failed
semodule:  Failed!

So the new base policy couldn't be linked with my local proftpd policy module, since both tried to define ftpdctl_exec_t. The solution was to unload my local policy module as it was no longer needed:

# semodule -r proftpd

However, that still left me with the old base policy loaded (since linking the new one with my local modules had failed at rpm upgrade time), so I also needed:

# semodule -b /usr/share/selinux/targeted/base.pp

This got me fully up to date.

This article was written by PaulHowarth, with contributions from Hans Ulrich Niedermann and Klaus Weidner.