Polkit CVE-2018-19788 vs. SELinux

Hi All,

Last month, I wrote about Xorg X server vulnerability and we have a new interesting vulnerability, now in PolicyKit.

The exploit is based on a bug in PolicyKit, which allows users with UID greater than INT_MAX to successfully execute any systemctl command.
For more info visit thehackersnews.com article.

How does SELinux handle this exploit? 🙂 Let’s see.

SELinux brings very powerful feature called confined users, which gives administrators the power to confine user entities on the system. See official Fedora documentation or Blog from Dan Walsh. This feature is not enabled out of the box and needs to be configured on SELinux enabled system.

Let’s try PoC of the exploit in Permissive mode, when SELinux security policy is not enforced:

[tester@localhost ~]$ getenforce
[tester@localhost ~]$ id
uid=4000000000(tester) gid=1012(tester) groups=1012(tester) context=user_u:user_r:user_t:s0
[tester@localhost ~]$ systemd-run -t /bin/bash

(pkttyagent:7036): GLib-GObject-WARNING **: 15:39:20.472: value "-294967296" of type 'gint' is invalid or out of range for property 'uid' of type 'gint'
ERROR:pkttyagent.c:156:main: assertion failed: (polkit_unix_process_get_uid (POLKIT_UNIX_PROCESS (subject)) >= 0)
Running as unit: run-u3748.service
Press ^] three times within 1s to disconnect TTY.
[root@localhost /]# id
uid=0(root) gid=0(root) groups=0(root) context=system_u:system_r:initrc_t:s0

As you can see, using PoC I can get root access with one command. Let’s repeat it with SELinux in enforcing:

[tester@lvrabec-workstation ~]$ getenforce
[tester@lvrabec-workstation ~]$ id
uid=4000000000(tester) gid=1012(tester) groups=1012(tester) context=user_u:user_r:user_t:s0
[tester@lvrabec-workstation ~]$ systemd-run -t /bin/bash

(pkttyagent:7243): GLib-GObject-WARNING **: 15:42:05.436: value "-294967296" of type 'gint' is invalid or out of range for property 'uid' of type 'gint'
ERROR:pkttyagent.c:156:main: assertion failed: (polkit_unix_process_get_uid (POLKIT_UNIX_PROCESS (subject)) >= 0)
Failed to start transient service unit: Access denied

[tester@lvrabec-workstation ~]$ id
uid=4000000000(tester) gid=1012(tester) groups=1012(tester) context=user_u:user_r:user_t:s0

What does it mean? SELinux technology can block this exploit! Because confined user domain (user_t) cannot start systemd services!

time->Sun Dec 9 15:43:17 2018
type=USER_AVC msg=audit(1544366597.713:2394): pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='avc: denied { start } for auid=4000000000 uid=4000000000 gid=1012 cmdline="systemd-run -t setenforce 0" scontext=user_u:user_r:user_t:s0 tcontext=system_u:system_r:init_t:s0 tclass=system permissive=0 exe="/usr/lib/systemd/systemd" sauid=0 hostname=? addr=? terminal=?

It looks like it’s the right time to confine users on your systems! 😉

Thanks @paragonsec for one-liner PoC.

How get file name from inode number?

Hi All,

I will generate SELinux denial using following command:

# cd /root ; passwd --help >& output.txt

# ausearch -m AVC -ts recent
type=AVC msg=audit(1535037404.461:1066390): avc: denied { write } for pid=11511 comm="passwd" path="output.txt" dev="dm-1" ino=1310732 scontext=staff_u:sysadm_r:passwd_t:s0:c0.c1023 tcontext=staff_u:object_r:admin_home_t:s0 tclass=file permissive=0

We can see that passwd process with passwd_t SELinux domain is trying to create file output.txt with label admin_home_t. We can find that admin_home_t is label for /root directory based on the output from semanage fcontext command.

# semanage fcontext -l | grep admin_home_t
/root(/.*)? all files system_u:object_r:admin_home_t:s0

But sometimes it’s not clear where the object from the AVC is stored. We just see name of the object. What could be helpful here is inode of object in the AVC message.
In our example the inode is ino=1310732.

We can use the following command to get file name and path from inode number:

# find / -xdev -inum 1310732

Now you can find where exactly the object is stored. 🙂

How to enable full auditing in audit daemon?

Full auditing in audit deamon could be useful e.g. to identify which object on system has too tight rules and object is causing dac_override SELinux denial. More info in my previous post.

 Open /etc/audit/rules.d/audit.rules file in an editor.

 1. Remove following line if it exists:

-a task,never

2. Add following line at the end of the file:

-w /etc/shadow -p w

 3. Restart the audit daemon:

 # service auditd restart

 4. Re-run your scenario.

Full auditing is useful when full paths to accessed objects are needed or certain audit event fields, which are normally hidden, should be visible.

The procedure works on Red Hat Enterprise Linux  >= 5 and Fedoras.

If /etc/audit/rules.d/audit.rules file does not exist, please edit /etc/audit/audit.rules directly. Older versions of audit did not generate /etc/audit/audit.rules from /etc/audit/rules.d/audit.rules.


