A step-by-step guide to building a new SELinux policy module
Who’s afraid of SELinux? Well, if you are, you shouldn’t be! Thanks to the introduction of new GUI tools, customizing your system’s protection by creating new policy modules is easier than ever. In this article, Dan Walsh gently walks you through the policy module creation process.
A lot of people think that building a new SELinux policy is magic, but magic tricks never seem quite as difficult once you know how they’re done. This article explains how I build a policy module and gives you the step-by-step process for using the tools to build your own.
Before we start, let’s review why we work with policy modules. In the past, in order to modify the current SELinux policy on a system running Red Hat Enterprise Linux 4, a system administrator would have had to to download the policy source, edit the policy source code, and rebuild and install the policy using tools like make install. The introduction of policy modules made this process easier and less error-prone. A system administrator could use the audit2allow utility to generate policy module updates directly from audit.log error messages. These modules function in a way similar to kernel modules in that they enable system administrators to modify part of the policy (a specific module) without having to rebuild the entire thing.
Remember to start small
One reminder! When you start to write policy, start small. Often, people send me email telling me they want to write SELinux policy and then they choose to write it for something huge like Firefox. When you decide to write policy for an application, you should have an idea of what the application does and what your security goals are for the application.For example:
- Least privilege.
An application’s security goals are often based on “least privilege,” meaning that the application is only allowed to do the things it was designed to do. For example, FTP isn’t allowed to talk to the telnet port. - Modified privilege.
Sometimes your security goal might be to give the application less privilege than it was designed to have. This is for when you want to prevent Firefox from writing to your home directories. - Booleans. You might want to add booleans so your end user could modify the security policy depending on his security goals for the application. The best example of this is FTP servers. Most people run anonymous FTP servers, but some want full access to the users’ home directories. I can write policy to satisfy both users and have a boolean to arrive at least privilege.
Since simple daemon applications usually have security goals close to what the application is designed to do,a good place to begin writing policy is for daemons started during the system startup routines or CGI scripts. Avoid writing policy for user applications until you thoroughly understand SELinux and your security goals for that application.
For this article I will describe confining the rwho daemon. This daemon generates output similar to who, but for users logged in to hosts on the local network. It’s helpful (but not necessary) if policy writers have an intimate understanding of the applications they’re going to confine.You certainly don’t have to know every application you confine.
The new policy generation GUI tool: polgengui
To build things, you need tools. Starting with RHEL5 and Fedora 7, I began building a GUI tool to help people generate templates for policy generation. I examined the upstream policy and built templates around how that policy is written. The goal was to easily get policy upstreamed to get the maximum usage. So I added the polgengui tool to the system-config-selinux utility. This tool generates four files:
- Type enforcement (te) file–contains all of the code used to confine my application
- File context (fc) file–contains the mappings between files and file context
- Interface (if) file–contains all of the interfaces that other domains might want to use to communicate with my domain and the file types created by my applications.
- Shell script (sh)–used to compile, install and fix the labeling on the test system.
Now let’s start writing policy. There are three main steps that we’ll perform.
Step 1 – Use system-config-selinux to create a new policy module
Start by running system-config-selinux:

To begin building a new policy module, click the New button to run the SELinux Policy Generation Druid.

This druid is a wizard that chunks the policy creation process into seven quick dialog boxes:
- Name of application to be confined
- Application type
- Incoming network port connections
- Outgoing network port connections
- Common application traits
- Files and directories (where the application will write)
- Generate policy in this directory
Name of application to be confined

This screen will prompt you for a name for your confined application and the path to the executable used to start it. The tool will use this information to create two SELinux types, rwho_t and rwho_exec_t. The running process (domain) will use the rwho_t type. File context on disk will use rwho_exec_t.
Application type

This screen asks you to identify the type of application. This allows you to set up all of the policy to correctly transition from other domains. The supported application types are:
- Standard Init Daemon. These applications are started during the init process either by rc.sysinit directly or as a start up script in /etc/init.d.
- Internet Services Daemon. These applications are started by inetd or xinetd.
- Web Application/Script (CGI). These Applications are actually executed by apache as separate process. This type does not currently work for applications that run in-process, like mod_perl or mod_php.
- User Application. These applications are usually started by a logged in user.
In the future we will be adding a mechanism for writing policy to actually confine a logged in user.
Incoming network port connections

