November 30th, 2006

Using Reference Policy/Generating a reference policy module

Today I am going to talk about the reference policy and building a policy module.

Reference Policy  was a redesign of the original "example" policy from the NSA. The example policy was used to build policy in Fedora 2/3/4 and in Red Hat Enterprise Linux 4. This policy grew unwieldy and was fairly difficult to work with. It worked fairly well in the development labs but did not really work in the real world. So NSA and mainly Tresys went to work to redesign and rewrite the example policy using software engineering techniques. Christopher J. PeBenito did most of the work of porting all of the example policy over to reference policy. A herculean effort. You can read much more about reference policy in SELinux by Example.

One of the big advancements in Reference policy is the introduction of interface files (.if). These files provide an interface into how an Type Enforcement domain can be used by other
domains. For example if you build an domain that contains config files which need to be read by other domains, you would build a interface named XYZ_read_config. Then other types would use that interface with out needing to know how you defined a XYZ config file. This file along with the Type Enforcement file (.te) and the file context file (.fc) are usually used to build a policy module.

A really neat things about the new version of SELinux is the combination of reference policy and policy modules. As I blogged about the other day, you can now build policy modules on the fly and do not need to work with the big monolithic policy package.By adding the power of reference policy to this you can greatly ease the pain in developing policy.

First lets install selinux-policy-devel. This is the reference policy development environment.

# yum -y install selinux-policy-devel

This package installs files under /usr/share/selinux/devel.

# ls /usr/share/selinux/devel/
example.fc example.if example.te include Makefile policygentool policyhelp

  • example.* are an example policy using reference policy.
  • policyhelp is a tool used to launch a web browser to read the interface definition documentation.
  • include is the top-level directory including all of the interface files used in the installed policy.
  • Finally there is policygentool and the Makefile.

policygentool is a basic script, which I originally wrote and was greatly enhanced by Erich Schubert, that allows you to generate a policy framework based off the reference policy.

One package that we do not currently have policy for is the pcscd daemon (PC/SC Smart Card Daemon).

I know relatively little about this package, but I can take a first stab at building policy for it.  Usually if you are building policy, you will know a lot more about the daemon you are confining then I do. :^)

The first step I do when generating a new policy module is to create a directory to do the work in.

# cd /root
# mkdir pcscd
# cd pcscd

policygentool takes two parameters, the name of the domain you are creating and the path to the executable

# /usr/share/selinux/devel/policygentool pcscd /usr/sbin/pcscd

This tool generate three files for policy development, A Type Enforcement (te)
file, a File Context (fc), and a Interface File(if). Most of the policy rules
will be written in the te file. Use the File Context file to associate file
paths with security context. Use the interface rules to allow other protected
domains to interact with the newly defined domains.

After generating these files use the /usr/share/selinux/devel/Makefile to
compile your policy package. Then use the semodule tool to load it.

# /usr/share/selinux/devel/policygentool myapp /usr/bin/myapp
# make -f /usr/share/selinux/devel/Makefile
# semodule -l myapp.pp
# restorecon -R -v /usr/bin/myapp "all files defined in myapp.fc"

Now you can turn on permissive mode, start your application and avc messages
will be generated. You can use audit2allow to help translate the avc messages
into policy.

# setenforce 0
# service myapp start
# audit2allow -R -i /var/log/audit/audit.log

Return to continue:

If the module uses pidfiles, what is the pidfile called?
If the module uses logfiles, where are they stored?

If the module has var/lib files, where are they stored?

Does the module have a init script? [yN]
Does the module use the network? [yN]

This script has now created three files.
# ls
pcscd.fc pcscd.if pcscd.te

Lets look at these files.

First the File Context File.

# cat pcscd.fc
# pcscd executable will have:
# label: system_u:object_r:pcscd_exec_t
# MLS sensitivity: s0
# MCS categories:

/usr/sbin/pcscd -- gen_context(system_u:object_r:pcscd_exec_t,s0)
/var/run/ gen_context(system_u:object_r:pcscd_var_run_t,s0)

Notice that the executable and pid file have a mapping between their path and the file context. There are three fields in this file. The first is a regular expression used to map files/directories to file context. The second optional field is a file type field. If it does not exist, it indicates match all. If it does exist it must be a dash '-' followed by the file type. This is the first field in the output of the ls -l command. So a regular file would be --, a directory would be -d, a chr_file would be -c ...The third field is a specification of the security context to be assigned. There is a macro call here which is used to generate either MLS or non MLS file context depending on whether you machine supports the fourth section of the security context. Since RHEL and FC do the end output for this would look something like the following:

/usr/sbin/pcscd -- system_u:object_r:pcscd_exec_t:s0
/var/run/ system_u:object_r:pcscd_var_run_t:s0

Next the Interface File.

# cat pcscd.if
## <summary>policy for pcscd</summary>

