An Introduction to SELinux

Originally published in SysAdmin Magazine, March 2003

Contents

      SELinux
      Introduction 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
Install any other packages that you want to run - BIND DNS, Apache, Samba, etc.

Also, I recommend configuring the system without a GUI login configured. It may become difficult to login on the GUI console once SELinux has been installed. Edit /etc/inittab and set the default run-level to 3 to disable the X server startup, just to be sure.

With a Linux system installed, its time to download SELinux. You can get the latest from the NSA website at :

http://www.nsa.gov/selinux/src-disclaim.html

Make sure you download the "2.4 LSM-based prototype stable, recommended" version. This version is based on the 2.4.xx kernel with LSM ( Linux Security Module ) support. There are a number of packages which you can download : I chose the "Everything on One Part" package which is a single download and includes a Linux kernel already patched with the LSM code, and all of the SELinux files you need for an installation. Other downloadable packages include patch files that you can apply to the normal kernel source to create an SELinux kernel.

The SELinux kit consists of the following parts :

  • 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.
There are two ways to install SELinux, and they are both clearly documented in the distribution README file. The first and easiest is to run the command 'make quickinstall' as root, this will perform most of the build and installation automatically.

Alternatively, you can follow the step-by-step installation instructions covered in the README file.

During the installation make sure that you enable both "NSA SELinux Support", and "NSA SELinux Development Support" in the kernel configuration under the "Security options" menu. The development support enables you to run your system in a permissive mode which simply logs policy violations without blocking them. Once things are setup and working, you can then use the avc_toggle command to enter enforcement mode which actively blocks policy violations.

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 :
    allow  :object_classes permissions;
for example, from named.te we see the line :
    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.

Summary

SELinux is a practical implementation of mandatory access control being applied to a real-world operating system. By deploying SELinux it is possible to create systems which have a very strong level of security : systems which can even resist being attacked through vulnerabilities in programs running at the highest levels of system privilege.

In this article, I've given a quick overview of what SELinux is, how it works, and how to manage and configure it. There is a large amount of additional information available on the Internet about SELinux and there is an active community continuing to develop SELinux. For example, there are plans to release an SELinux-specific Linux distribution, and plans to implement more advanced policy management systems based on XML.

And finally, does the NSA itself use SELinux? Not surprisingly, the NSA does not comment on such operational issues.