This screen allows you to enter a space-separated list of network ports that the application will bind/listen on for incoming connections. If you’re not sure, you can leave this blank and come back to it later.
In this screenshot, rwho will listen on UDP port 513.
Outgoing network port connections

This screen allows you to specify TCP and UDP ports that the confined application needs to connect to. Because rhwod doesn’t connect to any ports, I leave it blank.
Both of the ports screens search through the existing policy to see if a type is already defined for that port number. If the port is defined, it will write the appropriate interface to use the port. If the port is not currently defined, polgengui will generate a new type for the port and the appropriate interface. If you define a new port, you will need to run some semanage commands to define the ports during install. The sh script file that will be generated by polgengui will contain the correct semanage command.
Common application traits

This screen allows you to specify some common traits that applications exhibit. Checking these boxes will add policy to your template and allow the application to perform the selected functions. If you are not sure whether your applications need any of these, leave them blank, and polgengui will generate the policy by running the application. If you have access to the source a you can use grep to quickly find out if the application requires any of these. For example, grep -r syslog . will tell whether or not your application uses syslog, and grep -r getpw . will tell whether or not you want to use nsswitch.
Files and directories
This screen allows you to designate files and directories where the confined application needs write privileges. The tool looks at the paths that you enter and uses them to establish the name of the type to use. It is doing this based on the conventions established in the reference policy. So files stored under /var/log should be labeled rwho_log_t, and files/directories stored under /var/spool should be labeled rwho_spool_t. In order to get the regular expression labeling correct, polgengui asks you to differentiate between files and directories. Files/directories do not have to exist before running the application. Most new paths added to this screen will result in a new type being generated.
Note: One common mistake people make when writing policy is the over-generation of types. File context types should be generated only for files/directories that are owned and written by the confined application. If the file/directory is only read by the application, you are better off leaving it as the default file context. So conf files in /etc/ should usually be left as etc_t. Files with sensitive security data are the exception. Something like a credit card database should not be labeled etc_t, even if the confined application treats it as read-only.
Generate policy in this directory

This screen asks you where to put the tool’s output. It will default to the current working directory, but often you are better off putting your policy files in a separate directory.
Generated policy files

The tool will confirm what you’ve asked it to do. Click Apply to generate the files, and polgengui will display the results:

Warning: The tool will continue running, and you can go back through the steps again, but it will overwrite these four files if you hit the apply button again.
Step 2 – Apply the new policy module
Now that you have the policy files, it’s time to apply them to the current policy. My method is to use a terminal window, log in as root, and execute the shell script generated by polgengui.
# cd /home/devel/dwalsh/tmp # sh rwho.sh sh rwho.sh Compiling targeted rwho module /usr/bin/checkmodule: loading policy configuration from tmp/rwho.tmp /usr/bin/checkmodule: policy configuration loaded /usr/bin/checkmodule: writing binary representation (version 6) to tmp/rwho.mod Creating targeted rwho.pp policy package rm tmp/rwho.mod tmp/rwho.mod.fc /sbin/restorecon reset /usr/sbin/rwhod context system_u:object_r:bin_t:s0->system_u:object_r:rwho_exec_t:s0 /sbin/restorecon reset /var/log/rwhod context system_u:object_r:var_log_t:s0->system_u:object_r:rwho_log_t:s0 /sbin/restorecon reset /var/spool/rwho context system_u:object_r:var_spool_t:s0->system_u:object_r:rwho_spool_t:s0 /sbin/restorecon reset /var/spool/rwho/whod.patriots context system_u:object_r:var_spool_t:s0->system_u:object_r:rwho_spool_t:s0 /sbin/restorecon reset /var/spool/rwho/whod.dhcppc0 context system_u:object_r:var_spool_t:s0->system_u:object_r:rwho_spool_t:s0
The shell script compiles the module that you just created, then loads it into the kernel. It then fixes the file context labels with the restorecon command.
Step 3 – Debug and complete the policy module
So far, so good. You created and applied the policy. What I always do next, and what you should do, too, is to verify the policy module. The best approach to use is to put the machine in permissive mode and allow the application to generate AVC messages, which you can in turn use to debug and fill in any gaps in the new policy module that you just created. This is an iterative process, but it’s the safest way to ensure that the policy is complete and correct.
Note that you should not do this on a production machine, as you will lose your SELinux protection when you run in permissive mode. You could generate AVC messages one at a time in enforcing mode, but it would be very time consuming.
# setenforce 0 # service rwho restart Stopping rwho services: [FAILED] Starting rwho services: [ OK ] # >>> Run some commands to get rwho to execute.
Now I use audit2allow to generate policy.
# grep rwho /var/log/audit/audit.log | audit2allow -R
require {
type initrc_var_run_t;
type rwho_t;
class capability sys_chroot;
class file { read write getattr lock };
}
#============= rwho_t ==============
allow rwho_t initrc_var_run_t:file { read write getattr lock };
allow rwho_t self:capability sys_chroot;
kernel_read_system_state(rwho_t)
It searches through the installed policy interfaces files on disk and attempts to find the best match for the AVC messages that were generated. These interface files are installed under the /usr/share/selinux/devel directory. In this case, it kernel_read_system_state(rwho_t). Note that any AVC that requires a domain to interact with itself will include the self qualifier.
The tool did not find an interface to match the AVCs that generated:
allow rwho_t initrc_var_run_t:file { read write getattr lock };
Looking at the AVCs that were generated in the /var/log/audit/audit.log, we see:
type=AVC msg=audit(1184874740.520:1685): avc: denied { read write } for pid=18340 comm="rwhod" name="utmp" dev=dm-0 ino=3178503 scontext=system_u:system_r:rwho_t:s0 tcontext=system_u:object_r:initrc_var_run_t:s0 tclass=file
type=PATH msg=audit(1184874740.520:1685): item=0 name="/var/run/utmp" inode=3178503 dev=fd:00 mode=0100664 ouid=0 ogid=22 rdev=00:00 obj=system_u:object_r:initrc_var_run_t:s0
These two things indicate that the rwho daemon is trying to read/write the /var/run/utmp file. (Note the read write getattr lock reference.) From personal experience, I know that the library for interacting with the utmp file always attempts to open it read/write and then falls back to read-only. You also don’t want rwho to be able to write over the utmp file, because it could contains some important security data. If you search through the /usr/share/selinux/devel directories, you’ll find two interface calls:
init_read_utmp(rwho_t) init_dontaudit_write_utmp(rwho_t)
The first interface will allow rwho to read the utmp file, the second interface tells the kernel to stop generating AVC messages when rwho attempts to write to utmp.
Now you can execute the sh script again and set the machine back in enforcing mode. Watch for additional AVC messages. Rebooting the machine can often generate additional AVC messages that weren’t generated from simply restarting the service. Repeat the process until there are no new AVC messages.
As soon as you’re happy with the way the policy is working, add it to the policy rpm and release it to Rawhide. There, people will happily (maybe not that happily) report back any problems that SELinux is causing their application. (Eventually I also publish the policy to upstream and ask for critique.) Open Source people will then tell me the mistakes that I have made in my policy. It’s just like Eric Raymond said in “The Cathedral and the Bazaar,” “Given enough eyeballs, all bugs are shallow.”
What’s next?
What’s next for you is to try out this step-by-step process for building a new policy module. You’ll no doubt see AVC messages unique to your own target applications, but if you take your time and make use of audit2allow, you’ll be able to create your own new policy modules.
For me? The next article in this series will describe locking down userspace.
Acknowledgments
Thanks to Len DiMaggio for help editing and ideas for content.
Useful references
danwalsh.livejournal.com
SELinux by Example, Prentice Hall
About the author
Dan Walsh has over 20 years experience in the computer field. He has spent most of his career working on Security Applications and platforms. He spent several years working on the Athena Project while at Digital Equipment Corp. Dan was also involved in designing and developing the AltaVista Firewall and AltaVista Tunnel (VPN) Products. He has worked for Netect developing HackerShield, a Vulnerability Assessment Product. Netect was acquired by BindView, where he continued working on HackerShield and developed a new product, BVControl for UNIX. At Red Hat, Dan has led the SELinux project, concentrating mainly on the application space and policy development. Dan Graduated with a BA in Mathematics from the College of the Holy Cross and a MS in Computer Science from Worcester Polytechnic Institute.
Also See: http://docs.fedoraproject.org/selinux-faq-fc3/