Security Enhanced Linux Policy Writing Lab

What this exercise is about

The objective of this lab is to provide you with a practical understanding of the issues related to writing SE Linux policy.

What you should be able to do

At the end of this lab you should be able to:

Write policy for daemons.

Modify existing policy that doesn't match the operation of the system.

Analyse policy with the Tresys tool apol.

Introduction

This lab is based on using SE Linux in Fedora Core 3 test 1. The test release of the next version of Fedora was chosen because there have been significant changes in the SE Linux policy between Fedora Core 2 and Fedora Core 3 test 1, most notably the directory used for storing the policy has changed.

For the lab on the implementation I used Fedora Core 2 because the lab is aimed at people who will use SE Linux soon afterwards. This lab is aimed at people who have more long-term plans or who will be involved in SE Linux development so I think it's best to show how things will work in the future.

Part 1: Installation

For the IBM Technical University the machine will already be installed and you can go to part 2 now, but I include CD-ROM installation instructions for anyone who wants to complete the lab at home.

The first stage is to install Fedora Core 3 Test 1 with SE Linux. This will take about 10-15 minutes of human interaction telling it what to do (as little as 5 minutes if you are familiar with Fedora) and then about 15 minutes to copy the software to the hard disk, and finally a reboot.

At the boot: prompt type linux selinux and press ENTER.

When it says CD Found choose Skip to start the installation (no time to test media).

Use default settings for language and keyboard. Choose anything for monitor type (X windows is not used in this tutorial).

If prompted to “Upgrade an existing installation” you must instead select “Install Fedora Core”.

Select Personal Desktop for the Installation Type.

Select automatically partition in partition type, the default settings will work well.

In Network configuration disable eth0 and set the hostname manually to something that you like.

At Firewall Configuration it does not matter whether you enable the firewall.

Make sure that the Security Enhanced Linux setting is on Active.

Default language should be OK, but change it if you like.

Time zone doesn't really matter.

At "Package Installation Defaults" select "Customize software packages to be installed".

Make sure that you install quagga from Network Servers, the SQL Database Server group (it defaults to Postgresql which is the one we want), the Development Tools group, and any server programs that particularly interest you. Note that selecting excessive numbers of programs to install will take a large amount of time and reduce what you can do in the rest of this session.

Wait for the install to complete and click on the reboot button when prompted.

Part 2: Installing Strict policy

The first thing to do is to change the policy to the strict, the targetted policy (which is default) only limits a small number of programs, the serious policy work is on the strict policy.

Login as root, edit /etc/selinux/config and change SELINUXTYPE=targetted to SELINUXTYPE=strict. Many programs use this, restorecon uses it to determine which file_context file to use, and init uses it to determine which policy database to use at boot time.

Run setenforce 0 to put the machine in permissive mode, the next operations will invalidate the contexts of some processes and cause many operations not to be permitted.

Run load_policy /etc/selinux/strict/policy/policy.18 to load the strict policy.

Run setfiles /etc/selinux/strict/contexts/files/file_contexts / /boot to label all files on the file system with the contexts for the strict policy. Ignore all the errors and type "reboot" when it's complete.

Part 3: Installing Extra Packages

If you are running the kick-start then skip to part 4.

Insert disc 3 and run the following commands to install the policy compilation program:

mount /dev/cdrom /mnt

rpm -i /mnt/Fedora/RPMS/checkpolicy-1.14.1-1.i386.rpm

umount /mnt

Insert disc 4 and run the following commands to install the source to the strict policy:

mount /dev/cdrom /mnt

rpm -i /mnt/Fedora/RPMS/selinux-policy-strict-sources-1.14.1-5.noarch.rpm

umount /mnt

Part 4: Fixing Errors in the Default Policy

When the machine boots login as root and run the command "dmesg | grep avc > ~/errors".

View the file ~/errors and observe that the following line is present:

avc: denied { create } for pid=1180 exe=/usr/sbin/zebra scontext=system_u:system_r:zebra_t tcontext=system_u:system_r:zebra_t tclass=unix_dgram_socket

Change directory to the policy source directory /etc/selinux/strict/src/policy and edit the file domains/program/zebra.te . As there is usually no benefit in restricting what a program can do with a Unix domain socket it creates we will grant zebra_t full access with the following policy line:

allow zebra_t self:unix_dgram_socket create_socket_perms;

The next zebra_t error message we see in ~/errors is the following:

avc: denied { setgid } for pid=1180 exe=/usr/sbin/zebra capability=6 scontext=system_u:system_r:zebra_t tcontext=system_u:system_r:zebra_t tclass=capability

This shows that the zebra daemon wants to change it's GID, a common operation for a daemon start-up process that we want to allow. So we change this line:

allow zebra_t self:capability { net_admin net_bind_service };

To this:

allow zebra_t self:capability { setgid net_admin net_bind_service };

Edit the file Makefile and comment out line 88:

$(CHECKPOLICY) -c 17 -o $(POLICYPATH)/policy.17 policy.conf .

The reason for commenting this out is to prevent needless compiles (the kernel we are using is compiled with support for policy version 18 so the old version is just a waste of compilation time and disk space.

Now run make load to apply the change.

If you are logged in at a virtual console (text mode login) then you will see AVC messages from the kernel displayed on the screen. If you are logged in graphically then start a new terminal window and run tail -f /var/log/messages so that you can see the AVC messages in real time.

Restart zebra with the command /etc/init.d/zebra restart and observe the following AVC message:

avc: denied { setuid } for pid=1910 exe=/usr/sbin/zebra capability=7 scontext=root:system_r:zebra_t tcontext=root:system_r:zebra_t tclass=capability

To fix this change the capability line to the following:

allow zebra_t self:capability { setgid setuid net_admin net_bind_service };

Now run "make load" and restart zebra again and observe the following message:

avc: denied { setcap } for pid=1988 exe=/usr/sbin/zebra scontext=root:system_r:zebra_t tcontext=root:system_r:zebra_t tclass=process

Add the following line to permit this:

allow zebra_t self:process setcap;

Run the command audit2allow -d , this reads the kernel message log via "dmesg" and then outputs a series of SE Linux policy rules to permit the operations that are logged with avc: denied messages. Note that the commands are similar to the ones that you wrote apart from the unix_dgram_socket entry.

Run make load and restart zebra. Observe the following new messages:

avc: denied { create } for pid=2067 exe=/usr/sbin/zebra scontext=root:system_r:zebra_t tcontext=root:system_r:zebra_t tclass=rawip_socket

avc: denied { create } for pid=2067 exe=/usr/sbin/zebra scontext=root:system_r:zebra_t tcontext=root:system_r:zebra_t tclass=netlink_route_socket

avc: denied { name_bind } for pid=2068 exe=/usr/sbin/zebra scontext=root:system_r:zebra_t tcontext=system_u:object_r:port_t tclass=tcp_socket

To fix these add the following rules:

allow zebra_t self:rawip_socket create_socket_perms;

allow zebra_t self:netlink_route_socket { create r_netlink_socket_perms };

allow zebra_t port_t:tcp_socket name_bind;

auditallow zebra_t port_t:tcp_socket name_bind;

The reason we want to audit the port_t binding is that we want to discover what port is being used and then assign a label to it.

Now run make load and restart zebra to observe the following audit messages:

avc: denied { net_raw } for pid=2297 exe=/usr/sbin/zebra capability=13 scontext=root:system_r:zebra_t tcontext=root:system_r:zebra_t tclass=capability

avc: granted { name_bind } for pid=2296 exe=/usr/sbin/zebra scontext=root:system_r:zebra_t tcontext=system_u:object_r:port_t tclass=tcp_socket

Change the capability line to the following to permit net_raw access:

allow zebra_t self:capability { setgid setuid net_admin net_bind_service net_raw };

Run the command lsof | grep zebra.*TCP and you will see that zebra is listening on port discp-client, which according to /etc/services is 2601.

Now you want to assign that port to zebra. Firstly in zebra.te add the following lines:

type zebra_port_t, port_type;

allow zebra_t zebra_port_t:tcp_socket name_bind;

Remove the lines concerning port_t as they are now obsolete.

Now edit the file net_contexts and add the following:

ifdef(`zebra.te', `portcon tcp 2601 system_u:object_r:zebra_port_t')

This means that if zebra.te is included in the policy build then we want to assign the type zebra_port_t to the TCP port 2601. This prevents other programs from binding to that port.

Now run make load and restart zebra. Now it should start with no errors. Of course we would need to do some real tests to see if it is permitted to do everything it needs to do, but unfortunately we don't have a test network available for this.

In the file ~/errors you will find entries such as the following:

avc: denied { search } for pid=3253 exe=/bin/bash name=pgsql dev=dm-0 ino=83331 scontext=user_u:user_r:user_t tcontext=system_u:object_r:postgresql_db_t tclass=dir

This is because Postgresql is started through running su(1), in Fedora su is modified to change the context of the process (and it's controlling terminal) for the new session, with a default context of user_u:user_r:user_t, which is obviously not correct for a daemon. su is used so that profile scripts such as ~/.bash_profile are executed. The problem with this is that such scripts are writable by the postgres user and thus the postgres user can stuff key-presses into the input buffer of the controlling terminal, this controlling terminal is in many instances the terminal of an administrative shell, and commands such as "chmod 666 /etc/shadow" could be executed.

To solve this I have written a program named init_su to provide the necessary functionality from su(1) without the terminal issue. Please copy init_su to /usr/local/bin (it will already be there if you used th kick-start install from the IBM Technical University).

Modify /etc/init.d/postgresql at lines 165, 182, 200, and 227 to call /usr/local/bin/init_su instead of su and then start postgresql. Observe that postgresql now starts correctly.

init_su closes all file handles other than 1 and 2 (stdout and stderr). File handles 1 and 2 are fstat()'d, if they are regular files or pipes then they are left open (no attack is possible through a file or pipe), otherwise they are closed and /dev/null is opened instead. /dev/null is opened for file handle 0 regardless of what it might have pointed to previously. Then setsid() is called to create a new session for the process (make it a group leader), this invalidates /dev/tty. Then the uid is changed and the daemon is started.

Part 5: Writing Policy for a new Daemon

Install the package howl (for the Technical University install the rpm will be in the root home directory). This contains a daemon named nifd which needs policy.

To start writing the policy create a file named domains/program/howl.te and put the following in it:

daemon_domain(howl)

Then create the file file_contexts/program/howl.fc and put the following in it:

/usr/bin/nifd -- system_u:object_r:howl_exec_t

/usr/bin/mDNSResponder -- system_u:object_r:howl_exec_t

/var/run/nifd.pid -- system_u:object_r:howl_var_run_t

Run make load to load the policy and install the new file contexts file and then run the following command to label the files:

restorecon /usr/bin/nifd /usr/bin/mDNSResponder

Now run the command /etc/init.d/nifd start , you will see the following AVC message:

avc: denied { read } for pid=11292 exe=/usr/bin/nifd name=dev dev=proc ino=-268435210 scontext=root:system_r:howl_t tcontext=system_u:object_r:proc_t tclass=file

To permit that operation add the following policy to domains/program/howl.te :

allow howl_t proc_t:file { getattr read };

Note that almost always when you grant read access to a file you should grant getattr access as well. Often programs stat a file after they finish reading it so you won't see an audit message about getattr access unless read access is granted. In any case if you permit reading a file then there is usually no benefit in preventing the file being stat'd.

Run make load and /etc/init.d/nifd start and you will see the following AVC message:

avc: denied { create } for pid=11349 exe=/usr/bin/nifd scontext=root:system_r:howl_t tcontext=root:system_r:howl_t tclass=udp_socket

The current proceedure is that whenever an application tries to create a udp_socket or tcp_socket object it is granted all the basic network privs with the can_network() macro. So add the following policy to domains/program/howl.te :

can_network(howl_t)

Now run make load and start the daemon. You will now see repeating avc messages so run the command killall -9 nifd to stop it.

The messages are:

avc: denied { net_admin } for pid=11404 exe=/usr/bin/nifd capability=12 scontext=root:system_r:howl_t tcontext=root:system_r:howl_t tclass=capability

Grant this access through the policy:

allow howl_t self:capability net_admin;

For mDNSResponder to work you have to edit the file /etc/init.d/mDNSResponder and change su to /usr/local/bin/init_su at line 27 for the same reason as with Postgresql.

Run /etc/init.d/mDNSResponder start to start the daemon and observe the following audit message:

avc: denied { read } for pid=3426 exe=/usr/bin/mDNSResponder path=pipe:[6409] dev=pipefs ino=6409 scontext=root:system_r:howl_t tcontext=root:system_r:howl_t tclass=fifo_file

To permit this operation (the program communicating with it's children via pipes) add the following to the policy:

allow howl_t self:fifo_file rw_file_perms;

Load the policy and try to start mDNSResponder again and you will get the following audit message:

avc: denied { name_bind } for pid=3530 exe=/usr/bin/mDNSResponder scontext=root:system_r:howl_t tcontext=system_u:object_r:port_t tclass=tcp_socket

The solution to this is the same as in the case of Zebra, put the following lines in the policy as a temporary measure:

allow howl_t port_t:tcp_socket name_bind;

auditallow howl_t port_t:tcp_socket name_bind;

Now load the policy, start the daemon, and you will observe the following audit messages:

avc: granted { name_bind } for pid=3614 exe=/usr/bin/mDNSResponder scontext=root:system_r:howl_t tcontext=system_u:object_r:port_t tclass=tcp_socket

avc: denied { read } for pid=3613 exe=/usr/bin/mDNSResponder name=mDNSResponder.conf dev=dm-0 ino=66341 scontext=root:system_r:howl_t tcontext=system_u:object_r:etc_t tclass=file

avc: denied { read write } for pid=3613 exe=/usr/bin/mDNSResponder name=mDNSResponder.pid dev=dm-0 ino=83360 scontext=root:system_r:howl_t tcontext=root:object_r:initrc_var_run_t tclass=file

The granted message about the name_bind operation was expected and we will deal with it last. The etc_t file acess is a minor issue. The initrc_var_run_t file access indicates a problem with the startup script.

initrc_var_run_t is the type assigned to files that the domain initrc_t creates under /var/run. If we have several processes that need read/write access to files of that type then they can interfere with each other's pid files which can be used to trick a script into sending a signal to the wrong process. As a temporary measure we will allow this access, but long-term the daemon has to be changed. This will be secure enough as long as other daemons do not run as nobody, however we won't get the results we desire from policy analysis if we allow this.

To solve the initrc_var_run_t and etc_t issues add the following to the policy, load the policy, and try starting the daemon again:

allow howl_t etc_t:file { getattr read };

allow howl_t initrc_var_run_t:file rw_file_perms;

Run the command lsof | grep mDNS.*TCP and you will see that the daemon is listening on TCP port 5353.

To permit howl_t and nothing else to bind to that port add the following lines to domains/program/howl.te and remove the obsolete lines concerning port_t:

type howl_port_t, port_type;

allow howl_t howl_port_t:tcp_socket name_bind;

Then add the following to net_contexts:

ifdef(`howl.te', `portcon tcp 5353 system_u:object_r:howl_port_t')

Note that the ifdef is needed so that the policy will still compile even when howl.te is not included in the build. Referencing a type or attribute that is not defined is a compile error.

After loading the policy you should be able to start mDNSResponder without any audit messages.

Part 6: Analysing Policy

Run the command apol to analyse the policy, tell it to open the file policy.18 from the policy source directory. Run a second copy of apol at the same time and tell it to open the file policy.conf . The first exercise is to see the difference between analysing policy source and policy binary.

When apol starts it will be in the Policy Components tab and the Types sub-tab. Double-click on some of the types in policy.conf and observe that you are shown the attributes from those types. Double-click on the same types in policy.18 and observe that you are not shown any attributes, and that the attributes list-box is empty. Information on attributes is discarded at policy compilation time. Any policy analysis which concerns attributes must be performed on policy source.

Select the Policy Rules tab and the TE Rules sub-tab. In the Rule Selection box select only type_trans (for domain transitions on execute and for type transitions on file creation). Inside the Types/Attributes tab select the Use Source Type/Attrib button and enter sysadm_t in the entry field. Now click the New button to execute the search.

Once you have done this on both the policy.18 and policy.conf files observe the difference in the results. The policy.conf analysis gives blue links which you can click on to jump to the source code line (which may have comments to explain it's purpose). Also note that the policy.18 analysis gives significantly more results for this query and often gives more results for other queries, this is because one rule in the policy source may correspond to several rules in the policy (although in some situations one rule in policy source may give zero rules in the policy binary).

This demonstrates that analysing policy.conf is more useful than analysing policy.18, so close the copy of apol that is analysing policy.18.

Go back to the Policy Components tab and then select each sub-tab under it in turn and double-click on random items in the list boxes to see information on them. Ask me if you have any queries about what they do.

.

IBM Learning Services	Page 7 of 10

WF021004	©Copyright Russell Coker

Course material may not be reproduced in whole or in part Without the prior consent of IBM

SE Linux lab

IBM_User

2004-07-09T13:36:00