Creating application-level SELinux policies

July 06, 2021

Application-level policies provide confinement for applications or services. There are a number of different types of application-level policies around:

  • End user application policies, which focus on accessing end user data, and will often call various userdom_* interfaces (which are provided through the system/userdomain.if file). Most of these applications are inside the apps/ directory).
  • Administration applications, which are still user-facing, are more likely to enable interacting with system services and resources.
  • Services, which are generally daemonized applications, often interact mostly with their own resources and have a simpler structure.

When we covered the sepolicy generate command, we could select these types (and more) to generate a simple skeleton for those applications.

Let’s look into some example policies and identify useful calls that you might need when developing your own policies.

Constructing network-facing service policies

Services that are network-facing (meaning they can be interacted with from outside the system itself) are the first set of services that need to be confined. Hence, building SELinux policy modules for network-facing services should be a primary focus of any Linux administrator that needs to ensure the confinement of applications that do not have a working policy yet.

If we look at the OpenVPN service, then we find that there is an SELinux policy under services/openvpn.te that we can look into.

Identifying the resources that the service interacts with

As a policy starts with identifying the types and other SELinux objects, we need to consider the resources on the system that the service interacts with. When comparing service policies with each other, you’ll notice that the definitions are often very similar:

  • The main domain type and its entry point executable are declared first. Depending on the type of service, it contains a call as to how it would be started: as a system service (using init_daemon_domain()) or through the D-Bus system bus (using dbus_system_domain()).
  • The configuration files for the service (such as openvpn_etc_t), which could also differentiate between read-only files and read-write (such as openvpn_etc_rw_t).
  • Runtime files (which are generally stored in /var/run) such as openvpn_runtime_t.
  • Temporary files (which are generally stored in /tmp or /var/tmp) such as openvpn_tmp_t.
  • Log files (which are generally stored in /var/log) such as openvpn_var_log_t.

Each of the type declarations is followed by a call that marks the type appropriately. For instance, the logging_log_file() call will associate the type with the logfile SELinux attribute. This allows general logfile management domains to deal with the newly created resource through this attribute.

Handling the internal SELinux rules

With the resources declared, we have to define the internal SELinux rules within the SELinux policy. These rules tell SELinux what the domain can do with its own resources, and how SELinux should behave when the resources are interacted with.

We will generally have two sets of internal rules declared. One is the fine-grained permissions of the domain itself, such as if the domain is allowed to have any capabilities, creating sockets, and so on. The development of these rules is trial-and-error based: start with close to no permissions, see what AVC denials come up, extend the policy, and repeat.

The other set of internal rules focuses on the interaction with the types declared earlier on. This not only includes which permissions the domain has (such as through the manage_files_pattern() calls) but also whether transitions have to occur.

Setting the right set of transitions is one of the more important first steps to take while developing application policies because audit2allow and AVC denials generally do not consider the fact that a target resource has the wrong type assigned. So when we have a service that creates files in /tmp (which is labeled as tmp_t), we really want the target files to be labeled correctly (such as openvpn_tmp_t) and not inherit the tmp_t label from the directory:

allow openvpn_t openvpn_tmp_t:file manage_file_perms;
files_tmp_filetrans(openvpn_t, openvpn_tmp_t, file)

File transitions should be declared for all the resources involved. If a transition has to occur for both files and directories, you can mix the classes in a single call like so:

files_tmp_filetrans(openvpn_t, openvpn_tmp_t, { file dir })

We can also tell SELinux that a transition should only occur if a specific filename is used:

logging_log_filetrans(openvpn_t, openvpn_status_t, file, "openvpn-status.log")

It is really recommended to first consider the file transitions (and other resource transitions) before expanding the actual permissions for the domain to make sure that we are not tempted to allow the domain privileges to general types when that is not necessary.

Adding network related permissions

While developing and expanding the policy, several core functions will be added, such as the kernel_* calls to allow processes to interact with the proc_t resources, system control settings, and more. Tools such as audit2allow will reasonably be able to deduce the right interfaces to call, although it does not hurt to review the interfaces to make sure not too many privileges are assigned.

Network related permissions on the other hand might require some more attention. SELinux can dynamically address certain network flows based on the system configuration.

It is likely that systems who do not have specific controls in place, such as labelled networking or SECMARK, will find that three interface calls could allow the application to work as intended:

corenet_tcp_bind_generic_node(openvpn_t)
corenet_tcp_bind_openvpn_port(openvpn_t)
corenet_tcp_connect_http_port(openvpn_t)

These three interface calls allow the domain to be network-oriented (corenet_tcp_bind_generic_node), listen to the OpenVPN port (corenet_tcp_bind_openvpn_port), as well as connecting, as a client, to HTTP ports (corenet_tcp_connect_http_port).

But other calls exist that you might need to add, even though they are currently not detected. They might become necessary when the system is tuned further, such as adding support for labeled networking or introducing SECMARK filtering.

