Log in

No account? Create an account

Previous Entry Share Next Entry
Running unconfined scripts from a confined domain
I received the following email from an engineer named Marko.

I've got a program (actually a shell script) which does various things and sudoers is configured so that users can run it without a pw being asked. By using sudo from the cmd line everything works as expected.

However, if the script is invoked by udev rules (and thus run as root) under certain circumstances, it fails on several cases due to AVCs as the commands it is trying to execute won't work or the services it starts/stops fail partially as the context (udev_t) is not what it'd be when run with sudo from the command line. Examples include inability to communicate with dbus, accessing log/config files, etc. So figuring out all of those would be quite a task and if the local program is later changed it might require even more policy changes.

So what would a custom policy look like for a program "foo" so that it would work equally well when invoked by udev like it now works when a user invokes it from the command like with sudo? Any examples, blog posts, howtos, etc to look at?

Basically since his users are most likely running as unconfined_t, the sudo command is running the script as unconfined_t and SELinux is not blocking anything.  When he runs the command out of udev, it is running as udev_t and udev_t is not allowed the access.

To solve this problem, Marko has three choices:
  1. Add the allow rules to allow udev to have all the access required to run his application
  2. Write policy for his application and transition from udev and unconfined_t to this new domain.
  3. Write policy to allow udev_t to transition to unconfined_t when running only his application.
Since Marko did not want to all udev scripts this access, he chose not to do 1.  Marko decided it would be to difficult to do 2 and difficult to maintain, so he chose to do 3.

Here is the original policy written by Marko.

# cat myapp.fc /usr/sbin/myapp --
gen_context(system_u:object_r:myapp_exec_t, s0)

# cat myapp.te
policy_module(myapp, 1.0.0)

require {
    type fs_t; type setfiles_t;
    type udev_t; type unconfined_t;
    class process transition;

type myapp_exec_t;

allow setfiles_t myapp_exec_t:file { getattr relabelto };
allow myapp_exec_t fs_t:filesystem { associate };

allow unconfined_t myapp_exec_t:file *;

domain_auto_trans(udev_t, myapp_exec_t, unconfined_t);

Not bad. 

The first problem I notice is lots of rules around myapp_exec_t.  Since the myapp_exec_t was never defined as a particular TYPE of type.  Since myapp_exec_t is planned to be used on a file or directory it needs to have the attribute  file_type.  All domains that interact with all files/directories, have rules defined in policy which allow them to interact with the attribute file_type.  You can add the file_type attribute to a file by using the files_type(myapp_exec_t) interface.  This will eliminate most of the rules involving myapp_exec_t.

As a rule of thumb, anytime you define a new type in policy you should call an interface to define the "TYPE" of the type.  domain_type() for processes, files_type for files/directories, dev_node() for devices corenet_port() for network ports etc.

Here is a my simpler policy.

policy_module(myapp, 1.0.1)
  type udev_t;
  type unconfined_t;
  role system_r;

type myapp_exec_t;

domain_auto_trans(udev_t, myapp_exec_t, unconfined_t);
allow unconfined_t myapp_exec_t:file entrypoint;
role system_r types unconfined_t;

The domain_auto_trans rule tells the SELinux kernel, when a process labeled udev_t executes a file labeled myapp_exec_t, transition the new process to unconfined_t. 

SELinux controls the labels of "entrypoint", so we need to state that myapp_exec_t is an entrypoint to the unconfined_t domain.
For a further description of the entrypoint access see: SELinux By Example 2.2.4

Finally I added the role definition for system_r, since udev_t will be running as system_r, when it executes myapp_t it will attempt to run unconfined_t was system_u:system_r:unconfined_t:s0.  roles system_r types unconfined_t, states that the unconfined_t type can be executed in the system_r role.

While the ideal solution for this problem from a security point of view would have been to write policy for myapp_t, and have unconfined_t and udev_t transition to this domain, as a work around and not shutting SELinux off this is a decent alternative.

Note:  When ever you write a shell script that will have more privs then the calling app, you should be really carefull that the calling application can not effect the running app.  The script needs to up its environment and ignores stuff from the calling program, if the script takes arguments you must be sure these arguments are handled properly and can not get the script to do something unexpected.  SELinux will protect you somewhat but you need to be careful.  Every python scripts I write, I always use "#! /usr/bin/python -Es" for example.  Apple has a nice page on this.