A SELinux policy for incron: the incrond daemon

a-selinux-policy-for-incron-the-incrond-daemon

Sven Vermeulen Mon 27 May 2013

With incrontab_t (hopefully) complete, let's look at the incrond_t domain. As this domain will also be used to execute the user (and system) commands provided through the incrontabs, we need to consider how we are going to deal with this wide range of possible permissions that it might take. One would be to make incrond_t quite powerful, and extend its privileges as we go further. But in my opinion, that's not a good way to deal with it.

Another would be to support a small set of permissions, and introduce an interface that other modules can use to create a transition when incrond_t executes a script properly labeled for a transition. For instance, a domain foo_t might have an executable type foo_exec_t. Most modules support an interface similar to foo_domtrans (and foo_role if roles are applicable as well), but that assumes that the incron policy is modified every time a new target module is made available (since we then need to add the proper *_domtrans rules to the incron policy. Instead, we might want to make this something that the foo SELinux module can decide.

It is that approach that we are going to take here. To do so, we will create a new interface called incron_entry, taken a bit from the cron_system_entry interface already in place for the regular cron domain (the following comes in incron.if):

## <summary>
##      Make the specified program domain
##      accessible from the incrond job.
## </summary>
## <param name="domain">
##      <summary>
##      The type of the process to transition to
##      </summary>
## </param>
## <param name="entrypoint">
##      <summary>
##      The type of the file used as an entrypoint to this domain
##      </summary>
## </param>
#
interface(`incron_entry',`
        gen_require(`
                type incrond_t;
        ')

        domtrans_pattern(incrond_t, $2, $1)
')

With this in place, the foo SELinux module can call incron_entry(foo_t, foo_exec_t) so that, the moment incrond_t executes a file with label foo_exec_t, the resulting process will run in foo_t. I am going to test (and I stress that it is only for testing) this by assigning incron_entry(system_cronjob_t, shell_exec_t), making every shell script being called run in system_cronjob_t domain (for instance in the localuser.te file that already assigned incron_role to the user_t domain.

With that in place, it's time to start our iterations again.

# run_init rc-service incrond start
* start-stop-daemon: failed to start '/usr/sbin/incrond' [ !! ]
* ERROR: incrond failed to start

# tail audit.log
type=AVC msg=audit(1368732494.275:28319): avc:  denied  { read } for  pid=9282 comm="incrond" name="localtime" dev="dm-2" ino=393663 scontext=system_u:system_r:incrond_t tcontext=system_u:object_r:locale_t tclass=file
type=AVC msg=audit(1368732494.275:28320): avc:  denied  { create } for  pid=9282 comm="incrond" scontext=system_u:system_r:incrond_t tcontext=system_u:system_r:incrond_t tclass=unix_dgram_socket
type=AVC msg=audit(1368732494.276:28321): avc:  denied  { read } for  pid=9282 comm="incrond" name="incron.d" dev="dm-2" ino=394140 scontext=system_u:system_r:incrond_t tcontext=root:object_r:incron_spool_t tclass=dir
type=AVC msg=audit(1368732494.276:28322): avc:  denied  { create } for  pid=9282 comm="incrond" scontext=system_u:system_r:incrond_t tcontext=system_u:system_r:incrond_t tclass=unix_dgram_socket
type=AVC msg=audit(1368732494.276:28323): avc:  denied  { create } for  pid=9282 comm="incrond" scontext=system_u:system_r:incrond_t tcontext=system_u:system_r:incrond_t tclass=unix_dgram_socket

Ignoring the unix_dgram_socket for now, we need to allow incrond_t to read locale information, and to read the files in the /var/spool/incron location (this goes in incron.te again):

###########################################
#
# incrond policy
#

read_files_pattern(incrond_t, incron_spool_t, incron_spool_t)

files_search_spool(incrond_t)

miscfiles_read_localization(incrond_t)

The next run fails again, with the following denials:

type=AVC msg=audit(1368732806.757:28328): avc:  denied  { create } for  pid=9419 comm="incrond" scontext=system_u:system_r:incrond_t tcontext=system_u:system_r:incrond_t tclass=unix_dgram_socket
type=AVC msg=audit(1368732806.757:28329): avc:  denied  { read } for  pid=9419 comm="incrond" name="incron.d" dev="dm-2" ino=394140 scontext=system_u:system_r:incrond_t tcontext=root:object_r:incron_spool_t tclass=dir
type=AVC msg=audit(1368732806.757:28330): avc:  denied  { create } for  pid=9419 comm="incrond" scontext=system_u:system_r:incrond_t tcontext=system_u:system_r:incrond_t tclass=unix_dgram_socket
type=AVC msg=audit(1368732806.757:28331): avc:  denied  { create } for  pid=9419 comm="incrond" scontext=system_u:system_r:incrond_t tcontext=system_u:system_r:incrond_t tclass=unix_dgram_socket

So although incrond_t has search rights on the incron_spool_t directories (through the read_files_pattern), we need to grant it list_dir_perms as well (which contains the read permission). As list_dir_perms contains search anyhow, we can just update the line with:

allow incrond_t incron_spool_t:dir list_dir_perms;
allow incrond_t incron_spool_t:file read_file_perms;

Now the startup seems to work, but we still get denials:

# run_init rc-service incrond start
* Starting incrond... [ ok ]

# ps -eZ | grep incrond
# tail /var/log/cron.log
(nothing)

# tail audit.log
type=AVC msg=audit(1368733443.799:28340): avc:  denied  { create } for  pid=9551 comm="incrond" scontext=system_u:system_r:incrond_t tcontext=system_u:system_r:incrond_t tclass=unix_dgram_socket
type=AVC msg=audit(1368733443.802:28341): avc:  denied  { write } for  pid=9552 comm="incrond" name="/" dev="tmpfs" ino=1970 scontext=system_u:system_r:incrond_t tcontext=system_u:object_r:var_run_t tclass=dir
type=AVC msg=audit(1368733443.806:28342): avc:  denied  { create } for  pid=9552 comm="incrond" scontext=system_u:system_r:incrond_t tcontext=system_u:system_r:incrond_t tclass=unix_dgram_socket
type=AVC msg=audit(1368733443.806:28343): avc:  denied  { create } for  pid=9552 comm="incrond" scontext=system_u:system_r:incrond_t tcontext=system_u:system_r:incrond_t tclass=unix_dgram_socket

Those unix_dgram_sockets are here again. But seeing that cron.log is empty, and logging_send_syslog_msg is one of the interfaces that would enable it, we might want to do just that so that we get more information about why incrond doesn't properly start. Also, it tries to write into var_run_t labeled directories, probably for its PID file, so add in a proper file transition as well as manage rights:

type incrond_var_run_t;
files_pid_file(incrond_var_run_t)
...
allow incrond_t incrond_var_run_t:file manage_file_perms;
files_pid_filetrans(incrond_t, incrond_var_run_t, file)
...
logging_send_syslog_msg(incrond_t)

With that in place:

# run_init rc-service incrond start
* Starting incrond... [ ok ]

# ps -eZ | grep incron
system_u:system_r:incrond_t      9648 ?        00:00:00 incrond

# tail /var/log/cron.log 
May 16 21:51:34 test incrond[9647]: starting service (version 0.5.10, built on May 16 2013 12:11:29)
May 16 21:51:34 test incrond[9648]: loading system tables
May 16 21:51:34 test incrond[9648]: loading user tables
May 16 21:51:34 test incrond[9648]: table for invalid user user found (ignored)
May 16 21:51:34 test incrond[9648]: ready to process filesystem events

# tail audit.log
type=AVC msg=audit(1368733894.641:28347): avc:  denied  { read } for  pid=9648 comm="incrond" name="nsswitch.conf" dev="dm-2" ino=393768 scontext=system_u:system_r:incrond_t tcontext=system_u:object_r:etc_t tclass=file
type=AVC msg=audit(1368733894.645:28349): avc:  denied  { read } for  pid=9648 comm="incrond" name="passwd" dev="dm-2" ino=394223 scontext=system_u:system_r:incrond_t tcontext=system_u:object_r:etc_t tclass=file

It looks like we're getting there. Similar as with incrontab we allow auth_use_nsswitch as well, and then get:

# tail cron.log
May 16 21:55:10 test incrond[9715]: starting service (version 0.5.10, built on May 16 2013 12:11:29)
May 16 21:55:10 test incrond[9716]: loading system tables
May 16 21:55:10 test incrond[9716]: loading user tables
May 16 21:55:10 test incrond[9716]: loading table for user user
May 16 21:55:10 test incrond[9716]: access denied on /home/user/test2 - events will be discarded silently
May 16 21:55:10 test incrond[9716]: cannot create watch for user user: (13) Permission denied
May 16 21:55:10 test incrond[9716]: ready to process filesystem events

# tail audit.log
type=AVC msg=audit(1368734110.912:28353): avc:  denied  { getattr } for  pid=9716 comm="incrond" path="/home/user/test2" dev="dm-0" ino=16 scontext=system_u:system_r:incrond_t tcontext=user_u:object_r:user_home_t tclass=dir
type=AVC msg=audit(1368734110.913:28354): avc:  denied  { read } for  pid=9716 comm="incrond" name="test2" dev="dm-0" ino=16 scontext=system_u:system_r:incrond_t tcontext=user_u:object_r:user_home_t tclass=dir

What happens is that incrond read the (user) crontab, found that it had to "watch" /home/user/test2 but fails because SELinux doesn't allow it to do so. We could just allow that, but we might do it a bit better by looking into what we want it to do in a flexible manner... next time ;-)