## <summary>
##      Execute a domain transition to run pcscd.
## </summary>
## <param name="domain">
## <summary>
##      Domain allowed to transition.
## </summary>
## </param>
                type pcscd_t, pcscd_exec_t;


        allow pcscd_t $1:fd use;
        allow pcscd_t $1:fifo_file rw_file_perms;
        allow pcscd_t $1:process sigchld;

As you can see the policygentool application has generated just one interface, pcscd_domtrans. This interface could be used by any SELinux domain which wanted to executed pcscd and transition to its domain. Looking at the interface, you see that their is some XML at the beginning that the tools use to generate html for use in the interface documentation. You must describe the interface and the parameters to it.

Now we get to the M4 Macro code. The interface specification is required plus the name. By convention we begin interfaces with the type name. You should try to make the interfaces standardized on their actions also. IE Look for other similar interfaces in the include directory and name your similarly. The gen_require block, tells the compiler which types, attributes you will be using in this interface. This is used by semodule to verify everything is defined when loading a module. if pcscd_exec_t is not defined elsewhere in the policy, semodule will fail. As you see we also call out to another interface domain_auto_trans. The rest of interface is standard allow rules.

I will intersperse my comments in the type inforcement file

# cat pcscd.te

>> policy_module indicates to the compiler, we are using reference policy and pulls in a lot of standard require lines so we don't have to specify all the standard classes in our specification. The two parameters are the policy name and the version.

# Declarations

type pcscd_t;
type pcscd_exec_t;
init_daemon_domain(pcscd_t, pcscd_exec_t)

> These lines define the domain type of the running process and the type to be used on the file_context. It also calls an interface that basically says when init runs
pcscd_exec_t it should transition to pcscd_t

# pid files
type pcscd_var_run_t;

> This is the type of the pid file.

# pcscd local policy
# Check in /etc/selinux/refpolicy/include for macros to use instead of allow rules.

# Some common macros (you might be able to remove some)

> policygentool took the liberty to add these interface calls, since almost all domains need these

## internal communication is often done using fifo and unix sockets.
allow pcscd_t self:fifo_file { read write };
allow pcscd_t self:unix_stream_socket create_stream_socket_perms;

# pid file
allow pcscd_t pcscd_var_run_t:file manage_file_perms;
allow pcscd_t pcscd_var_run_t:sock_file manage_file_perms;
allow pcscd_t pcscd_var_run_t:dir rw_dir_perms;
files_pid_filetrans(pcscd_t,pcscd_var_run_t, { file sock_file })

> This is an interesting interface. Basically this says if pcscd_t creates a file or sock_file in /var/run, it should create it with the pcscd_var_run_t file context.

## Networking basics (adjust to your needs!)
## if it is a network daemon, consider these:
allow pcscd_t self:tcp_socket { listen accept };

> Then it throws in some networking rules that almost all daemons need.

Now we want to compile the policy files. This will create the pcscd.pp file.
# make -f /usr/share/selinux/devel/Makefile

Now we load the policy module into the kernel.
# semodule -l pcscd.pp

Set the file context correctly on the files created.
# restorecon -R -v /usr/sbin/pcscd /var/run/

Turn off enforcing mode so we can generate avc message.
# setenforce 0

Now restart the daemon to run in the correct domain
#service pcscd restart

Check to make sure the transition actually happened by looking at the context pcscd is running as
# ps -eZ | grep pcscd
user_u:system_r:pcscd_t 13636 ? 00:00:00 pcscd

Now you should see lots of avc's messages in /var/log/audit/audit.log,  You can use audit2allow to translate these avc messages in to allow rules
# grep pcscd /var/log/audit/audit.log | audit2allow -l
allow pcscd_t devlog_t:sock_file write;
allow pcscd_t devpts_t:chr_file { read write };
allow pcscd_t etc_runtime_t:file { getattr ioctl read };
allow pcscd_t self:unix_dgram_socket { connect create write };
allow pcscd_t syslogd_t:unix_dgram_socket sendto;
allow pcscd_t usb_device_t:chr_file { ioctl read write };

But a better way would be to use the -R qualifier to try to search Reference policy for some matches.

# grep pcscd /var/log/audit/audit.log | audit2allow -R

