danwalsh


Dan Walsh's Blog

Got SELinux?


Previous Entry Share Next Entry
Confining the unconfined. Oxymoron?
danwalsh
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

allow UNCONFINED_DOMAIN *:* *;

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
     50  

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

We have changed the line

allow UNCONFINED_DOMAIN *:* *;

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 };

    tunable_policy(`allow_execheap',`
        # Allow making the stack executable via mprotect.
        allow $1 self:process execheap;
    ')

    tunable_policy(`allow_execmem',`
        # 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

Note:
    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.

should runcon/chcon be priviledged commands ?

tanso.net

2009-08-18 05:52 pm (UTC)

I'm a bit surprised that an unconfined normal user is allower to use runcon/chcon this way. I would expect these to be priviledged commands only available to root.. Shouldn't they be?

Re: should runcon/chcon be priviledged commands ?

danwalsh

2009-08-18 06:08 pm (UTC)

runcon and chcon do not add any priv they just execute the libselinux api. The kernel is responsible for checking whether on not a type is able to change the context from one context to another.

Similary, the kernel/policy will control whether one process can transition to another process.

From an SELinux point of view running as UID=0 means NOTHING. There is no concept of Privledged in the DAC sense.

Re: should runcon/chcon be priviledged commands ?

tanso.net

2009-08-18 06:26 pm (UTC)

Alternatively .. shouldn't modifying the file security context be a priviledged operation ? I've never had a use for letting non-admins modify these labels, and always assumed they were managed by the central file_context policy..

Re: should runcon/chcon be priviledged commands ?

danwalsh

2009-08-18 09:40 pm (UTC)

Well the type enforcement rules are controling what labels they can relabel from and what labels they can relabel to.

So a user_t user might be allowed to relabelfrom user_home_t to httpd_user_content_t to allow them to make content available to apache from within their home dir.

This is very different then the way traditional MLS machines worked. So they are not changing the sensitivity level of data, they are just labeling data from one type they control to another.

You are viewing danwalsh