Related to my previous post, this post is about tightening up the default virt-policy, as it comes with the Fedora distribution. The default policy is already pretty tight, but I felt it would be good idea to narrow down the resources qemu is allowed access. If nothing else, it would be a good exercise. And exercise it was indeed.

SELinux is notorious for its complexity and weird policy language. There was a time when people just turned it off because it was blocking a lot of applications, but that time is a distant past. Nowadays people use the targeted SELinux on Fedora without even knowing it’s there. But it’s good to remind in any case: stop disabling SELinux.

At first, I didn’t have any idea where to start looking at the policy. Googling around produces literally hundreds of tutorials explaining what is SELinux user, role and type, and how to set booleans, and even how to write simple policy from scratch, but it’s difficult to find a good comprehensive documentation on how the policy is actually used in system like Fedora. I mean where are the files, how they are related to each other, what to do if some of the modules needs to be tweaked etc. Fortunately, I hit a nice blog post from Lukas Vrabec (he’s also nice guy and may also weep if we keep on disabling SELinux).

First thing is to clone the Fedora SELinux selinux-policy-contrib. I also recommend cloning the base policy selinux-policy, it proved to be good source for documentation.

From the contrib policy, I copied files virt.te, virt.fc and virt.if. These three files basically define the virt module common for all virtualization-related stuff (qemu, lxc, virsh etc.).

virt.te is the actual policy file containing type definitions, booleans, and the actual policies. virt.fc contains some kind of regular expressions for file patterns referenced in the policy. virt.if is the interface for the module, so other modules can reference to it. It also contains some templates used in the virt.te.

The most basic thing for qemu domains is defined in virt.if:

template(`virt_domain_template',`
	gen_require(`
		attribute virt_image_type, virt_domain;
		attribute virt_tmpfs_type;
		attribute virt_ptynode;
		type qemu_exec_t;
		type virtlogd_t;
	')

	type $1_t, virt_domain;

        # Here the basic domain is defined. In order to tigthten up qemu
        # even further, I guess new type of domain should be created.
        # application_domain enables for example read access to all basic
        # readable files (base_ro_file_type).
	application_domain($1_t, qemu_exec_t)

	domain_user_exemption_target($1_t)
	mls_rangetrans_target($1_t)
	mcs_constrained($1_t)
	role system_r types $1_t;

	type $1_devpts_t, virt_ptynode;
	term_pty($1_devpts_t)

        # Commented out these two
	# kernel_read_system_state($1_t)
	# auth_read_passwd($1_t)

	logging_send_syslog_msg($1_t)

	allow $1_t $1_devpts_t:chr_file { rw_chr_file_perms setattr_chr_file_perms };
	term_create_pty($1_t, $1_devpts_t)

	# Allow domain to write to pipes connected to virtlogd
	allow $1_t virtlogd_t:fd use;
	allow $1_t virtlogd_t:fifo_file rw_inherited_fifo_file_perms;
')

As far as I understand, this template defines the running domain for qemu-based virtualization. There was couple of interesting lines I just commented out. I don’t understand why qemu needs to read /etc/passwd, for example. Not much else that could be done here, as explained in the comments.

Moving on, there are couple of interesting things in the virt.te. I just commented out these particular statements in section labeled as “virtual domains common policy”:

# files_read_var_files(virt_domain)
# miscfiles_read_public_files(virt_domain)
# miscfiles_read_generic_certs(virt_domain)
# storage_raw_read_removable_device(virt_domain)
# sysnet_read_config(virt_domain)

This is already a good start! After all these changes, I compiled and installed the new module:

$ make -f /usr/share/selinux/devel/Makefile virt.pp
$ sudo semodule -i virt.pp

Now with my exploit simulation setup explained in the previous post, I could try out the new policy. The guest booted up and executed my “exploit”, so I could log in:

$ nc localhost 9999
ls
bin
boot
dev
etc
....
usr
var
cd /etc
ls
DIR_COLORS
DIR_COLORS.256color
DIR_COLORS.lightbgcolor
....
yum
yum.repos.d
zfs-fuse
cat passwd
cat: passwd: Permission denied
cat hosts
cat: hosts: Permission denied

Nice!

I realize this is only the beginning. The real work is in the domain-policy, which defines much too sloppy policy for the qemu domain. But at the very least, I have now the dev environment to test out things.

Disclaimer: this is very experimental work, use at your own risk. This will break the virt-manager, and auditd is very very busy and happy while doing its job.