#allow pcscd_t devlog_t:sock_file write;
optional_policy(`logging', `
allow pcscd_t devpts_t:chr_file { read write };
allow pcscd_t etc_runtime_t:file { getattr ioctl read };
allow pcscd_t self:unix_dgram_socket { connect create write };

#allow pcscd_t syslogd_t:unix_dgram_socket sendto;
optional_policy(`logging', `
allow pcscd_t usb_device_t:chr_file { ioctl read write };

So I can add logging_send_syslog_msg(pcscd_t) to my te file but I still need to deal with. BTW We are working on a better tool to map AVC messages to interfaces.
But I still need to deal with:

allow pcscd_t devpts_t:chr_file { read write };
allow pcscd_t etc_runtime_t:file { getattr ioctl read };
allow pcscd_t self:unix_dgram_socket { connect create write };
allow pcscd_t usb_device_t:chr_file { ioctl read write };

I do not want to use any types in my te file that I did not define.  Any types were defined elsewhere so their should be an interface defined to provide access.   I notice one of the audit2allow rules has the rules for self in it, So I know I wont find an interface for this.  I need to add this rule directly to the type enforcement file, but through experience, i know that the daemon will probably also want other access to the dgram socket, so I change it to

allow pcscd_t self:unix_dgram_socket create_socket_perms;

BTW These macro definitions are in /usr/share/selinux/devel/include/support/obj_perm_sets.spt

I usually begin grepping through the interface tools for rules that match.

For example

# grep -r " devpts_t:chr_file" /usr/share/selinux/devel/include/
/usr/share/selinux/devel/include/kernel/terminal.if: type_transition $1 devpts_t:chr_file $2;
/usr/share/selinux/devel/include/kernel/terminal.if: allow $1 devpts_t:chr_file ioctl;
/usr/share/selinux/devel/include/kernel/terminal.if: allow $1 devpts_t:chr_file setattr;
/usr/share/selinux/devel/include/kernel/terminal.if: dontaudit $1 devpts_t:chr_file setattr;
/usr/share/selinux/devel/include/kernel/terminal.if: allow $1 devpts_t:chr_file { rw_term_perms lock append };
/usr/share/selinux/devel/include/kernel/terminal.if: dontaudit $1 devpts_t:chr_file { getattr read write ioctl };

This tells me to look for the interface in terminal.if. most daemons never really need to talk to the terminal, So I can probably dontaudit this access request.  
I find this interface and add it to my pcscd.te file.


Similarly I look for the rest of the calls and I find.


I end up adding

allow pcscd_t self:unix_dgram_socket create_socket_perms;

I also take a look in /var/run/ for pcscd files and I see

# ls -lZ /var/run/pcscd.*
srwxrwxrwx  root root user_u:object_r:pcscd_var_run_t  /var/run/pcscd.comm
-rw-------  root root user_u:object_r:pcscd_var_run_t  /var/run/
-rw-r--r--  root root user_u:object_r:pcscd_var_run_t  /var/run/

My pcscd.fc file ends up looking like

# cat pcscd.fc
# pcscd executable will have:
# label: system_u:object_r:pcscd_exec_t
# MLS sensitivity: s0
# MCS categories: <none>

/usr/sbin/pcscd         --      gen_context(system_u:object_r:pcscd_exec_t,s0)
/var/run/pcscd\.pid     --      gen_context(system_u:object_r:pcscd_var_run_t,s0)
/var/run/pcscd\.pub     --      gen_context(system_u:object_r:pcscd_var_run_t,s0)
/var/run/pcscd\.comm    -s      gen_context(system_u:object_r:pcscd_var_run_t,s0)

Now I rebuild and reinstall the policy module.

# make -f /usr/share/selinux/devel/Makefile
Compiling targeted pcscd module
/usr/bin/checkmodule: loading policy configuration from tmp/pcscd.tmp
/usr/bin/checkmodule: policy configuration loaded
/usr/bin/checkmodule: writing binary representation (version 6) to tmp/pcscd.mod
Creating targeted pcscd.pp policy package
rm tmp/pcscd.mod tmp/pcscd.mod.fc

# semodule -i pcscd.pp

# service pcscd restart

# audit2allow -l -i /var/log/audit/audit.log
/usr/bin/audit2allow: No AVC messages found.

# setenforce 1

Now I have a rudimentary policy that will not blow up on start/stop. If I had a test script I could run against the daemon and generate additional avc messages.
I need to figure out how pcscd actually works or use Rawhide to find someone, who uses it and work with them to finish writing the policy.

Now a couple of last comments, when building policy like this look for some strange anomalies. If you see you domain needs to "write" to a file in a standard system domain, (etc_t, var_t, var_run_t, var_lib_t, sbin_t, bin_t, lib_t ...) This probably means you need to define a type and a transition rule for that type. You can use
code similar to the rules generated for the pid file in the example. If you see a domain using fd's owned by another domain, which makes no sense, this could indicate a file descriptor leak by the other domain. If your domain wants uses pam and wants to read shadow_t, you should dontaudit this and force it to follow other paths. Some times do to bad coding programs check if they have write access to files, even though they don't need it, so you can dontaudit this. If your application wants to connect to or bind to a port_t or a reserved_port_t, you should define a type for this port and then use semanage to add the port numbers for it.

Well that is all I can think of right now. Hopefully this is useful to people trying to write SELinux policy.