An Introduction to SELinux
Originally published in SysAdmin Magazine, March 2003Contents
SELinuxIntroduction and History
Models for Access Control
Installing SELinux
Using SELinux
Managing the SELinux Security Policy
Configuring a policy for the BIND DNS server
Summary
SELinux
Security Enhanced Linux is an extension to the standard Linux kernel which has been designed to enforce strict access controls that confine processes to the minimum amount of privilege that they require. In this article I will go over the ideas behind SELinux and how to install, configure and manage an SELinux system. As an example of configuring a security policy, I'll show how to configure a BIND-based DNS server with an example security policy which restricts the DNS server to accessing only those files which it requires for operation. Note: this article refers to SELinux as it was implemented in the Linux 2.4 kernel. It has changed a lot since the 2.6 kernel was released. This article is published for informational use only.Introduction and History
Released late in 2000 by the the US National Security Agency ( NSA ), SELinux was developed with cooperation from such security heavyweights as NAI Labs, Secure Computing Corporation, and MITRE Corporation. The NSA Information Assurance Research Office continues to guide SELinux development, it is this office that is responsible for carrying out research and development of solutions to achieve a high level of information security critical to government and industry. Following the initial release of SELinux, the Linux community soon realized that the standard kernel needed to be extended to provide more flexibility for security add-ons. From this came the Linux Security Module ( LSM ) version of the Linux kernel which provides for the modular addition of security extensions to the standard Linux kernel. SELinux was then changed to be built as an LSM module, and it is the LSM implementation of SELinux that I will be covering in this article. The full source code for SELinux was released to the Open Source community with the aim of creating a viable secure operating system. With the assistance of open-source developers worldwide, SELinux is quickly becoming accepted as a mainstream operating system which can provide a high level of security through mandatory access control.Models for Access Control
Security experts use a number of models to describe security access control systems. Probably the most common of these is Discretionary Access Control ( DAC ). DAC is the model commonly used by Unix systems - it describes how each user has complete control over the files that they own and the programs that they use, and programs run by a user will have all of the rights that the user has. A user can allow others access to her objects at her discretion, and under such a model the level of security of a system is left to the discretion of the applications running on it. For instance, if a program running as the root user is compromised ( such as through a buffer overflow, or a misconfiguration ) then the attacker could gain root privileges, and the security of the entire system has been compromised. Another security model is Mandatory Access Control ( MAC ). If you've configured rules for a firewall system then you will know what a MAC model is like : the strict security access controls are defined by an administrator and cannot be changed by anybody else. A system with a MAC model operates under very strictly defined rules, the key one of which is usually "that which is not expressly permitted, is denied". Implementing a MAC model on a Unix system would typically be a very difficult job : defining rules for every user to use every program to access every object would result in an enormous set of rules. To make things easier we can use the concept of Role-Based Access Control ( RBAC ). Under RBAC an administrator can define roles and then allow certain users access to those roles. For example, an accountant would want read and write access to the accounts database but nothing else, and a system auditor should be able to read all of the system logs and configuration files, but never be able to write to them. By defining roles in a system, defining what objects certain roles can access, and allowing various users to become various roles the task of defining a mandatory access security policy is greatly simplified. There are very few operating systems available with such a level of access control. The main goal of the release of SELinux is to demonstrate that such highly secure operating systems are practical and viable in the world outside of the government and military. SELinux is a practical implementation of a Mandatory Access Control model based on the Linux kernel. Put simply, the administrator of an SELinux system has the ability to setup a security policy on the system to define what programs can access which files. You could think of it as having the ability to place a firewall around every user and process on the system. To do this, SELinux implements a mechanism to tag each and every process and file with a security context to which it belongs and it adds a security enforcement module into the Linux kernel to permit or deny all accesses to objects such as files and devices. The security policy defined by the administrator is accessed by the kernel through a security server process which runs as a part of the kernel and decides which subjects ( processes, users ) can access which objects ( files, devices ). In SELinux, this control mechanism is called Type Enforcement ( TE ). If a process which is running as the root user on an SELinux system is compromised ( such as through a buffer overflow, etc. ) then the damage is limited to what that process can access as defined by the policy. Having the security policy definition external to the kernel allows for easier management and dynamic changing of the policy at run time. SELinux also includes RBAC controls and has the ability to assign roles to users and processes and to control the switching of roles. SELinux also has the ability to implement the Multi-Level Security model ( MLS ). Under this model objects such as files in the system can be classed with layers of security, such as "Top Secret", "Secret", "Confidential", and "Unrestricted". The theory being that information can only be passed from a lower level to an upper level and never flow the other way. MLS is usually only used in military and government multiuser systems which demand an extremely high level of security. I won't be covering MLS in this article. But that is enough theory. Its time to look at how SELinux actually works.Installing SELinux
Start off by installing a Linux system. For my research I used RedHat Linux 7.3 which has been reliable and stable. Other distributions known to work include Debian, Suse, and Mandrake. Any Linux with a standard configuration should work fine. When installing the Linux system, be sure to do the following :- use ext2 or ext3 type filesystems
- install the kernel development kit
- install the gcc compiler, I used GCC v2.96
- use Grub ( or Lilo ) as your bootloader
- a standard Linux kernel
- the Linux Security Module ( LSM ) add-on
- SELinux kernel modules
- an extra patch for SELinux which needs to be applied
- SELinux management programs to tag files, build the policy, etc.
- a standard set of policy files for RBAC and TE policies
- some SELinux "aware" programs to replace standard ones : cp, find, id, ls, mkdir, etc.
Using SELinux
At the end of the installation, you should have all of the SELinux support and management software in place. If your PATH is set properly, you should be able to run the SELinux specialized programs. For example, the SELinux ps command can show the security context of running processes : [root@xena /]# ps -e --context
PID SID CONTEXT COMMAND
1 7 system_u:system_r:init_t init
2 1 system_u:system_r:kernel_t [keventd]
3 1 system_u:system_r:kernel_t [kapmd]
4 1 system_u:system_r:kernel_t [ksoftirqd_CPU0]
5 1 system_u:system_r:kernel_t [kswapd]
6 1 system_u:system_r:kernel_t [bdflush]
7 1 system_u:system_r:kernel_t [kupdated]
8 7 system_u:system_r:init_t [khubd]
9 7 system_u:system_r:init_t [kjournald]
515 274 system_u:system_r:syslogd_t syslogd -m 0
520 283 system_u:system_r:klogd_t klogd -x
706 295 system_u:system_r:ntpd_t /usr/sbin/ntpd -U ntp -g
757 296 system_u:system_r:named_t /usr/local/sbin/named -u named
778 297 system_u:system_r:sshd_t /usr/sbin/sshd
910 305 system_u:system_r:gpm_t gpm -t ps/2 -m /dev/mouse
928 306 system_u:system_r:crond_t crond
982 310 system_u:system_r:xfs_t xfs -droppriv -daemon
1018 311 system_u:system_r:atd_t /usr/sbin/atd
1319 312 system_u:system_r:getty_t /sbin/mingetty tty2
1620 312 system_u:system_r:getty_t /sbin/mingetty tty1
2726 297 system_u:system_r:sshd_t /usr/sbin/sshd
2728 323 root:user_r:user_t -bash
2920 323 root:user_r:user_t ps -e --context
[root@xena /]#
The output from ps here shows an extra two columns giving the SID (
Security IDentifier tag ) and the associated user, role, and type for
the process being displayed. Similarly, the ls command also shows the
context tags for files :
[root@xena /]# ls -l --context
drwxr-xr-x root root system_u:object_r:bin_t bin
drwxr-xr-x root root system_u:object_r:boot_t boot
drwxr-xr-x root root system_u:object_r:device_t dev
drwxr-xr-x root root system_u:object_r:etc_t etc
drwxr-xr-x root root system_u:object_r:file_t initrd
drwxr-xr-x root root system_u:object_r:lib_t lib
drwx------ root root system_u:object_r:lost_found_t lost+found
drwxr-xr-x root root system_u:object_r:file_t misc
drwxr-xr-x root root system_u:object_r:file_t mnt
dr-xr-xr-x root root system_u:object_r:proc_t proc
drwxr-x--- root root system_u:object_r:sysadm_home_dir_t root
drwxr-xr-x root root system_u:object_r:sbin_t sbin
drwxrwxrwt root root system_u:object_r:tmp_t tmp
drwxr-xr-x root root system_u:object_r:usr_t usr
drwxr-xr-x root root system_u:object_r:var_t var
[root@xena /]#
Note that system_u user is assigned to all files at installation
time. For files created after installation, the user context will be
assigned the user identity of the creating process. Since the notion
of a role is irrelevant for files, all files are assigned the object_r
role.
If you've configured your kernel with the SELinux Development Support,
then your system will be running in permissive mode. In other words,
instead of blocking those functions not permitted by the security
policy it will simply be logging the illegal activities and allowing
them to proceed. Examining messages in /var/log/messages should show a
number of these. By running the avc_toggle command you can switch the
kernel into the enforcement mode, where illegal functions will
actually be blocked. Even if you're logged in as root, after entering
enforcement mode you may find you can't do some things. Consider the
following example :
[root@xena /]# id
uid=0(root) gid=0(root) groups=0(root) context=root:user_r:user_t sid=323
[root@xena /]# avc_toggle
enforcing
[root@xena /]# tail /var/log/messages
tail: /var/log/messages: Permission denied
[root@xena /]# avc_toggle
avc_toggle: Permission denied
[root@xena /]#
In this example, I'm logged in as root and I switched the kernel into
enforcing mode with avc_toggle. Unfortunately, under SELinux there is
no such thing as the root user being all-powerful, as demonstrated
when I try to display the tail of the messages file, or even switch
enforcement mode off. Its not very often that you see "Permission
denied" on a Unix system when logged in as root! I need to have access
to the system administrator role to be able to do this under
SELinux. So I switch role to sysadm_r using the SELinux newrole
command, and things work better :
[root@xena /]# newrole -r sysadm_r
Authenticating root.
Password:
[root@xena /]# tail /var/log/messages
Jan 20 10:53:22 xena kernel: avc: denied { avc_toggle } for pid=12592
exe=/usr/local/selinux/bin/avc_toggle scontext=root:user_r:user_t
tcontext=system_u:system_r:kernel_t tclass=system
Jan 20 10:53:30 xena kernel: avc: denied { read } for pid=12593
exe=/usr/bin/tail path=/var/log/messages dev=03:01 ino=23230
scontext=root:user_r:user_t tcontext=system_u:object_r:var_log_t tclass=file
Jan 20 10:53:35 xena kernel: avc: denied { avc_toggle } for pid=12594
exe=/usr/local/selinux/bin/avc_toggle scontext=root:user_r:user_t
tcontext=system_u:system_r:kernel_t tclass=system
[root@xena /]# avc_toggle
permissive
[root@xena /]#
In the tail of the messages file here you can see three denials of
access. The first is the first avc_toggle that I ran while in
permissive mode : it got logged and was performed anyway switching the
kernel into enforcement mode. The second is when I tried to run tail
while logged in as root, and being in enforcement mode the tail
command was actively denied access to the messages file. The third was
an attempt to get out of enforcement mode before I switched to the
sysadm_r role.
Next, we will look at how the SELinux security policy is defined and
managed.
Managing the SELinux Security Policy
Defining security policies on SELinux is a large topic in itself. There are a number of papers longer than this article on the subject, so here I will cover only the minimum topics that you will need to know to get things working in a simple manner. By default, the installation will install the policy definition files into directories under /etc/security/selinux/src - if it hasn't, then copy everything in the selinux/policy directory from the distribution package into /etc/security/selinux/src. The "users" file contains user and role definitions. If you want your users to be able to switch roles then they should have entries in this file. Under the directory "file_contexts" is the definitions for the labelling of files on the system. In file_contexts/program there are file context definitions for each type of program on the system. For example, file_contexts/program/ntpd.fc contains labelling definitions for ntpd programs : /var/lib/ntp(/.*)? system_u:object_r:var_lib_ntp_t
/etc/ntp.conf system_u:object_r:etc_ntp_t
/usr/sbin/ntpd system_u:object_r:ntpd_exec_t
/var/log/ntpstats(/.*)? system_u:object_r:var_log_ntp_t
/var/log/ntpd.* system_u:object_r:var_log_ntp_t
/etc/cron.(daily|weekly)/ntp-simple system_u:object_r:ntpd_exec_t
The first column contains a regular-expression to match against file
names. The second column contain the context with which the matching
files are to be labelled. The command 'make relabel' will read all of
the file context definitions and apply them to all local filesystems -
this can take 10 minutes or more to run.
Under the directory "domains" is where the core of where the security
policy definition lies. There are a number of files in the
"domains/program" directory which contain m4 macro definitions for
many common programs. The command "make load" will rebuild and reload
the kernel security policy from these files.
Configuring a policy for a BIND DNS server
SELinux is compatible with the normal Linux kernel so that an SELinux system is capable of running any software compiled to run on Linux. To evaluate the mandatory access control features of SELinux I installed the latest copy of the BIND DNS server on a SELinux system. While the BIND server can be secured by running it in a chroot "jail", on my test system I'll run it as a normal service. I downloaded the latest BIND ( 9.2.2rc1 in my case ), and then ran the usual configure and installation : wget ftp://ftp.isc.org/isc/bind9/9.2.2rc1/bind-9.2.2rc1.tar.gz
gzip -cd
I then setup my /etc/named.conf configuration file, which simply
contains the following :
## named.conf - configuration for bind
#
options {
directory "/var/named";
pid-file "/var/named/named.pid";
};
zone "." {
type hint;
file "named.ca";
};
zone "example.com" {
type master;
file "example.zone";
allow-transfer { any; };
allow-update { 192.168.1.0/24; };
};
I then setup the named.ca and example.zone zone files in
/var/named. Note that I've got dynamic updates enabled for the
example.com domain.
Next, we need to label the files that the "named" process will be
using. SELinux comes with a predefined configuration for named, but
this needs to be changed to the slightly different file locations that
are on my system. Editing the SELinux policy file
file_contexts/program/named.fc I setup labelling for the new files :
# ./file_contexts/program/named.fc
# SELinux file contexts for BIND 9.2.2rc1
/var/named system_u:object_r:named_zone_t
/var/named/.* system_u:object_r:named_conf_t
/var/named/(.*).zone system_u:object_r:named_zone_t
/var/named/(.*).jnl system_u:object_r:named_zone_t
/var/named/named.pid system_u:object_r:named_zone_t
/etc/named.conf system_u:object_r:named_conf_t
/etc/rndc.key system_u:object_r:rndc_conf_t
/usr/local/sbin/named.* system_u:object_r:named_exec_t
/usr/sbin/lwresd system_u:object_r:named_exec_t
Note that the ordering of entries in the named.fc file is
significant. The directory itself will be labelled with
system_u:object_r:named_zone_t, all files within this directory will
be labelled as system_u:object_r:named_conf_t, except for *.zone zone
files, *.jnl journal files, and the named.pid file. I label named.pid
the same as a zone file because named reads and writes to the file in
just the same way as it does for a zone or jnl file.
Next, create the Type Enforcement rules in
domains/program/named.te. Again, SELinux comes with a suitable file
which needs little editing. Below is an excerpt from the file covering
the main areas of interest :
#################################
#
# Rules for the named_t domain.
#
type named_port_t, port_type;
type rndc_port_t, port_type;
daemon_domain(named)
can_exec(named_t, named_exec_t)
allow named_t sbin_t:dir search;
allow named_t self:process setsched;
# named configuration file types
type named_conf_t, file_type, sysadmfile;
type rndc_conf_t, file_type, sysadmfile;
# master zone file types
type named_zone_t, file_type, sysadmfile;
# slave zone file types
type named_cache_t, file_type, sysadmfile;
# allow reading of files in /etc
allow named_t etc_t:{ file lnk_file } { getattr read };
allow named_t etc_runtime_t:{ file lnk_file } { getattr read };
allow named_t resolv_conf_t:file { getattr read };
# Named can use network devices
can_network(named_t)
# allow UDP transfer to/from any program
can_udp_send(domain, named_t)
can_udp_send(named_t, domain)
can_tcp_connect(domain, named_t)
# Bind to the named port.
allow named_t named_port_t:udp_socket name_bind;
allow named_t { named_port_t rndc_port_t }:tcp_socket name_bind;
# read configuration files
r_dir_file(named_t, named_conf_t)
# read/write dynamic zone files
rw_dir_create_file(named_t, named_zone_t)
allow named_t named_zone_t:file setattr;
# write cache for secondary zones
rw_dir_create_file(named_t, named_cache_t)
# Read /proc/cpuinfo.
allow named_t proc_t:dir r_dir_perms;
allow named_t proc_t:file r_file_perms;
# Read /dev/random.
allow named_t device_t:dir r_dir_perms;
allow named_t random_device_t:chr_file r_file_perms;
There are basically two types of statements in a TE policy file : type
statements and allow statements. Type statements simply define a
context type which will be assigned to file(s). Allow statements
define what operations are allowed, these are of the form :
allowfor example, from named.te we see the line ::object_classes permissions;
allow named_t resolv_conf_t:file { getattr read };
this entry allows the named process to perform reads and get
attributes on files tagged with resolv_conf_t. Other entries in the
file are m4 macros, for example :
rw_dir_create_file(named_t, named_zone_t)is a macro to allow processes running in the named_t context ( the named process ) to read, write, and create files in directories labelled with named_zone_t. Once the file contexts and policy have been defined for named, we can label the files and load the policy. To label the files, simply run 'make relabel' from the policy directory. Then check that the files are labelled correctly with the 'ls --context' command. Once the files have been labelled, the policy can be loaded by running "make load" from the policy directory. This collects all of the m4 TE files together into the policy.conf file and compiles loads the policy into the running SELinux kernel. The full policy is defined in the policy.conf file which is a huge : 150,000 lines on my system. This goes to show how big a job it can be to define a mandatory security policy even on a relatively simple system. A useful utility for writing policy rules is the newrules.pl perl program which is in the scripts directory of the selinux release. By processing "denied" lines from the messages file, newrules.pl can generate rules to allow those functions which were denied. For example, if we take the 3 denied lines from the messages file listed in Example 4 the following rules will be generated :
[root@xena selinux]# tail /var/log/messages | ./scripts/newrules.pl
allow user_t kernel_t:system { avc_toggle };
allow user_t var_log_t:file { read };
[root@xena selinux]#
Of course, the output from newrules.pl should be checked to make sure
you're not permitting too much access when you add the rules to your
policy.
With a policy configured for the named process, we can now start it up
and monitor its progress with the system in permissive mode. Security
violations will be logged into syslog ( in the /var/log/messages file
), and we can use these logs to get our policy correct before
switching the system into enforcement mode.