A SELinux policy for incron: new types and transitions

So I’ve shown the iterative approach used to develop policies. Again, please be aware that this is my way of developing policies, other policy developers might have a different approach. We were working on the incrontab command, so let’s continue with trying to create a new user incrontab:

$ incrontab -e
cannot create temporary file: Permission denied

# tail audit.log
type=AVC msg=audit(1368709633.285:28211): avc:  denied  { setgid } for  pid=8159 comm="incrontab" capability=6  scontext=user_u:user_r:incrontab_t tcontext=user_u:user_r:incrontab_t tclass=capability
type=AVC msg=audit(1368709633.285:28212): avc:  denied  { setuid } for  pid=8159 comm="incrontab" capability=7  scontext=user_u:user_r:incrontab_t tcontext=user_u:user_r:incrontab_t tclass=capability
type=AVC msg=audit(1368709633.287:28213): avc:  denied  { search } for  pid=8159 comm="incrontab" name="/" dev="tmpfs" ino=3927 scontext=user_u:user_r:incrontab_t tcontext=system_u:object_r:tmp_t tclass=dir

The requests for the setuid and setgid capabilities are needed for the application to safely handle the user incrontabs. Note that SELinux does not “remove” the setuid bit on the binary itself, but does govern the related capabilities. Since this is required, we will add these capabilities to the policy. We also notice that incrontab searched in the /tmp location.

allow incrontab_t self:capability { setuid setgid };
...
files_search_tmp(incrontab_t)

In the next round of iteration, we notice the same error message with the following denial:

type=AVC msg=audit(1368728433.521:28215): avc:  denied  { write } for  pid=8913 comm="incrontab" name="/" dev="tmpfs" ino=3927 scontext=user_u:user_r:incrontab_t tcontext=system_u:object_r:tmp_t tclass=dir

It is safe to assume here that the process wants to create a temporary file (if it is a directory, we will find out later and can adapt). But when temporary files are created, we better make those files a specific type, like incrontab_tmp_t. So we define that on top of the policy:

type incrontab_tmp_t;
files_tmp_file(incrontab_tmp_t)

Also, we need to allow the incrontab_t domain write privileges into the tmp_t labeled directory, but with an automatic file transition towards incrontab_tmp_t for every file written. This is done through the files_tmp_filetrans method:

files_tmp_filetrans(incrontab_t, incrontab_tmp_t, file)

What this sais is that, if a domain incrontab_t wants to create a file inside tmp_t, then this file is automatically labeled incrontab_tmp_t. With SELinux, you can make this more precise: if you know what the file name would be, then you can add that as a fourth argument. However, this does not seem necessary now since we definitely want all files created in tmp_t to become incrontab_tmp_t. All that rests us is to allow incrontab to actually manage those files:

allow incrontab_t incrontab_tmp_t:file manage_file_perms;

With those in place, let’s look at the outcome:

$ incrontab -e
editor finished with error: No such file or directory

# tail audit.log
type=AVC msg=audit(1368729268.465:28217): avc:  denied  { search } for  pid=8981 comm="incrontab" name="bin" dev="dm-3" ino=524289 scontext=user_u:user_r:incrontab_t tcontext=system_u:object_r:bin_t tclass=dir

Considering that here, incrontab is going to launch the users $EDITOR application to allow him (or her) to create an incrontab, we need to allow incrontab_t not only search privileges inside bin_t directories, but also execute rights: corecmd_exec_bin(incrontab_t). We choose here to execute the editor inside the existing domain (incrontab_t) instead of creating a different domain for the editor for the following reasons:

  • If we would create a separate domain for the editor, the editor would eventually need to have major permissions, depending on when it is used. Editors can be used to modify the sudoers files, passwd files, the /etc/selinux/config file, etc. Instead, it makes much more sense to just be able to launch the editor in the current domain (which is much more confined to its specific purpose)
  • The additional privileges needed to launch the editor are usually very slim, or even nonexistent. It generally only makes sense if, by executing it, the existing domain would need many more privileges, because then a new (confined) domain keeps the privileges for the current domain low.

Let’s see if things work now:

$ incrontab -e
(Editor opened, so I added in an incrontab line. Upon closing:)
cannot move temporary table: Permission denied

# tail audit.log
type=AVC msg=audit(1368729825.673:28237): avc:  denied  { dac_read_search } for  pid=9030 comm="incrontab" capability=2  scontext=user_u:user_r:incrontab_t tcontext=user_u:user_r:incrontab_t tclass=capability
type=AVC msg=audit(1368729825.673:28237): avc:  denied  { dac_override } for  pid=9030 comm="incrontab" capability=1  scontext=user_u:user_r:incrontab_t tcontext=user_u:user_r:incrontab_t tclass=capability

From a quick look through ps, I notice that the application runs as the user (luckily, otherwise I could use the editor to escape and get a root shell) after which it tries to do something. Of course, it makes sense that it wants to move this newly created incrontab file somewhere in /var/spool/incron so we grant it the permission to dac_read_search (which is lower than dac_override as explained before):

allow incrontab_t self:capability { dac_read_search setuid setgid };

On to the next failure:

$ incrontab -e 
cannot move temporary table: Permission denied

