Skip to content

SELinux Security Policy: Part 3 – Labels in action!

SELinux is all about labels. Do you remember from the previous blog post?

We know that SELinux decisions are based on labels and made by the kernel according to the loaded policy. Access is granted (action is permitted) if and only if there is a rule allowing it. You can see it in picture 2 featured in the first part of this blog series. We also know how these labels look and where they are stored from the second part of these blogs.

Now lets put the pieces together and demonstrate real SELinux decisions between real system entities – between the system and service manager

$ ps -eZ | grep systemd
system_u:system_r:init_t:s0         1 ?        00:00:02 systemd

which is trying to read a picture file in your home directory.

$ ls -Z /home/mgrepl/Pictures/picture 
unconfined_u:object_r:user_home_t:s0 /home/mgrepl/Pictures/picture

We use Fedora Targeted policy so we already know that we care only about types (the 3rd part of above mentioned labels). Together with the class (file) and  with the permission (read) we get the following tuple.

(init_t, user_home_t, file, read)

which is used for the decision made by the kernel, whether the policy permits a process with source type to access object of given class and target type with the requested action.

We can demonstrate this kernel decision using the security_compute_av() function with SELinux python module.

$ python
Python 2.7.12 (default, Oct 12 2016, 14:31:21) 
[GCC 6.2.1 20160916 (Red Hat 6.2.1-2)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import selinux 
>>> avd=selinux.av_decision()
>>> slabel="system_u:system_r:init_t:s0"
>>> tlabel="unconfined_u:object_r:user_home_t:s0"
>>> selinux_class=selinux.string_to_security_class("file")
>>> selinux_permission=selinux.string_to_av_perm(selinux_class,"read")
>>> 
>>> selinux.security_compute_av(slabel,tlabel,selinux_class,selinux_permission,avd)
0
>>> 
>>> if (selinux_permission&avd.allowed == selinux_permission): print("The access is granted.")
... else: print("The access is not granted.")
... 

The access is not granted.

As we can see the access is not granted by the kernel because there is no rule for our (init_t, user_home_t, file, read) tuple. If you want to test a different tuple then you can simply replace bold values in the code above. As I said, there was no rule for tested tuple.

Do we have a way to check if a rule exists?

Yes, we do. We can use the sesearch tool to query existing SELinux rules in the SELinux security policy . For our example, the query has the following form

$ sesearch -A -s init_t -t user_home_t -c file -p read

and will return an empty output because there is no rule as we already know.

What will happen if  the type of the /home/mgrepl/Pictures/picture file will be different and readable by the init_t process type?

We can demonstrate the kernel decision again using the security_compute_av() function.

$ python
Python 2.7.12 (default, Oct 12 2016, 14:31:21) 
[GCC 6.2.1 20160916 (Red Hat 6.2.1-2)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import selinux 
>>> avd=selinux.av_decision()
>>> slabel="system_u:system_r:init_t:s0"
>>> tlabel="unconfined_u:object_r:systemd_home_t:s0"
>>> selinux_class=selinux.string_to_security_class("file")
>>> selinux_permission=selinux.string_to_av_perm(selinux_class,"read")
>>> 
>>> selinux.security_compute_av(slabel,tlabel,selinux_class,selinux_permission,avd)
0
>>> 
>>> if (selinux_permission&avd.allowed == selinux_permission): print("The access is granted.")
... else: print("The access is not granted.")
... 
The access is granted.

We can again query the rule using setools as previously.

$ sesearch -A -s init_t -t systemd_home_t -c file -p read
Found 1 semantic av rules:
   allow init_t systemd_home_t : file { ioctl read write create getattr setattr lock append unlink link rename open } ;

So as we can see SELinux is really all about labels. You can define hundreds, thousands types, assign them to system entities and write rules between them as you wish. SELinux gives you fine-grained control over your system.

If systemd is exploited and an attacker now has permissions of the main systemd process, which is running with root permissions, he could access any files on your system.

With SELinux he could access only files with specific labels for which we define SELinux policy rules. He could access files with the systemd_home_t type but he would not able to access user’s  home files with the user_home_t type.

Is your system protected by SELinux? Do you know how to check it? Do you know how to get more details about decisions performed by SELinux?

I will give you answers on these questions in the next part of this blog series 😉

Thank you.

SELinux Security Policy – Part2: Labels

From the previous blog we know that SELinux policy consists of rules, each of which describes an INTERACTION between processes and system resources.

In the second part of this blog series I will tell you more about LABELS. Where SELinux labels are stored and how they look in reality.  Labels are an important part of SELinux since all SELinux decisions are based on them. As my colleague Dan Walsh says:

SELinux is all about labels

Where are SELinux labels stored?

SELinux labels are stored in extended attributes (abbreviated xattr) of file systems that support them – ext2, ext3, ext4 and others.

How can I show labels placed in XATTR?

# getfattr -n security.selinux /etc/passwd
getfattr: Removing leading '/' from absolute path names
# file: etc/passwd
security.selinux="system_u:object_r:passwd_file_t:s0"

Is there another way to show it?

# ls -Z /etc/passwd
system_u:object_r:passwd_file_t:s0 /etc/passwd

-Z” option is your friend. In most cases it is related to SELinux and help us either show SELinux labels or modify them directly. You can check it for mv, cp, ps user commands for example.

From the above example of SELinux label  you can see that SELinux labels consist of FOUR parts with the following template

SELinux user:SELinux role:SELinux type:SELinux category

SELinux users Not the same as Linux users.
Several Linux users can be mapped to a single SELinux user.
object_u is a placeholder for Linux system resources.
system_u is a placeholder for Linux processes.
Can be limited to a set of SELinux roles.
SELinux role SELinux users can have multiple roles but only one can be active.
object_r is a placeholder for Linux system resources.
system_r is a placeholder for system processes.
Can be limited to a set of SELinux types.
 SELinux type Security model known as TYPE ENFORCEMENT.
In 99% you care only about TYPES.
policy rules and interactions between types.
 SELinux category Allow users to mark resources with compartment tags (MCS1, MCS2).
Used for RHEL virtualization and for container security.
s0 as the placeholder for default category.
s0:c1 can not access s0:c2.

In Fedora we ship TARGETED SELinux policy featuring mainly TYPE ENFORCEMENT. It means we mostly care only about TYPES. We can re-define the introductory statement of policy rules with this knowledge from

ALLOW LABEL1 LABEL2:OBJECT_CLASS PERMISSION;

to

ALLOW TYPE1 TYPE2:OBJECT_CLASS PERMISSION;

Where TYPE1 could be APACHE_T process type for apache processes and TYPE2 could be file type for apache logging files. In that case we declare the following SELinux policy rules

ALLOW APACHE_T APACHE_LOG_T:FILE READ;

Now you know that if we talk about TYPES, we talk about LABELS with respect to TARGETED policy, which is the default policy used in FEDORA.

SELinux Security Policy – Part1: Is it magic?

For a long time I tried to explain SELinux with different techniques –  nice pictures with houses, with animals, with animations and so on. Despite that, SELinux is still magic for some people who talk to me. So I was thinking about a new way (again) and I started to explain it step by step, without a magic and the first topic is SELinux security policy.

What is a SELinux Security Policy?

It is a core component of SELinux. It is a collection of SELinux policy rules in a binary form loaded into the kernel by SELinux userspace tools.

selinux_policy_userspace_kernel.png

Picture 1: SELinux binary policy vs. SELinux kernel policy

Once the binary policy is built and loaded into the kernel then the policy is ENFORCED on your system by the kernel. Every request coming from a user level process to access a system resource on your system is confronted with SELinux security policy. This is done by LSM (Linux security modules) hooks in all significant system calls.

lsm_hook_arch

Picture 2: LSM hook architecture

By default EVERYTHING is denied and you define policy rules to allow certain requests.

What does SELinux policy RULE describe?

It describes an INTERACTION between processes and system resources.

How can I describe this kind of interaction for my apache server?

I have an apache process that needs to access its log file. I would like to add SELinux policy rule reflecting the following interaction

"APACHE process can READ own LOGGING FILE"

so that  I am able to read important info from the apache logging file.

What is SELinux view of that interaction?

SELinux view of that interaction has the following form

ALLOW apache_process apache_log:FILE READ;

where apache_process and apache_log are LABELS. These labels are assigned to processes and system resources by SELinux security policy to map real system entities into the SELinux world.

Because the default SELinux policy says that everything is denied by default, we mostly define ALLOW rules to say which operations can be allowed between labeled processes and system resources. We define the following form of SELinux ALLOW rules

ALLOW LABEL1 LABEL2:OBJECT_CLASS PERMISSION;

With this knowledge we can try to respond to the following question in more detail. “What does SELinux policy rule describe?”

It describes an INTERACTION between processes and system resources in RULES. This interaction is specified between LABELS which are assigned by SELinux security policy and these labels map real system entities into the SELinux world.

CVE-2015-5602 and SELinux?

How is SELinux helpful?

That is one of the most common questions that we get when a new CVE (Common Vulnerabilities and Exposures) appears. We explain SELinux as a technology for process isolation to mitigate attacks via privilege escalation.

A real example of this attack can be seen in CVE-2015-5602 known as Unauthorized Privilege Escalation in sudo. Under certain conditions, this security issue allows you to modify any file on your system. From there it follows that you are able to modify the /etc/shadow file, containing secure user account data. To demonstrate how SELinux could help you here we would remind a SELinux feature called SELinux Confined Users.

SELinux confined users

On Fedora systems, the default Targeted security policy is enforced to confine commonly used applications/services to mitigate attacks on a system. With this policy, Linux users are unconfined by default. It means there are no restrictions for attacks coming from these users. CVE-2015-5602 is such an example. Fortunately, you can configure SELinux to confine also Linux users how it is described in Confining users with SELinux in RHEL and Confining Users on Fedora as a part of process isolation for Linux users.

I personally use SELinux confined users by default to take all advantages of process isolation for Linux users on my Fedora system.

In my case mgrepl Linux user is mapped to staff_u SELinux user

# semanage login -l |grep mgrepl

Login Name SELinux User MLS/MCS Range
mgrepl staff_u s0-s0:c0.c1023

who is supposed to be a SELinux login user with common administrative permissions and he is able to run sudo in the dedicated SELinux domain.

type_transition staff_t sudo_exec_t : process staff_sudo_t;

It tells me if staff_u SELinux user executes sudo then there is a SELinux transition to staff_sudo_t domain. With configured sudoers we can see

$ sudo -e ~/test.txt
$ ps -efZ | grep sudo
staff_u:staff_r:staff_sudo_t:s0-s0:c0.c1023 root 5390 4925 0 23:04 pts/3 00:00:00 sudo -e /home/mgrepl/test.txt

CVE-2015-5602 vs. confined SELinux users

With followed steps to reproduce of CVE-2015-5602 and with defined SELinux confinement for this Linux user using semanage utility

# semanage login -a -s staff_u usr
$ ssh usr@localohst
[usr@localhost ~]$ ln -s /etc/shadow ~/temp/test.txt
[usr@localhost ~]$ id -Z
staff_u:staff_r:staff_t:s0

we can try to edit ~/temp/test.txt file to access /etc/shadow

[usr@localhost ~]$ sudo -e ~/temp/test.txt
sudoedit: /home/usr/temp/test.txt: Permission denied
[usr@localhost ~]$ getenforce
Enforcing

That’s it.

SELINUX STOPS YOU!.

And the following log event is generated for this denied.

type=AVC msg=audit(1446584115.930:558): avc: denied { read } for pid=3098 comm="sudoedit" name="shadow" dev="dm-1" ino=1049344 scontext=staff_u:staff_r:staff_sudo_t:s0 tcontext=system_u:object_r:shadow_t:s0 tclass=file permissive=0

Are you now thinking about SELinux confined users?

I would like to thank Daniel Kopeček <dkopecek@redhat.com> for a heads-up and co-authoring this post.

SELinux insides – Part2: Neverallow assertions

Usually if we describe how to create a local policy, how to generate a new policy, how to add additional rules, we always talk about ALLOW rules and sometimes about DONTAUDIT rules. But we have another Access Vector (AV) rules – AUDITALLOW and NEVERALLOW.

ALLOW allows defined rules
DONTAUDIT stops the auditing of denial messages
AUDITALLOW audits events defined by a rule
NEVERALLOW specifies that an allow rule must not be generated for the operation

In this blog post, I would like to describe more details about NEVERALLOW rules on real examples and announce that we turn them back on in Fedora 23/Rawhide.

But why do we need to have NEVERALLOW rules/assertions? The answer is pretty easy. We need to be sure that we do not allow any unwanted/unsecure/dangerous actions. For example, we do not want to allow ordinary services to access /etc/shadow and NEVERALLOW assertions give us this ability. In the policy, we declare rules like

neverallow ~can_read_shadow_passwords shadow_t:file read;

It ensures that the policy will not contain rules allowing any domain without can_read_shadow_passwords attribute read access to /etc/shadow (otherwise the policy won’t compile).

What does it mean in practice? We can demonstrate it with the following rules on a system where NEVERALLOW assertion checks are enabled.

$ cat neverallow_test.cil
(allow sssd_t shadow_t (file (read)))

$ sudo semodule -i neverallow_test.cil
Neverallow found that matches avrule at line 310 of /var/lib/selinux/targeted/tmp/modules/100/authlogin/cil
Binary policy creation failed at line 1 of /var/lib/selinux/targeted/tmp/modules/400/neverallow_test/cil
Failed to generate binary

against

$ cat neverallow_test.cil
(typeattributeset can_read_shadow_passwords (sssd_t))
(allow sssd_t shadow_t (file (read)))

$ sudo semodule -i neverallow_test.cil
$ sesearch -A -s sssd_t -t shadow_t
allow sssd_t shadow_t : file read ;

In the first case, we were not able to define ALLOW rule because of existing NEVERALLOW rule in the policy. In the second case, we assigned can_read_shadow_passwords attribute to sssd_t to pass this NEVERALLOW rule.

As I mentioned above we turned this assertion check back on in Fedora 23/Rawhide with a new 2.4 userspace release which contains some optimalization in libsepol. Before that it took a long time to get a build of Fedora distribution policy with enabled NEVERALLOW check.

SEMOD_EXP="/usr/bin/semodule_expand -a"

was a part of our Fedora selinux-policy.spec file.

$ man semodule_expand
-a Do not check assertions. This will cause the policy to not check any neverallow rules.

Together with that we also changed to not check policy assertions during load, by setting expand-check=0 in /etc/selinux/semanage.conf. This option affects our practice examples.

So for a long time we did not have this check and we needed to be really careful with a rules which could conflict with defined assertions in the policy. With the latest Fedora SELinux userspace and policy packages, we no longer use “-a” option in selinux-policy.spec file and we modified semanage.conf to contain expand-check=1.

CIL – Part2: Module priorities

In my previous blog, I talked about CIL performance improvements. In this blog post, I would like to introduce another cool feature called module priorities. If you check the link, you can see a nice blog post published by Petr Lautrbach about this new feature.

With new SELinux userspace, we are able to use priorities for SELinux policy modules. It means you can ship own ipa policy module, which is based on distribution policy module, with additional rules and load it with higher priority. No more different names for policy modules and higher priority wins.

# semodule --list-modules=full | grep ipa
400 ipa pp
100 ipa pp

Of course, you can always say you want to use distro policy module and add just additional fixes. Yes, it works fine for some cases when you add just minor fixes which you are not able to get to distro policy for some reasons. Actually you can also package this local policy how Lukas Vrabec wrote in his blog.

Another way how to deal with this case is a fact you can ship SELinux policy for your application at all and don’t be a part of distro policy. Yes, we can see these cases.

For example

# semodule --list-modules=full | grep docker
400 docker pp

But what are disadvantages with this way?

* you need to know how to write SELinux policy
* you need to maintain this policy and reflect the latest distro policy changes
* you need to do “hacks” in your policies if you need to have some interfaces for types shipped by distro policy
* you would get your policy to upstream and check if there is no conflict with distribution policy if they do a merge with the same upstream

From Fedora/RHEL point of view, this was always a problem how to deal with policies for big projects like Cluster, Gluster, OpenShift and so on. We tried to get these policies out of distro policy but it was really hard to do a correct rpm handling and then we faced my above mentioned points.

So is there any easy way how to deal with it? Yes, it is. We ships a policy for a project in our distribution policy and this project takes this policy, adds additional fixes, creates pull requests against distribution policy and if there will be different timelines then it will be shipped by this project. And that’s it! It can be done easily using module priorities.

For example, we have Gluster policy in Fedora by default.

# semodule --list-modules=full | grep gluster
100 glusterd pp

And now, Gluster team needs to do a new release but it causes some SELinux issues. Gluster folks can take distribution policy, add additional rules and package it.

Then we will see something like

# semodule --list-modules=full | grep gluster
100 glusterd pp
400 glusterd pp

In the mean time, Gluster folks can do pull requests with all changes against disitribution policy and they can still ship the same policy. The Gluster policy is a part of distribution policy, it can be easily usptream-able and moreover, it can be disabled in distribution policy by default.

# semodule --list-modules=full | grep gluster
400 gluster cil
100 glusterd pp disabled

$ matchpathcon /usr/sbin/glusterfsd
/usr/sbin/glusterfsd system_u:object_r:glusterd_exec_t:s0

This model is really fantastic and give us answers for lot of issues.

CIL – Part1: Faster SELinux policy (re)build

As you probably know we shipped new features related to SELinux policy store migration in Fedora 23. If you check the link, you can see more details about this change. You can read some technical details, benefits and examples how to test it. In this blog series, called CIL, I would like to introduce you this new feature and show you benefits which CIL brings.

One of the most critical part of SELinux usability are time-consuming SELinux operations like policy installations or loading new policy modules for example. I guess you know what about I am talking. For example, you want to create own policy module for your application and test it on your virtual machine. It means you attempt to execute

semodule -i myapps.pp

and you are waiting, waiting, waitng and waiting.

The same you can see if you try to disable a module

semodule -d rhcs

and you are waiting, waiting, waitng and waiting.

It directly depends on used policy language and on the amount of policy rules which need to be rebuilt if SELinux policy modules are managed. You can read more info about policy modules and kernel policy in my previous blog.

And at this point, CIL brings really big performance improvements. Just imagine, no more “waiting waiting waiting” on a policy installation. No more “waiting waiting waiting” if you load your own policy module.

But no more words and let show you some real numbers.

SELinux_mange_store_time_statistics

You can see really big differences for chosen SELinux operations between a regular system with old SELinux userspace without CIL and with a new SELinux userspace with CIL.

It means we can really talk about ~75% speed-up for tools/apps which access to manage SELinux policy.

Note: These numbers come from Fedora 23 virtual machine and all these actions require a policy rebuild.

And it is not only about SELinux tools but we have also SELinux aware applications – systemd for example which loads Fedora distribution policy on boot process. And you get also big improvements on this boot process.

CIL: systemd[1]: Successfully loaded SELinux policy in 91.886ms.
REGULAR: systemd[1]: Successfully loaded SELinux policy in 172.393ms.

I believe you are now really excited to test this new feature and get own numbers and see how much faster SELinux tools like semodule, semanage are if they manipulate with a policy.