[apparmor] [RFC] Controlling the ability to start D-Bus services

Simon McVittie simon.mcvittie at collabora.co.uk
Wed Oct 21 17:56:57 UTC 2015


One of the features of D-Bus that are not typically present in other IPC
mechanisms is the ability to start services that have a .service file
on-demand (the jargon term is "service activation"). I'm currently
looking into ways to limit who can start services, particularly for
sandboxed applications.

There are two ways this can happen, either StartServiceByName:

* a client C sends a StartServiceByName message to the dbus-daemon,
  naming a service S, which is not currently running
* dbus-daemon checks in its own policies whether C is allowed to send
  StartServiceByName to it; by default, all clients of the well-known
  session and system buses are allowed to do this [*]
* If enabled, dbus-daemon checks with LSMs (currently SELinux or
  AppArmor) whether C is allowed to send StartServiceByName to it [*]
* dbus-daemon starts S, either directly or by asking systemd [*]
* S requests and receives its bus name [*]
* dbus-daemon sends a successful reply back to C

or auto-starting, which is preferred because it is simpler for clients
and avoids some race conditions:

* a client C sends any non-broadcast message M to the dbus-daemon,
  with its destination field set to a service S, which is not currently
  running
* dbus-daemon does *not* check whether C is allowed to send messages to
  S at this stage [1]
* dbus-daemon starts S, either directly or by asking systemd [*]
* S requests and receives its bus name [*]
* dbus-daemon checks whether C is allowed to send M to S, via
  its own policies (as above); by default, all clients of the
  well-known session bus may send messages, and all clients of the
  well-known system bus may send signals but not other messages
  [*][2]
* dbus-daemon checks whether S is allowed to receive M from C via
  its own policies; by default, all clients of the well-known
  session and system buses can receive any message [*]
* if enabled, dbus-daemon checks with LSMs whether C is allowed to send
  M to S, and whether S is allowed to receive M from C [*]
* dbus-daemon delivers M to S

Any step marked [*] can stop the process, either by failing or by
denying access.

If that happens during auto-starting, then S will still be started, even
though the auto-starting message M is never actually delivered. That
might be considered to be unexpected. The reason that there isn't a
check at that stage is that dbus-daemon cannot evaluate all of its
access-control rules at this stage, because it does not know most of the
attributes of the process that will implement S (in particular the uid,
the LSM context, and the complete set of bus names that it will have).

Our policy on this has traditionally been that this is not a bug:
StartServiceByName is not normally restricted, so if you don't want your
D-Bus service to be available for starting by anyone, don't install a
.service file. That's fine for traditional D-Bus services on mainstream
computers, but less good if you are using D-Bus activation to start
arbitrary applications that might have side-effects on startup, or if
your system is resource-constrained.

To reduce the impact on existing services and systems, it might be
interesting to add a new RestrictActivation key to .service files, with
new activation-time checks only performed for services that have it;
services wishing to be available to all users on-demand would not have
this key, but services with activation-time side-effects would have it.

For StartServiceByName, it is easy for an OS designer or a system
administrator to lock down who can call that method. However, it is
allowed by default in D-Bus, and is also allowed by default in
AppArmor's <abstractions/dbus-strict> and
<abstractions/dbus-session-strict> abstractions (which allow access to
the system or session bus, but restrict interaction with the bus and
services to a whitelist). One interesting limitation here is that the
D-Bus policy language doesn't currently have a way to discriminate
according to method arguments, and neither does AppArmor's D-Bus
mediation - so the choice here would currently be between allowing all
StartServiceByName calls from a particular client, or denying all of
them. A client can typically replace StartServiceByName calls with a
call to a harmless method (and get simpler code and reduced latency as a
side-effect), so the conservative thing to do would be to deny all of them.

For auto-starting, there is currently no way to limit who can start
services; the solution is to add a check at the step marked [1]. One
possible check would be to evaluate D-Bus' built-in "may send" policies
as if the message M was being delivered to a hypothetical service with
the bus name S requested as destination, and no other bus names. This
should not replace the check marked [2], because that check is carried
out with more information available, and could conceivably produce a
more negative result (it is possible to deny the ability to send to a
process owning a particular name, although I don't know why anyone would
want to do so).

D-Bus' "may receive" policies are not really suitable for step [1],
because they require that we know the uid of the service, which we do
not necessarily: a system service run with User=root might drop
privileges to a different user before connecting to D-Bus. However,
those policies are rarely (never?) used in practice, so that doesn't
seem like a real problem. I would recommend just not checking the
receive rules at [1]; they will be checked later anyway.

For systems using an LSM, we can potentially also do an LSM check at
[1]. Again, one possibility is to behave as though M was being delivered
to a service with the bus name S that was requested as destination, and
no other bus names, and do checks on that basis. In an AppArmor
environment, we would probably also want to check against an AppArmor
label, but we can't necessarily know what AppArmor profile transitions
the service S would go through during startup until we run it: its
eventual AppArmor label might not actually match the executable in the
Exec= line of its D-Bus .service file, if a profile-changing rule like
"/usr/lib/mydaemon Cx -> profile-for-daemons" has been applied. As a
result, what I have been prototyping is to have an optional
"AssumeAppArmorProfile" key in .service files. If present, the check at
[1] will assume that S will have that profile; if absent, no label is
specified while querying the kernel, which I think means that any rule
that specifies a peer label will not match.

Another possibility would be to introduce a new AppArmor action which
would be evaluated for both StartServiceByName and auto-activation: in
addition to

    dbus (send, receive) peer=(label=com.example.MyService),

we could perhaps have rules like

    dbus (activate) name=com.example.MyService,

This could either be implied by the ability to send to
peer=(name=com.example.MyService), or entirely independent. I'm not sure
whether this would need kernel-side changes, or whether it could be done
entirely in AppArmor user-space.

Any opinions on this?

-- 
Simon McVittie
Collabora Ltd. <http://www.collabora.com/>



More information about the AppArmor mailing list