Thanks Milos Malik for this article.

Why do you see DAC_OVERRIDE SELinux denials?

Hello everyone!

You could have seen SELinux denials (AVC messages) in your system in the recent release of Fedora 28 and of course Fedora Rawhide. I removed lot of rules allowing DAC_OVERRIDE capability for the process domain to bring the tightened security on SELinux enabled systems. In many cases, DAC_OVERRIDE capability is not needed and there is issue with handling UNIX permissions on objects stored in the system.

But what does DAC_OVERRIDE capability means?
In capabilities(7) man page you can find explanation. “If process has DAC_OVERRIDE capability, it can bypass file read, write and execute permissions check.”

What does it mean in reality?
Process could read, write and execute files even when there are no proper flags set on the file.

And this is a solid security hole.

Dan Walsh mentioned on his blog, that there is a myth that root is all powerful. This is not completely true, because on SELinux enabled systems, even processes ran under root user must have DAC_OVERRIDE capability allowed by SELinux policy. Aaand this is the problem for many cases on Fedora system!

Lot of daemons run as root:root user and group permissions and are accessing several files/directories in the system, but these files have too tight permissions.

Let’s make an example:

Directory below is owned by mpd user.

# ll -aZ /var/run/ | grep lirc
drwxr-xr-x. 2 lirc lirc system_u:object_r:lircd_var_run_t:s0 80 Jul 3 10:18 lirc

Following process is trying to access this directory and write logs files.

# ps -efZ | grep lircd
system_u:system_r:lircd_t:s0 root 6404 1 0 10:18 ? 00:00:00 /usr/sbin/lircd --nodaemon
system_u:system_r:lircd_t:s0 root 6405 6404 0 10:18 ? 00:00:00 [uname]

As we can see the process is owned by user root and group root. Which means that kernel will look on “others” group in UNIX permissions, and there is no written access for others.

This action should be terminated by kernel because permissions are too tight, but in discretionary access control root could bypass all permissions and access the file in the system. However, this is not allowed in mandatory access control which is implemented by SELinux.

For this reason processes owned by root needs DAC_OVERRIDE capability, or changed permissions on files/directories. In most cases this is a bug in the application package.

Dan Walsh also wrote a nice blog about this issue. He’s describing same situation on dovecot example.

Newest SELinux policy every day!

SELinux policy for Fedora Rawhide and Fedora 27 is changing very dynamically and new rules are appearing in SELinux policy repositories almost every day.

New selinux-policy RPM packages aren’t created every day, but if you want to have always up-to-date SELinux policy you can use copr repository with nightly builds. These builds are created every day.

Enabling this repo is very easy, just enable it via:

$ dnf copr enable lvrabec/selinux-policy-nightly 

And that’s it!
This is the fastest way how to deal with new denials in bleeding edge Fedora Rawhide.

No more massive patches in selinux-policy rpm package

Hi SELinux folks,

Building selinux-policy rpm package was quite complicated and confusing for developers because of massive patches against Tresys reference base and contrib repositories. The data flow during policy build was following:

This flow is unfit for selinux-policy rpm package because of the patch size (difficult to manage and near impossible to manually check). The following data flow should fix this:

From now on, we don’t use patches against refpolicy. Tarballs created directly from github are used instead. First build with this change is selinux-policy-3.14.1-1.fc28

Hope this sheds more light on building selinux-policy rpm package. 🙂

Creating local module quickly in CIL!


Today, I’ll show you how to create local policy module for testing purposes or workaround while issue will be fixed in our distro selinux-policy. Local modules will be written in CIL (common intermediate language) so this post concerns Fedora 23 and higher.

As an example we can look on following BZ:

In description of BZ we can find this AVC:

type=AVC msg=audit(1466793837.663:1360): avc:  denied  { setrlimit }
for  pid=15770 comm="zabbix_agentd" scontext=system_u:system_r:zabbix_agent_t:s0
tcontext=system_u:system_r:zabbix_agent_t:s0 tclass=process permissive=0

Important parts of AVC for us:

avc:  denied  { setrlimit }

From these parts we can create allowing rule in CIL. Template for creating allow rules:

(allow source_context target_context (tclass (permissions)))

If we apply template on our AVC it looks like this:

(allow zabbix_agent_t zabbix_agent_t(process (setrlimit)))

Now, we add this line to file:
(Note: Name of your local module must be different than any other used in distro policy!)

$ cat zabbix_setrlimit.cil 
(allow zabbix_agent_t zabbix_agent_t(process (setrlimit)))

Finally, we could load this local module into kernel:

# semodule -i zabbix_setrlimit.cil

And that’s it! Rule from AVC report is loaded into kernel! You can verify it using:

$ sesearch -A -s zabbix_agent_t -t zabbix_agent_t -c process -p setrlimit
Found 1 semantic av rules:
   allow zabbix_agent_t zabbix_agent_t : process { fork sigchld sigkill sigstop signull signal getsched setsched setpgid getcap setrlimit } ; 

Pretty easy, don’t you think? 😉