Communicating over D-Bus using SELinux

June 24, 2021

The D-Bus daemon provides an inter-process communication channel between applications. Unlike traditional IPC methods, D-Bus is a higher-level communication channel that offers more than simple signaling or memory sharing. Applications that want to chat over D-Bus link with one of the many D-Bus-compatible libraries, such as those provided by the libdbus, sd-bus (part of systemd), GDBus, and QtDBus applications.

The D-Bus daemon is part of the systemd application suite.

Understanding D-Bus

Linux generally supports two D-Bus types – system-wide and session-specific D-Bus instances:

  • The system-wide D-Bus is the main instance used for system communication. Many services or daemons will associate themselves with the system D-Bus to allow others to communicate with them through D-Bus.
  • The session-specific D-Bus is an instance running for each logged-in user. It is commonly used by graphical applications to communicate with each other within a user session.

Both D-Bus instances are provided through the dbus-daemon application. The system-wide D-Bus will run with the --system option, whereas a session-specific instance will run with the --session option.

Applications register themselves against D-Bus through a namespace. Conventionally, this namespace uses the domain name of the project. For instance, systemd declares the org.freedesktop.systemd1 namespace, whereas D-Bus is at org.freedesktop.DBus.

The currently associated applications can be queried using Python easily:

# python3.6
>>> import dbus
>>> for service in dbus.SystemBus().list_names():
...   print(service)
org.freedesktop.DBus
org.freedesktop.login1
org.freedesktop.systemd1
org.freedesktop.PolicyKit1
com.redhat.tuned
:1.10
:1.11
org.freedesktop.NetworkManager
...

Each application then provides objects on the bus that can be reached by other objects (other applications)—of course, assuming they have the privileges to do so. These objects are represented through a path-like syntax and generally also use the domain of the project as a prefix.

For instance, to list the objects currently associated with org.freedesktop.systemd1, we can use the gdbus command. To facilitate its use, we first enable auto-completion support, after which we can use the Tab key to easily add the appropriate values:

# source /usr/share/bash-completion/completions/gdbus
# gdbus call --system --dest <TAB><TAB>
# gdbus call --system --dest org.freedesktop.systemd1 --object-path /org/freedesktop/systemd1<TAB><TAB>
Display all 220 possibilities? (y or no)
/org/freedesktop/systemd1
/org/freedesktop/systemd1/job
/org/freedesktop/systemd1/unit
...

Applications can trigger methods on these objects, or send messages to the applications bound to these objects through these methods.

For instance, to get the state of the sshd.service unit through D-Bus, we invoke the org.freedesktop.systemd1.Manager.GetUnitFileState method on the org.freedesktop.systemd1 object reachable through the /org/freedesktop/systemd1 path, and with the sshd.service argument, like this:

# gdbus call --system \
  --dest org.freedesktop.systemd1 \
  --object-path /org/freedesktop/systemd1 \
  --method org.freedesktop.systemd1.Manager.GetUnitFileState \
  sshd.service
('enabled',)

These calls can also be controlled through the SELinux policy, as we will learn next.

Controlling service acquisition with SELinux

The D-Bus application, like systemd, will query the SELinux policy to verify whether to allow an operation. Again, it is the D-Bus application itself that enforces the policy and not a Linux kernel subsystem.

The first control that administrators can enable within D-Bus is to ensure that only well-established domains can acquire a specified object within D-Bus. Without this control, malicious code could register itself as org.freedesktop.login1, for instance, and act as a system daemon on the bus. Other applications might mistakenly send out sensitive information to the application.

Applications store this policy information in files hosted in /usr/share/dbus-1/system.d. The login service, for instance (stored as org.freedesktop.login1.conf) has the following policy snippet installed:

<busconfig>
  <policy user="root">
    <allow own="org.freedesktop.login1"/>
    <allow send_destination="org.freedesktop.login1"/>
    <allow receive_sender="org.freedesktop.login1"/>
  </policy>
  <policy context="default">
    <deny send_destination="org.freedesktop.login1"/>
    <allow
       send_destination="org.freedesktop.login1"
       send_interface="org.freedesktop.DBus.Introspectable"/>
    ...
  </policy>
</busconfig>

As the login daemon runs in the systemd_logind_t domain, we could enhance this configuration as follows:

<busconfig>
  <selinux>
    <associate
      own="org.freedesktop.login1"
      context="system_u:system_r:systemd_logind_t:s0" />
  </selinux>
  ...
</busconfig>

With this enhancement in place, D-Bus will check whether the application (which we presume is running in the systemd_logind_t context) has the acquire_svc permission (of the dbus class) against the systemd_logind_t context. By default, the SELinux policy does not have this permission, and as such, the registration fails:

# systemctl restart dbus-org.freedesktop.login1
Job for systemd-logind.service failed because a timeout was exceeded.
See "systemctl status systemd-logind.service" and "journalctl -xe" for details.
# ausearch -m user_avc -ts recent

When we add the following SELinux policy rule, the registration of systemd-logind will succeed, as expected:

(allow systemd_logind_t systemd_logind_t (dbus (acquire_svc)))

Load this policy (say test.cil) and try the restart operation again:

# semodule -i test.cil
# systemctl restart dbus-org.freedesktop.login1

By limiting which domains can obtain a given service, we ensure that only trusted applications are used. Non-trusted applications will generally not run within the domain of that application (end users, for instance, cannot trigger a transition to such a domain) even if they receive root privileges (which is another check that D-Bus does for the login service, as shown in the first busconfig snippet).

Administrators can enhance this D-Bus configuration without having to alter the existing configuration files. For instance, the previously mentioned SELinux-governing busconfig snippet could very well be saved as a different file.

Governing message flows

A second control that D-Bus validates is which applications can communicate with each other. This is not configurable through the service configurations but is a pure SELinux policy control.

Whenever a source application is calling a method of a target application, D-Bus validates the send_msg permission between the two domains associated with the source and target applications.

For instance, communication over D-Bus between a user domain (sysadm_t) and service domain (systemd_logind_t) will check the following permissions:

allow sysadm_t systemd_logind_t : dbus send_msg;
allow systemd_logind_t sysadm_t : dbus send_msg;

If these permissions are not granted, then D-Bus will not allow the communication to happen. If at any point, the application context cannot be obtained, then the bus daemon context will be used.

Failures will be logged as USER_AVC entries in the audit log. If the communication should be allowed, we can create a simple SELinux policy file to address this like so:

(allow sysadm_t systemd_logind_t (dbus (send_msg)))
(allow systemd_logind_t sysadm_t (dbus (send_msg)))

Store these rules in a file with the suffix .cil (say, local_logind_systemd.cil), and load it with semodule:

# semodule -i local_logind_systemd.cil

Let’s consider a few other applications that have SELinux support, not necessarily built-in, but through the SELinux policy and PAM integration within the system.

Related Articles

No Results Found

The page you requested could not be found. Try refining your search, or use the navigation above to locate the post.

Lorem ipsum dolor sit amet consectetur

0 Comments

Submit a Comment

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

sixteen + 20 =