The first set is to allow sending and receiving packets on generic nodes (hosts) and interfaces:

corenet_tcp_sendrecv_generic_node(openvpn_t)
corenet_tcp_sendrecv_generic_if(openvpn_t)

For NetLabel support, you might need to add support to receive labeled network packets:

corenet_all_recvfrom_netlabel(openvpn_t)

For SECMARK support, you need to add support for sending and receiving SECMARK labeled packets:

corenet_sendrecv_openvpn_server_packets(openvpn_t)
corenet_sendrecv_http_client_packets(openvpn_t)

These calls might not show up in early tests, but could be needed later on, and it is recommended to consider the impact of labeled networking and SECMARK on your policy from the beginning.

Building the service interface methods

We next focus our efforts on the interface methods. These are used to facilitate other SELinux policy modules to interact with the domain we’re developing, although they can also be used to simplify policy development for your own policy.

The three most common interfaces to define, and which other policy developers will assume exist, are the following:

  • A domain transition interface, such as openvpn_domtrans, allowing the given SELinux domain to execute the appropriate binaries or scripts and have the executed commands or applications run in our domain (and as such transition from the source domain to ours).
  • A run interface, such as openvpn_run, which is like the domain transition interface (and in fact will call it) but also allows our domain for the role. Without this interface, some roles might not be able to transition even if they call the domain transition interface.
  • An administration interface, such as openvpn_admin, which will be assigned to user roles/domains to allow them to administer our service. This will allow the user to interact with the processes of our domain (including killing the processes, tracing their actions, and so on) as well as to administer the files and resources used.

Within the interfaces, we need to declare the SELinux objects that we are going to explicitly reference. This allows the SELinux subsystem to validate whether the code is applicable or not: if the objects are not present in the current policy, then this interface is not valid and will not be used. Declaring objects is done with the gen_require() macro:

interface(`openvpn_run',`
 gen_require(`
 attribute_role openvpn_roles;
 ')
 openvpn_domtrans($1)
 roleattribute $2 openvpn_roles;
')

Other interfaces can be added as needed. While you can add interfaces already just in case, be aware that once you define an interface it can be used by other policies, and you might not be made aware of this if you are not developing all policies yourself. If you, later on, want to change the behavior of interfaces or remove them, you might break other policies.

Addressing user applications

If we develop end user applications, their structure will be very similar to those for more service-oriented applications. Content-wise, however, there are a few areas of attention to consider. Let’s use the apps/thunderbird.te policy as an example:

  • The first thing we notice is that many resource defining interfaces are prefixed with userdom_. For instance, a temporary file is not files_tmp_file() but userdom_user_tmp_file(). This will ensure that the resources are known as user-managed temporary files and not regular system temporary files.
  • Another important addition is the support for the X Desktop Group (XDG) locations. The XDG locations, defined in the XDG Base Directory specification at https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html, standardize which end user locations are used for what purpose. For instance, inside ~/.cache, application cache data is stored, whereas configuration data is in ~/.config.

    The use of the XDG locations is supported through the xdg SELinux policy module, which enables support for types of user content as well: regular documents, downloads, music, pictures, and videos. This distinction allows developing SELinux policies that are only able to interact with that specific end user content and not all the user’s data.

    For instance, the thunderbird application is able to manage download data (which by default is located in ~/Downloads) through the following:

xdg_manage_downloads(thunderbird_t)
  • To easily establish user content access, user applications should also call the userdom_user_content_access_template() template. This will automatically create booleans as well, which administrators can toggle. For instance, for the thunderbird SELinux policy, this will create thunderbird_manage_generic_user_content. If set, then thunderbird can not only access the downloads-related resources but all user resources.
  • Another template that user application policies will need if they are graphical in nature, is xserver_user_x_domain_template(). This template will generate X server related SELinux objects for the application, and allow the application to use the graphical environment on the server.

Note:

The reference policy makes a distinction between regular interfaces and templates. Interfaces grant privileges to the domains and roles that are passed to it. Templates on the other hand will generate new objects, such as SELinux booleans, types, attributes, and more. Code-wise, templates cannot be part of boolean-triggered statements (as they do not just add type enforcement rules).

When the baseline of a user application policy is drafted, including the preceding templates, then expanding the policy through trial and error should suffice. Do make sure, however, that all resources on the user location for which you are testing the application are correctly labeled, as otherwise, the denials might trick you into granting more privileges to the domain than necessary.

Related Articles

How to add swap space on Ubuntu 21.04 Operating System

How to add swap space on Ubuntu 21.04 Operating System

The swap space is a unique space on the disk that is used by the system when Physical RAM is full. When a Linux machine runout the RAM it use swap space to move inactive pages from RAM. Swap space can be created into Linux system in two ways, one we can create a...

read more

Lorem ipsum dolor sit amet consectetur

0 Comments

Submit a Comment

Your email address will not be published. Required fields are marked *

7 + fourteen =