Playing with SELinux ... part 1

This Friday I am going to have a speech/workshop at DeveloperConference2011. Together with Eduard Benes we will guide you through a process of using permissive domains for developing a SELinux policy and using it for your advantage and tell you something about sVirt.

I decided to write a blog related to this workshop. You can go through this blog, try it and prepare some questions. I would also recommend you to look at writing SELinux Policy blog in which Dan Walsh explains some basics of SELinux.

But now back to my blog ;-). I was thinking during my Friday's travelling which a good example I would show you. An example which really works. I chose the combination of apache, CUPS and portreserve services (btw. portreserve was my first policy written in Red Hat).

Ready to play with SELinux?

First, we need to setup our environment. Stop all intended services and remove the default portreserve policy. We also need to run the restorecon command to restoring contexts.

# for s in portreserve cups httpd; do service $s status 2>&1 >/dev/null; if [ $? -eq 0 ]; then service $s stop; fi; done;
# semodule -r portreserve
# for files in `rpm -ql portreserve | grep -E "(etc|bin|log|lib|run)"`; do restorecon -R -v $files; done;
# service portreserve start

Now you can check the context of portreserve service.

# ps -eZ | grep initrc
system_u:system_r:initrc_t:s0 3374 ? 00:00:00 portreserve

What happened while we were starting the service? Init script executing the portreserve binary labeled bin_t did not transition and stayed in the same context as the parent process.

initrc_t -> bin_t -> initrc_t

Really important to understand.

!! SELinux is all about labels!!

If we had tried to setup the cupsd_exec_t context for the portreserve binary, the service would have run in the cupsd_t domain.

initrc_t -> cupsd_exec_t -> cupsd_t

since we define this transition. So let's create a new policy which will say

initrc_t -> portrserve_exec_t -> portreserve_t

Use the sepolgen command.

# sepolgen –help

Since the portreserve is a standard init daemon.

# service portreserve stop
# sepolgen -t 0 `which portreserve`
Created the following files in:
portreserve.te # Type Enforcement file
portreserve.if # Interface file
portreserve.fc # File Contexts file # Setup Script

Install the portreserve policy.

# sh

Do some checks.

# semodule -l | grep portreserve
portreserve 1.0.0
# ls -Z `which portreserve`
-rwxr-xr-x. root root system_u:object_r:portreserve_exec_t:s0 /sbin/portreserve

It was really easy ;-). We have now the basic portreserve policy. Why not try it?

# service portreserve start
# ps -eZ | grep portre
system_u:system_r:portreserve_t:s0 3498 ? 00:00:00 portreserve

Maybe you would like to ask me: "Is this policy really working? We have Enforcing mode and the daemon is running". Well I have two answers for you ;-).

1. You can check how is the initial policy good using

# ausearch -m avc -ts recent

Probably you will see some AVC messages which are really important for the next steps.

2. The magic there is the portreserve is running as a permissive domain.

This means while SELinux access ckecks are performed for these domains, they are not enforced. The kernel allows the access and reports it as an AVC denial. We can push out a new policy as permissive domain and simply collect AVC messages. Users don’t have to switch to permissive mode globally and they can stay in the enforcing mode.

Now we can easy to complete our policy using ausearch, audit2allow tools (which are my favourite)

# service portreserve restart
# ausearch -m avc -ts today | grep portreserve | audit2allow -R >> portreserve.te

Compile and load it:

# make -f /usr/share/selinux/devel/Makefile
# semodule -i portreserve.pp

Now we should test if the policy works without the "permissive domain" declaration.

# date_time=`date | cut -d " " -f 5`
# sed -i s/^permissive/#permissive/ portreserve.te
# make -f /usr/share/selinux/devel/Makefile
# semodule -i portreserve.pp
# service portreserve restart 2>&1 >/dev/null; echo $?;
# 0

Looks good. Maybe one additional check would fine

# ausearch -m avc -ts $date_time
no matches