danwalsh (danwalsh) wrote,

Confining the unconfined. Oxymoron?

When we first designed targeted policy, we defined a domain that allowed users and administrators to login and have the same access privileges they would have had if SELinux was disabled.  Similarly, we wanted to allow third party applications to be installed and run without requiring the administrator/user to write special policy rules for these applications.   They would just work.

We designed the unconfined domain for this.

The unconfined domain originally was written as a rule like


meaning processes running as the unconfined_domain are allowed to do everything SELinux can deny.  Unconfined domains are thus an exception to the way an SELinux system is usually written.

Over time we have had requests to add exceptions to the "unconfined can do anything" rule.

We confine the unconfined domain in a  couple of different ways.

One way to confine an unconfined domain is via process transitions.  We can write rules to specify that an unconfined_domain will transition into a different, confined, domain when it executes a program with a certain label.

On most targeted systems, the init scripts run as initrc_t, an unconfined domain.  When an init script executes a file labeled httpd_exec_t, the process transitions to the httpd_t domain, a confined domain.  Another process that transitions from an unconfined domain to a confined one is the unconfined_t domain, the domain used by unconfined users.   For example, when the unconfined user tries to set up a vpn connection using vpnc, the vpnc program transitions to vpnc_t, a confined domain. Currently in Rawhide, I count around 50 transitions from the unconfined_t domain to other domains.

# sesearch --allow -s unconfined_t -c process -p transition | wc -l

A second way to confine the unconfined domains is to wrap specific allow rules within a boolean.

We have changed the line


to something like

    # Use any Linux capability.
    allow $1 self:capability all_capabilities;
    allow $1 self:fifo_file manage_fifo_file_perms;
    # Userland object managers
    allow $1 self:nscd all_nscd_perms;
    allow $1 self:dbus all_dbus_perms;
    allow $1 self:passwd all_passwd_perms;
    allow $1 self:association all_association_perms;

    allow $1  self:process ~{ transition dyntransition execmem execstack execheap };

        # Allow making the stack executable via mprotect.
        allow $1 self:process execheap;

        # Allow making anonymous memory executable, e.g.
        # for runtime-code generation or executable stack.
        allow $1 self:process execmem;


Wrapping the access within booleans gives the administrator the ability to decide whether or not to add limited confinement to the unconfined domains. 

For example, we restrict the unconfined users from executing memory that is both writable and executable at the same time.  This access can be tuned using these booleans: allow_execheap, allow_execmem, allow_execstack, allow_execmod.

Denying this access allows us to stop a lot of buffer overflows from being exploited.  If a user downloads content that can cause applications like acroread or evince to overflow a buffer, these checks will prevent the execution of code in corrupted memory, protecting the user.  Since the permission checks prevent the execution of random code, they help secure the system.

I would like to point out that these checks are trying to protect a user who is not trying to crack the system from being cracked.  These checks do nothing to prevent a cracker currently running as unconfined_t from attacking the system.

Periodically we have added checks to try to harden the machine against broken or malicious applications running as unconfined_t from attacking the machine.  Most of these preventions are the equivalent of putting a garden fence around the prison,  i.e. they are easily gotten around.   If a cracker gets into an SELinux box as unconfined_t, SELinux will prevent him from doing very little, since that is how unconfined_t is designed.  It is up to DAC to protect the system.

Last week, Brad Spengler  revealed a vulnerability that allowed him to gain control of the kernel and in doing so is able to disable all security mechanisms including those of LSMs like SELinux.  this is a whole new vulnerability, although it is in the same class of bugs as one he reported a couple of years back, and in that case he was also able to turn off SELinux policy enforcement.  The mmap_zero check was added to the kernel to control whether or not applications would be allowed to map memory at the zero address.  A decision was made to treat this differently on systems with or without SELinux enabled.  On systems without SELinux, applications have to run as root or you need to disable the check on the entire system.   On systems with SELinux enabled, this is decided by policy, and whether you are root or not is ignored.  One application that requires mmap_zero is wine. If you want to run wine apps on an SELinux disabled machine, you need to disable this check, for the entire system.  To run wine as a non-root user you need to
lessen the security of every single program on the machine.  On a machine with SELinux enabled you can run wine as normal user, and still have the protection where all confined domains, even those running as root, can not subvirt the machine.  The only confined applications that have the mmap_zero privilege in RHEL5 are vbetool, xdm, xserver and wine.  In rawhide,  only wine and vbetool have the privilege.  All unconfined domains have a boolean, allow_unconfined_mmap_zero, wrapping the mmap_zero privilege.  In RHEL5 the boolean is enabled by default for backwards compatibilitally, as mmap_min_addr was introduced during the lifetime of RHEL5, allowing all unconfined domains to mmap_zero.  By default in Fedora 10, 11 and rawhide, the boolean is disabled, denying all unconfined domains the mmap_zero access.  We are not planning on changing the default in RHEL5, to maintain backawards compatability.

You can toggle the boolean off by executing

# setsebool -P allow_unconfined_mmap_low=0

    There was a bug in policy in both Fedora and RHEL5 where unconfined_t, ignored the boolean.  This means unconfined_t could mmap_zero, whether or not the boolean was turned on. We have decided to change the unconfined_t domain to follow the boolean in updates. Versions: selinux-policy-3.5.13-66.fc10, selinux-policy-3.6.12-66.fc11, selinux-policy-3.6.22-1.fc12.noarch, selinux-policy-2.4.6-253.el5, have the fix.

Brad figured out that unconfined_t domain ignores the boolean and is always allowed to mmap_zero.  The default logged in user on a targeted SELinux system has the ability to mmap_zero. 

But I question whether or not this is even worth doing.  We are putting a speed bump in front of a determined cracker.  If a cracker is logged in to a machine as the unconfined_t user he can easily compile the executable described by Brad, change its context to vbetool_exec_t, and then use runcon to execute the script with the mmap_zero privilege.

$ /usr/sbin/getsebool allow_unconfined_mmap_low
allow_unconfined_mmap_low --> off

So the user downloads the cracker.

$ ./cracker
mmap: Permission denied

SELinux is working, but the unconfined_t user can label his cracker app as vbetool_exec_t, (a tool that needs mmap_zero).

chcon -t vbetool_exec_t ./cracker

$ ./cracker
mmap: Permission denied

Notice that it still blew up.  This is because it does not transition from unconfined_t to vbetool_t, automatically.

But how about.

$ runcon -t initrc_t -- sh -c ./a.out
got a NULL mapping...

This succeeds because, there is a transition defined from unconfined_t to initrc_t, and also a transition from initrc_t to vbetool_t.

In conclusion, this shows that the boolean preventing the unconfined_t domain or any other unconfined domains from performing mmap_zero is pretty useless in preventing a cracker from attacking the system.
If you use unconfined_t users and a cracker gets to login as one, SELinux can not prevent mmap_zero.
A cracker running as unconfined_t, can use just about any root exploit, just as if SELinux was disabled.

In the future we need to better understand what it means to try to confine the unconfined user.

  • Container Domains (Types)

    One of the things people have always had a hard time understanding about SELinux is around different types. In this blog, I am going to discuss…

  • Musings on Hybrid Cloud

    I work on the lowest levels of container runtimes and usually around process security. My team and I work on basically everything needed run…

  • Container Labeling

    An issue was recently raised on libpod, the github repo for Podman. "container_t isn't allowed to access container_var_lib_t" Container policy…

  • Post a new comment


    Anonymous comments are disabled in this journal

    default userpic

    Your reply will be screened