# tail audit.log
type=AVC msg=audit(1368730155.706:28296): avc:  denied  { write } for  pid=9088 comm="incrontab" name="incron" dev="dm-4" ino=19725 scontext=user_u:user_r:incrontab_t tcontext=root:object_r:incron_spool_t tclass=dir

Now the application wants to write this file there. Now remember we already have search_dir_perms permissions into incron_spool_t? We need to expand those with read/write permissions into the directory, and manage permissions on files (manage because users should be able to create, modify and delete their files). These two permissions are combined in the manage_files_pattern interface, and makes the search one obsolete:

manage_files_pattern(incrontab_t, incron_spool_t, incron_spool_t)
$ incrontab -e
...
table updated

Finally! And looking at the other options in incrontab, it seems that the policy for incrontab_t is finally complete, and looks like so:


###########################################
#
# incrontab policy
#

allow incrontab_t self:capability { setuid setgid dac_read_search };

manage_files_pattern(incrontab_t, incron_spool_t, incron_spool_t)

allow incrontab_t incrontab_tmp_t:file manage_file_perms;
files_tmp_filetrans(incrontab_t, incrontab_tmp_t, file)

corecmd_exec_bin(incrontab_t)

domain_use_interactive_fds(incrontab_t)

files_search_spool(incrontab_t)
files_search_tmp(incrontab_t)

auth_use_nsswitch(incrontab_t)

userdom_use_user_terminals(incrontab_t)

Next on the agenda: the incrond_t domain.

Posted in SELinux | Tagged , , | Leave a comment

A SELinux policy for incron: basic set for incrontab

Now that our regular user is allowed to execute incrontab, let’s fire it up and look at the denials to build up the policy.

$ incrontab --help

That doesn’t show much does it? Well, if you look into the audit.log (or avc.log) file, you’ll notice a lot of denials. If you are developing a policy, it is wise to clear the entire log and reproduce the “situation” so you get a proper idea of the scope.

# cd /var/log/audit
# > audit.log
# tail -f audit.log | grep AVC

Now let’s run incrontab –help again and look at the denials:

type=AVC msg=audit(1368707274.429:28180): avc:  denied  { read write } for  pid=7742 comm="incrontab" path="/dev/tty2" dev="devtmpfs" ino=1042 scontext=user_u:user_r:incrontab_t tcontext=user_u:object_r:user_tty_device_t tclass=chr_file
type=AVC msg=audit(1368707274.429:28180): avc:  denied  { use } for  pid=7742 comm="incrontab" path="/dev/tty2" dev="devtmpfs" ino=1042 scontext=user_u:user_r:incrontab_t tcontext=system_u:system_r:getty_t tclass=fd
type=AVC msg=audit(1368707274.429:28180): avc:  denied  { use } for  pid=7742 comm="incrontab" path="/dev/tty2" dev="devtmpfs" ino=1042 scontext=user_u:user_r:incrontab_t tcontext=system_u:system_r:getty_t tclass=fd
type=AVC msg=audit(1368707274.429:28180): avc:  denied  { use } for  pid=7742 comm="incrontab" path="/dev/tty2" dev="devtmpfs" ino=1042 scontext=user_u:user_r:incrontab_t tcontext=system_u:system_r:getty_t tclass=fd

You can start piping this information into audit2allow to generate policy statements, but I personally prefer not to use audit2allow for building new policies. For one, it is not intelligent enough to deduce if a denial should be fixed by allowing it, or by relabeling or even by creating a new type. Instead, it always grants it. Second, it does not know if a denial is cosmetic (and thus can be ignored) or not.

This latter is also why I don’t run domains in permissive mode to see the majority of denials first and to build from those: you might see denials that are actually never triggered when running in enforcing mode. So let’s look at the access to /dev/tty2. Given that this is a user application where we expect output to the screen, we want to grant it the proper access. With sefindif as documented before, we can look for the proper interfaces we need. I look for user_tty_device_t with rw (commonly used for read-write):

$ sefindif user_tty_device_t.*rw
system/userdomain.if: template(`userdom_base_user_template',`
system/userdomain.if:   allow $1_t user_tty_device_t:chr_file { setattr rw_chr_file_perms };
system/userdomain.if: interface(`userdom_use_user_ttys',`
system/userdomain.if:   allow $1 user_tty_device_t:chr_file rw_term_perms;
system/userdomain.if: interface(`userdom_use_user_terminals',`
system/userdomain.if:   allow $1 user_tty_device_t:chr_file rw_term_perms;
system/userdomain.if: interface(`userdom_dontaudit_use_user_terminals',`
system/userdomain.if:   dontaudit $1 user_tty_device_t:chr_file rw_term_perms;
system/userdomain.if: interface(`userdom_dontaudit_use_user_ttys',`
system/userdomain.if:   dontaudit $1 user_tty_device_t:chr_file rw_file_perms;

Two of these look interesting: userdom_use_user_ttys and userdom_use_user_terminals. Looking at the API documentation (or the rules defined therein using seshowif) reveals that userdom_use_user_terminals is needed if you also want the application to work when invoked through a devpts terminal, which is probably also something our user(s) want to do, so we’ll add that. The second one – using the file descriptor that has the getty_t context – is related to this, but not granted through the userdom_use_user_ttys. We could grant getty_use_fds but my experience tells me that domain_use_interactive_fds is more likely to be needed: the application inherits and uses a file descriptor currently owned by getty_t but it could be from any of the other domains that has such file descriptors. For instance, if you grant the incron_role to sysadm_r, then a user that switched roles through newrole will see denials for using a file descriptor owned by newrole_t.

Experience is an important aspect in developing policies. If you would go through with getty_use_fds it would work as well, and you’ll probably hit the above mentioned experience later when you try the application through a few different paths (such as within a screen session or so). When you think that the target context (in this case getty_t) could be a placeholder (so other types are likely to be needed as well), make sure you check which attributes are assigned to the type:

# seinfo -tgetty_t -x
   getty_t
      privfd
      mcssetcats
      mlsfileread
      mlsfilewrite
      application_domain_type
      domain

Of the above ones, privfd is the important one:

$ sefindif privfd.*use
kernel/domain.if: interface(`domain_use_interactive_fds',`
kernel/domain.if:       allow $1 privfd:fd use;
kernel/domain.if: interface(`domain_dontaudit_use_interactive_fds',`
kernel/domain.if:       dontaudit $1 privfd:fd use;

So let’s update incron.te accordingly:

...
type incron_spool_t;
files_type(incron_spool_t)

###########################################
#
# incrontab policy
#

userdom_use_user_terminals(incrontab_t)
domain_use_interactive_fds(incrontab_t)

Rebuild the policy and load it in memory.

If we now run incrontab we get the online help as we expected. Let’s now look at the currently installed incrontabs (there shouldn’t be any of course):

$ incrontab -l
cannot determine current user

In the denials, we notice:

type=AVC msg=audit(1368708632.060:28192): avc:  denied  { create } for  pid=7968 comm="incrontab" scontext=user_u:user_r:incrontab_t tcontext=user_u:user_r:incrontab_t tclass=unix_stream_socket
type=AVC msg=audit(1368708632.060:28194): avc:  denied  { read } for  pid=7968 comm="incrontab" name="nsswitch.conf" dev="dm-2" ino=393768 scontext=user_u:user_r:incrontab_t tcontext=system_u:object_r:etc_t tclass=file
type=AVC msg=audit(1368708632.062:28196): avc:  denied  { read } for  pid=7968 comm="incrontab" name="passwd" dev="dm-2" ino=394223 scontext=user_u:user_r:incrontab_t tcontext=system_u:object_r:etc_t tclass=file

Let’s first focus on nsswitch.conf and passwd. Although both require read access to etc_t files, it might be wrong to just add in files_read_etc (which is what audit2allow is probably going to suggest). For nsswitch, there is a special interface available: auth_use_nsswitch. It is very, very likely that you’ll need this one, especially if you want to share the policy with others who might not have all of the system databases in local files (as etc_t files).

...
domain_use_interactive_fds(incrontab_t)
auth_use_nsswitch(incrontab_t)

Let’s retry:

$ incrontab -l
cannot read table for 'user': Permission denied

# tail audit.log
type=AVC msg=audit(1368708893.260:28199): avc:  denied  { search } for  pid=7997 comm="incrontab" name="spool" dev="dm-4" ino=20 scontext=user_u:user_r:incrontab_t tcontext=system_u:object_r:var_spool_t tclass=dir

So we need to grant search privileges on var_spool_t. This is offered through files_search_spool. Add it to the policy, rebuild and retry.

$ incrontab -l
cannot read table for 'user': Permission denied

# tail audit.log
type=AVC msg=audit(1368709146.426:28201): avc:  denied  { search } for  pid=8046 comm="incrontab" name="incron" dev="dm-4" ino=19725 scontext=user_u:user_r:incrontab_t tcontext=root:object_r:incron_spool_t tclass=dir

For this one, no interface exists yet. We might be able to create one for ourselves, but as long as other domains don’t need it, we can just add it locally in our policy:

allow incrontab_t incron_spool_t:dir search_dir_perms;

Adding raw allow rules in a policy is, according to the refpolicy styleguide, only allowed if the policy module defines both the source and the destination type of the rule. If you look into other policies you might also find that you can use the search_dirs_patter call. However, that one only makes sense if you need to do this on top of another directory – just look at the definition of search_dirs_pattern. So with this permission set, let’s retry.

$ incrontab -l
no table for user

Great, we have successfully updated the policy until the commands worked. In the next post, we’ll enhance it even further while creating new incrontabs.

Posted in SELinux | Tagged , , , | Leave a comment

A SELinux policy for incron: our first interface

The next step after having a basic skeleton is to get incrontab running. We know however that everything invoked from the main daemon will be running with the rights of the daemon context (unless we would patch the source code, but that is beyond the scope of this set of posts). As a result, we probably do not want everyone to be able to launch commands through this application.

What we want to do is to limit who can invoke incrontab and, as such, limit who can decide what is invoked through incrond. First of all, we define a role attribute called incrontab_roles. Every role that gets this attribute assigned will be able to transition to the incrontab_t domain.

We can accomplish this by editing the incron.te file:

policy_module(incron, 0.2)

# Declare the incrontab_roles attribute
attribute_role incrontab_roles;

...
type incrontab_t;
type incrontab_exec_t;
application_domain(incrontab_t, incrontab_exec_t)
# Allow incrontab_t for all incrontab_roles 
role incrontab_roles types incrontab_t;

Next, we need something where we can allow user domains to call incrontab. This will be done through an interface. Let’s look at incron.if with one such interface in it: the incron_role interface.

## inotify-based cron-like daemon

#########################################
## <summary>
##      Role access for incrontab
## </summary>
## <param name="role">
##      <summary>
##      Role allowed access.
##      </summary>
## </param>
## <param name="domain">
##      <summary>
##      User domain for the role.
##      </summary>
## </param>
#
interface(`incron_role',`
        gen_require(`
                attribute_role incrontab_roles;
                type incrontab_exec_t, incrontab_t;
        ')

        roleattribute $1 incrontab_roles;

        domtrans_pattern($2, incrontab_exec_t, incrontab_t)

        ps_process_pattern($2, incrontab_t)
        allow $2 incrontab_t:process signal;
')

The comments in the file are somewhat special: if the comments start with two hashes (##) then it is taken into account while building the policy documentation in /usr/share/doc/selinux-base-*. The interface itself, incron_role, grants a user role and domain the necessary privileges to transition to the incrontab_t domain as well as read process information (as used through ps, hence the name of the pattern being ps_process_pattern) and send a standard signal to it. Most of the time, you can use signal_perms here but from looking at the application we see that the application is setuid root, so we don’t want to grant too many privileges by default if they are not needed.

With this interface file created, we can rebuild the module and load it.

# make -f /usr/share/selinux/strict/include/Makefile incron.pp
# semodule -i incron.pp

But how to assign this interface to users? Well, what we want to do is something like the following:

incron_role(user_r, user_t)

When interfaces are part of the policy provided by the distribution, the definitions of it are stored in the proper location and you can easily add it. For instance, in Gentoo, if you want to allow the user_r role and user_t domain the cron_role access (and assuming it doesn’t have so already), then you can call selocal as follows:

# selocal -a "cron_role(user_r, user_t)" -c "Granting user_t cron access" -Lb

However, because the interface is currently not known yet, we need to create a second small policy that does this. Create a file (called localuser.te or so) with the following content:

policy_module(localuser, 0.1)

gen_require(`
        type user_t;
        role user_r;
')

incron_role(user_r, user_t)

Now build the policies and load them. We’ll now just build and load all the policies in the current directory (which will be the incron and localuser ones):

# make -f /usr/share/selinux/strict/include/Makefile
# semodule -i *.pp

You can now verify that the user is allowed to transition to the incrontab_t domain:

# seinfo -ruser_r -x | grep incron
         incrontab_t
# sesearch -s user_t -t incrontab_exec_t -AdCTS
Found 1 semantic av rules:
   allow user_t incrontab_exec_t : file { read getattr execute open } ; 

Found 1 semantic te rules:
   type_transition user_t incrontab_exec_t : process incrontab_t; 

Great, let’s get to our first failure to resolve… in the next post ;-)

Posted in SELinux | Tagged , , | Leave a comment

A SELinux policy for incron: the basic skeleton

So, in the previous post I talked about incron and why I think moving it into the existing cron policy would not be a good idea. It works, somewhat, but is probably not that future-proof. So we’re going to create our own policy for it.

In SELinux, policies are generally written through 3 files:

  1. a type enforcement file that contains the SELinux rules applicable to the domain(s) related to the application (in our example, incron)
  2. a file context file that tells the SELinux utilities how the files and directories offered by the application should be labeled
  3. an interface definition file that allows other SELinux policy modules to gain rights offered through the (to be written) incron policy

We now need to create a skeleton for the policy. This skeleton will define the types related to the application. Such types can be the domains for the processes (the context of the incrond and perhaps also incrontab applications), the contexts for the directories (if any) and files, etc.

So let’s take a look at the content of the incron package. On Gentoo, we can use qlist incron for this. In the output of qlist, I added comments to show you how contexts can be (easily) deduced.

# Application binary for managing user crontabs. We want to give this a specific
# context because we want the application (which will manage the incrontabs in
# /var/spool/incron) in a specific domain
/usr/bin/incrontab  ## incrontab_exec_t

# General application information files, do not need specific attention
# (the default context is fine)
/usr/share/doc/incron-0.5.10/README.bz2
/usr/share/doc/incron-0.5.10/TODO.bz2
/usr/share/doc/incron-0.5.10/incron.conf.example.bz2
/usr/share/doc/incron-0.5.10/CHANGELOG.bz2
/usr/share/man/man8/incrond.8.bz2
/usr/share/man/man5/incron.conf.5.bz2
/usr/share/man/man5/incrontab.5.bz2
/usr/share/man/man1/incrontab.1.bz2

# Binary for the incrond daemon. This definitely needs its own context, since
# it will be launched from an init script and we do not want it to run in the
# initrc_t domain.
/usr/sbin/incrond ## incrond_exec_t

# This is the init script for the incrond daemon. If we want to allow 
# some users the rights to administer incrond without needing to grant
# those users the sysadm_r role, we need to give this file a different
# context as well.
/etc/init.d/incrond ## incrond_initrc_exec_t

With this information at hand, and the behavior of the application we know from the previous post, can lead to the following incron.fc file, which defines the file contexts for the application.

/etc/incron.d(/.*)?     gen_context(system_u:object_r:incron_spool_t,s0)

/etc/rc\.d/init\.d/incrond      --      gen_context(system_u:object_r:incrond_initrc_exec_t,s0)

/usr/bin/incrontab      --      gen_context(system_u:object_r:incrontab_exec_t,s0)

/usr/sbin/incrond       --      gen_context(system_u:object_r:incrond_exec_t,s0)

/var/spool/incron(/.*)?         gen_context(system_u:object_r:incron_spool_t,s0)

The syntax of this file closely follows the syntax that semanage fcontext takes – at least for the regular expressions in the beginning. The last column is specifically for policy development to generate a context based on the policies’ requirements: an MCS/MLS enabled policy will get the trailing sensitivity with it, but when MCS/MLS is disabled then it is dropped. The middle column is to specify if the label should only be set on regular files (--), directories (-d), sockets (-s), symlinks (-l), etc. If it is omitted, it matches whatever class the path matches.

The second file needed for the skeleton is the incron.te file, which would look like so. I added in inline comments here to explain why certain lines are prepared, but generally this is omitted when the policy is upstreamed.

policy_module(incron, 0.1)
# The above line declares that this file is a SELinux policy file. Its name
# is incron, so the file should saved as incron.te

# First, we declare the incrond_t domain, used for the "incrond" process.
# Because it is launched from an init script, we tell the policy that
# incrond_exec_t (the context of incrond), when launched from init, should
# transition to incrond_t.
#
# Basically, the syntax here is:
# type 
# type 
# 
type incrond_t;
type incrond_exec_t;
init_daemon_domain(incrond_t, incrond_exec_t)

# Next we declare that the incrond_initrc_exec_t is an init script context
# so that init can execute it (remember, SELinux is a mandatory access control
# system, so if we do not tell that init can execute it, it won't).
type incrond_initrc_exec_t;
init_script_file(incrond_initrc_exec_t)

# We also create the incrontab_t domain (for the "incrontab" application), which
# is triggered through the incrontab_exec_t labeled file. This again follows a bit
# the syntax as we used above, but now the interface call is "application_domain".
type incrontab_t;
type incrontab_exec_t;
application_domain(incrontab_t, incrontab_exec_t)

# Finally we declare the spool type as well (incron_spool_t) and tell SELinux that
# it will be used for regular files.
type incron_spool_t;
files_type(incron_spool_t)

Knowing which interface calls, like init_daemon_domain and application_domain, we should use is not obvious at first. Most of this can be gathered from existing policies. Other frequently occurring interfaces to be used immediately at the skeleton side are (examples for a foo_t domain):

  • logging_log_file(foo_log_t) to inform SELinux that the context is used for logging purposes. This allows generic log-related daemons to do “their thing” with the file.
  • files_tmp_file(foo_tmp_t) to identify the context as being used for temporary files
  • files_tmpfs_file(foo_tmpfs_t) for tmpfs files (which could be shared memory)
  • files_pid_file(foo_var_run_t) for PID files (and other run metadata files)
  • files_config_file(foo_conf_t) for configuration files (often within /etc)
  • files_lock_file(foo_lock_t) for lock files (often within /run/lock)

We might be using these later as we progress with the policy (for instance, the PID file is a very high candidate for needing to be included). However, with the information currently at hand, we have our first policy module ready for building. Save the type enforcement rules in incron.te and the file contexts in incron.fc and you can then build the SELinux policy:

# make -f /usr/share/selinux/strict/include/Makefile incron.pp
# semodule -i incron.pp

On Gentoo, you can then relabel the files and directories offered through the package using rlpkg:

# rlpkg incron

Next is to start looking at the incrontab application.

Posted in SELinux | Tagged , , , , , | Leave a comment

A SELinux policy for incron: what does it do?

In this series of posts, we’ll go through the creation of a SELinux policy for incron, a simple inotify based cron-like application. I will talk about the various steps that I would take in the creation of this policy, and give feedback when certain decisions are taken and why. At the end of the series, we’ll have a hopefully well working policy.

The first step in developing a policy is to know what the application does and how/where it works. This allows us to check if its behavior matches an existing policy (and as such might be best just added to this policy) or if a new policy needs to be written. So, what does incron do?

From the documentation, we know that incron is a cron-like application that, unlike cron, works with file system notification events instead of time-related events. Other than that, it uses a similar way of working:

  • A daemon called incrond is the run-time application that reads in the incrontab files and creates the proper inotify watches. When a watch is triggered, it will execute the matching rule.
  • The daemon looks at two definitions (incrontabs): one system-wide (in /etc/incron.d) and one for users (in /var/spool/incron).
  • The user tabfiles are managed through incrontab (the command)
  • Logging is done through syslog
  • User commands are executed with the users’ privileges (so the application calls setuid() and setgid())

With this, one can create a script to be executed when a file is uploaded (or deleted) to/from a file server, or when a process coredump occurred, or whatever automation you want to trigger when some file system event occurred. Events are plenty and can be found in /usr/include/sys/inotify.h.

So, with this information, it is safe to assume that we might be able to push incron in the existing cron policy. After all, it defines the contexts for all these and probably doesn’t need any additional tweaking. And this seems to work at first, but a few tests reveal that the behavior is not that optimal.

# chcon -t crond_exec_t /usr/sbin/incrond
# chcon -t crontab_exec_t /usr/bin/incrontab
# chcon -R -t system_cron_spool_t /etc/incron.d
# chcon -t cron_log_t /var/log/cron.log
# chcon -R -t cron_spool_t /var/spool/incron

System tables work somewhat, but all commands are executed in the crond_t domain, not in a system_cronjob_t or related domain.
User tables fail when dealing with files in the users directories, since these too run in crond_t and thus have no read access to the user home directories.

The problems we notice come from the fact that the application is very simple in its code: it is not SELinux-aware (so it doesn’t change the runtime context) as most cron daemons are, and when it changes the user id it does not call PAM, so we cannot trigger pam_selinux.so to handle context changes either. As a result, the entire daemon keeps running in crond_t.

This is one reason why a separate domain could be interesting: we might want to extend the rights of the daemon domain a bit, but don’t want to extend these rights to the other cron daemons (who also run in crond_t). Another reason is that the cron policy has a few booleans that would not affect the behavior at all, making it less obvious for users to troubleshoot. As a result, we’ll go for the separate policy instead – which will be for the next post.

Posted in SELinux | Tagged , , | 1 Comment

Why oh why does a process run in unlabeled_t?

If you notice that a process is running in the unlabeled_t domain, the first question to ask is how it got there.

Well, one way is to have a process running in a known domain, like screen_t, after which the SELinux policy module that provides this domain is removed from the system (or updated and the update does not contain the screen_t definition anymore):

test ~ # ps -eZ | grep screen
root:sysadm_r:sysadm_screen_t    5047 ?        00:00:00 screen
test ~ # semodule -r screen
test ~ # ps -eZ | grep screen
system_u:object_r:unlabeled_t    5047 ?        00:00:00 screen

In permissive mode, this will be visible easily; in enforcing mode, the domains you are running in might not be allowed to do anything with unlabeled_t files, directories and processes, so ps might not show it even though it still exists:

test audit # ps -eZ | grep 5047
test audit # ls -dZ /proc/5047
ls: cannot access /proc/5047: Permission denied
test audit # tail audit.log | grep unlabeled
type=AVC msg=audit(1368698097.494:27806): avc:  denied  { getattr } for  pid=4137 comm="bash" path="/proc/5047" dev="proc" ino=6677 scontext=root:sysadm_r:sysadm_t tcontext=system_u:object_r:unlabeled_t tclass=dir

Notice that, if you reload the module, the process becomes visible again. That is because the process context itself (screen_t) is retained, but because the policy doesn’t know it anymore, it shows it as unlabeled_t.

Basically, the moment the policy doesn’t know how a label would be (should be), it uses unlabeled_t. The SELinux policy then defines how this unlabeled_t domain is handled. Processes getting into unlabeled_t is not that common though as there is no supported transition to it. The above one is one way that this still can occur.

Posted in SELinux | Tagged , , | Leave a comment

A simple IPv6 setup

For internal communication between guests on my workstation, I use IPv6 which is set up using the Router Advertisement “feature” of IPv6. The tools I use are dnsmasq for DNS/DHCP and router advertisement support, and dhcpcd as client. It might be a total mess (grew almost organically until it worked), but as far as I’m concerned, it is working… and that is all that matters (for now). I’ll have to look deeper into the IPv6 stuff to understand it all better though.

On the client side, dhcpcd is ran with the following options:

dhcpcd_eth0="-t 5 -L --ipv6ra_own"

I had to enable --ipv6ra_own to get it to obtain its global address, otherwise it only got its link local one (fe80:: something). I also added a hook into /lib/dhcpcd/dhcpcd-hooks to get it to trigger a hostname update for IPv6.

$ cat 28-set-ip6-address 
if $ifup; then export new_ip_address=${ra1_prefix%%/64}; fi

SELinux-policy wise, I had to enable dhcpc_t to write to the hostname proc file and set the system hostname. The first one (21) is needed because of the --ipv6ra_own parameter.

# selocal -l | grep dhcpc_t
21: allow dhcpc_t self:rawip_socket create_socket_perms; # dhcpclient
22: kernel_rw_kernel_sysctl(dhcpc_t) # set hostname
23: allow dhcpc_t self:capability sys_admin; # set hostname

Finally, in /etc/dhcpcd.conf, I removed the nohook lookup-hostname and set the force_hostname one:

#nohook lookup-hostname
env force_hostname=YES

On the server side, I use the following configuration of dnsmasq (snippet):

dhcp-range=2001:db8:81:e2::,ra-only
enable-ra
dhcp-option=option6:dns-server,[2001:db8:81:e2::26b5:365b:5072]

As you can see, I use the documentation prefix for now (since it is meant for internal communication only, and makes it easier to copy/paste into documentation ;-) but when I am going to use full IPv6 access to the Internet, this prefix will of course change.

Finally, I enabled IPv6 forwarding on the tap0 interface because otherwise I continuously got the following messages on the clients:

May 12 18:43:07 test dhcpcd[3869]: eth0: adding default route via fe80::d848:19ff:fe0d:55c2
May 12 18:43:07 test dhcpcd[3869]: eth0: fe80::d848:19ff:fe0d:55c2 is no longer a router
May 12 18:43:07 test dhcpcd[3869]: eth0: deleting default route via fe80::d848:19ff:fe0d:55c2
May 12 18:43:13 test dhcpcd[3869]: eth0: fe80::d848:19ff:fe0d:55c2 is unreachable, expiring it

To enable IPv6 forwarding, you can use sysctl but I added it in the script that sets up the tap0 interface:

tunctl -b -u swift -t tap0
ifconfig tap0 add 2001:db8:81:e2::26b5:365b:5072/64
vde_switch --numports 16 --mod 777 --group users --tap tap0 -d
echo 1 > /proc/sys/net/ipv6/conf/tap0/forwarding
Posted in Uncategorized | Tagged , , , , | Leave a comment

The weird “audit_access” permission

While writing up the posts on capabilities, one thing I had in my mind was to give some additional information on frequently occurring denials, such as the dac_override and dac_read_search capabilities, and when they are triggered. For the DAC-related capabilities, policy developers often notice that these capabilities are triggered without a real need for them. So in the majority of cases, the policy developer wants to disable auditing of this:

dontaudit <somedomain> self:capability { dac_read_search dac_override };

When applications wants to search through directories not owned by the user as which the application runs, both capabilities will be checked – first the dac_read_search one and, if that is denied (it will be audited though) then dac_override is checked. If that one is denied as well, it too will be audited. That is why many developers automatically dontaudit both capability calls if the application itself doesn’t really need the permission.

Let’s say you allow this because the application needs it. But then another issue comes up when the application checks file attributes or access permissions (which is a second occurring denial that developers come across with). Such applications use access() or faccessat() to get information about files, but other than that don’t do anything with the files. When this occurs and the domain does not have read, write or execute permissions on the target, then the denial is shown even when the application doesn’t really read, write or execute the file.

#include <stdio.h>
#include <unistd.h>

int main(int argc, char ** argv) {
  printf("%s: Exists (%d), Readable (%d), Writeable (%d), Executable (%d)\n", argv[1],
    access(argv[1], F_OK), access(argv[1], R_OK),
    access(argv[1], W_OK), access(argv[1], X_OK));
}
$ check /var/lib/logrotate.status
/var/lib/logrotate.status: Exists (0), Readable (-1), Writeable (-1), Executable (-1)

$ tail -1 /var/log/audit.log
...
type=AVC msg=audit(1367400559.273:5224): avc:  denied  { read } for  pid=12270 comm="test" name="logrotate.status" dev="dm-3" ino=2849 scontext=staff_u:staff_r:staff_t tcontext=system_u:object_r:logrotate_var_lib_t tclass=file

This gives the impression that the application is doing nasty stuff, even when it is merely checking permissions. One way would be to dontaudit read as well, but if the application does the check against several files of various types, that might mean you need to include dontaudit statements for various domains. That by itself isn’t wrong, but perhaps you do not want to audit such checks but do want to audit real read attempts. This is what the audit_access permission is for.

The audit_access permission is meant to be used only for dontaudit statements: it has no effect on the security of the system itself, so using it in allow statements has no effect. The purpose of the permission is to allow policy developers to not audit access checks without really dontauditing other, possibly malicious, attempts. In other words, checking the access can be dontaudited while actually attempting to use the access (reading, writing or executing the file) will still result in the proper denial.

Posted in Security, SELinux | Tagged , , , | Leave a comment

Commandline SELinux policy helper functions

To work on SELinux policies, I use a couple of functions that I can call on the shell (command line): seshowif, sefindif, seshowdef and sefinddef. The idea behind the methods is that I want to search (find) for an interface (if) or definition (def) that contains a particular method or call. Or, if I know what the interface or definition is, I want to see it (show).

For instance, to find the name of the interface that allows us to define file transitions from the postfix_etc_t label:

$ sefindif filetrans.*postfix_etc
contrib/postfix.if: interface(`postfix_config_filetrans',`
contrib/postfix.if:     filetrans_pattern($1, postfix_etc_t, $2, $3, $4)

Or to show the content of the corenet_tcp_bind_http_port interface:

$ seshowif corenet_tcp_bind_http_port
interface(`corenet_tcp_bind_http_port',`
        gen_require(`
                type http_port_t;
        ')

        allow $1 http_port_t:tcp_socket name_bind;
        allow $1 self:capability net_bind_service;
')

For the definitions, this is quite similar:

$ sefinddef socket.*create
obj_perm_sets.spt:define(`create_socket_perms', `{ create rw_socket_perms }')
obj_perm_sets.spt:define(`create_stream_socket_perms', `{ create_socket_perms listen accept }')
obj_perm_sets.spt:define(`connected_socket_perms', `{ create ioctl read getattr write setattr append bind getopt setopt shutdown }')
obj_perm_sets.spt:define(`create_netlink_socket_perms', `{ create_socket_perms nlmsg_read nlmsg_write }')
obj_perm_sets.spt:define(`rw_netlink_socket_perms', `{ create_socket_perms nlmsg_read nlmsg_write }')
obj_perm_sets.spt:define(`r_netlink_socket_perms', `{ create_socket_perms nlmsg_read }')
obj_perm_sets.spt:define(`client_stream_socket_perms', `{ create ioctl read getattr write setattr append bind getopt setopt shutdown }')

$ seshowdef manage_files_pattern
define(`manage_files_pattern',`
        allow $1 $2:dir rw_dir_perms;
        allow $1 $3:file manage_file_perms;
')

I have these defined in my ~/.bashrc (they are simple functions) and are used on a daily basis here ;-) If you want to learn a bit more on developing SELinux policies for Gentoo, make sure you read the Gentoo Hardened SELinux Development guide.

Posted in Hardened, SELinux | Tagged , , , , , , | Leave a comment

Looking at the local Linux kernel privilege escalation

There has been a few posts already on the local Linux kernel privilege escalation, which has received the CVE-2013-2094 ID. arstechnica has a write-up with links to good resources on the Internet, but I definitely want to point readers to the explanation that Brad Spengler made on the vulnerability.

In short, the vulnerability is an out-of-bound access to an array within the Linux perf code (which is a performance measuring subsystem enabled when CONFIG_PERF_EVENTS is enabled). This subsystem is often enabled as it offers a wide range of performance measurement techniques (see its wiki for more information). You can check on your own system through the kernel configuration (zgrep CONFIG_PERF_EVENTS /proc/config.gz if you have the latter pseudo-file available – it is made available through CONFIG_IKCONFIG_PROC).

The public exploit maps memory in userland, fills it with known data, then triggers an out-of-bound decrement that tricks the kernel into decrementing this data (mapped in userland). By looking at where the decrement occurred, the exploit now knows the base address of the array. Next, it targets (through the same vulnerability) the IDT base (Interrupt Descriptor Table) and targets the overflow interrupt vector. It increments the top part of the address that the vector points to (which is 0xffffffff, becoming 0×00000000 thus pointing to the userland), maps this memory region itself with shellcode, and then triggers the overflow. The shell code used in the public exploit modifies the credentials of the current task, sets uid/gid with root and gives full capabilities, and then executes a shell.

As Brad mentions, UDEREF (an option in a grSecurity enabled kernel) should mitigate the attempt to get to the userland. On my system, the exploit fails with the following (start of) oops (without affecting the system further) when it tries to close the file descriptor returned from the syscall that invokes the decrement:

[ 1926.226678] PAX: please report this to pageexec@freemail.hu
[ 1926.227019] BUG: unable to handle kernel paging request at 0000000381f5815c
[ 1926.227019] IP: [] sw_perf_event_destroy+0x1a/0xa0
[ 1926.227019] PGD 58a7c000 
[ 1926.227019] Thread overran stack, or stack corrupted
[ 1926.227019] Oops: 0002 [#4] PREEMPT SMP 
[ 1926.227019] Modules linked in: libcrc32c
[ 1926.227019] CPU 0 
[ 1926.227019] Pid: 4267, comm: test Tainted: G      D      3.8.7-hardened #1 Bochs Bochs
[ 1926.227019] RIP: 0010:[]  [] sw_perf_event_destroy+0x1a/0xa0
[ 1926.227019] RSP: 0018:ffff880058a03e08  EFLAGS: 00010246
...

The exploit also finds that the decrement didn’t succeed:

test: semtex.c:76: main: Assertion 'i<0x0100000000/4' failed.

A second mitigation is that KERNEXEC (also offered through grSecurity) which prevents the kernel from executing data that is writable (including userland data). So modifying the IDT would be mitigated as well.

Another important mitigation is TPE – Trusted Path Execution. This feature prevents the execution of binaries that are not located in a root-owned directory and owned by a trusted group (which on my system is 10 = wheel). So users attempting to execute such code will fail with a Permission denied error, and the following is shown in the logs:

[ 3152.165780] grsec: denied untrusted exec (due to not being in trusted group and file in non-root-owned directory) of /home/user/test by /home/user/test[bash:4382] uid/euid:1000/1000 gid/egid:100/100, parent /bin/bash[bash:4352] uid/euid:1000/1000 gid/egid:100/100

However, even though a nicely hardened system should be fairly immune against the currently circling public exploit, it should be noted that it is not immune against the vulnerability itself. The methods above mentioned make it so that that particular way of gaining root access is not possible, but it still allows an attacker to decrement and increment memory in specific locations so other exploits might be found to modify the system.

Now out-of-bound vulnerabilities are not new. Recently (february this year), a vulnerability in the networking code also provided an attack vector to get a local privilege escalation. A mandatory access control system like SELinux has little impact on such vulnerabilities if you allow users to execute their own code. Even confined users can modify the exploit to disable SELinux (since the shell code is ran with ring0 privileges it can access and modify the SELinux state information in the kernel).

Many thanks to Brad for the excellent write-up, and to the Gentoo Hardened team for providing the grSecurity PaX/TPE protections in its hardened-sources kernel.

Posted in Hardened, Linux, Security | Tagged , , , , , , , , | 1 Comment