On Mon, 2014-07-14 at 13:06 +0200, Tomasz Swierczek wrote:
> 3.      SAPI idea is not the way we’d like to go now
> 
> a.      We decided to experiment with DBus and Cynara access checks in
> DBus daemon as first step
> 
> b.     Most services use DBus and UDS-based services don’t usually
> interface the applications; UDS-based services are few and mostly our
> own code we’re already tampering with
> 
> c.      Contact points(s): Patrick Ohly (Intel, DBus modification) &
> Lukasz Wojciechowski (Cynara dev.)

Thanks for reporting back from the meeting. When I first heard about the
meeting at the end of last week, I wasn't even sure whether this topic
had been on the agenda ;-}

As D-Bus and Cynara integration now seems to have more attention in
Tizen, let me use the opportunity to give a status summary. I started
investigating what it would take to enhance dbus-daemon after TDC. So
far, I have only updated the API for obtaining a D-Bus client's Smack
label:

https://review.tizen.org/git/?p=platform%2Fupstream%2Fdbus.git;a=shortlog;h=refs%2Fheads%2Fsandbox%2Fpohly%2Ftizen

This is necessary because upstream rejected the current Tizen patch
(https://bugs.freedesktop.org/show_bug.cgi?id=47581).

Further specific work on policy enforcement is blocked by the need to
clarify goals and various Cynara aspects, in particular the asynchronous
API (see the separate mails about that on the list). But prototyping without
Cynara involved is possible (see the last section of this email) and it
is useful to discuss the concept before investing a lot of work into
the implementation.

Approaches
==========

We have two possible ways of securing D-Bus services:
     1. A D-Bus service calls Cynara and rejects to do things which are
        not allowed. It can reply with an error or simply ignore
        messages (TBD, see below).
     2. The dbus-daemon asks Cynara whether a) a client is allowed to
        send a certain message and/or b) whether it is allowed to
        receive one (for signals).

Option 1 is potentially more flexible, but it cannot work in practice
without at least some rudimentary support for option 2:
     A. Once we allow less privileged apps to connect to the D-Bus
        buses, we need a default-deny policy enforced by the dbus-daemon
        and then only selectively open up access to specific services
        and/or interfaces. Expecting all D-Bus services *and* apps to be
        resilient against unexpected messages is unrealistic.
     B. Signal delivery with unspecified recipient can only be
        controlled by dbus-daemon; all services would have to be
        modified to only use unicast signals.

What I have in mind for option 2 is an extension of the <allow/deny>
rules with a <check privilege="foobar"> rule. This <check> rule would
have the same match criteria as the <allow/deny> rules. If it matches, a
call to cynara with
      * client = Smack label of D-Bus client
      * user = UID of the client process
      * session_id = unique connection name (1)
      * privilege = "foobar"
determines whether the rule grants access. Note that in this model,
Cynara would never be used to take away access already granted before by
some other rule. This means that if the current state is "allowed" when
evaluating a check rule, it gets skipped, because a "DENIED" would be
ignored and an "OKAY" wouldn't change the state.

Alternatively, one could always call Cynara and change the state
accordingly, including a transition from "allowed" to "denied". I find
that model a bit harder to understand and it would be less efficient
(more Cynara checks); if it turns out to be needed, then changing the
implementation would be trivial.

(1) As I said before, I have my concerns about such an ad-hoc choice for
the session_id. I'd prefer to have a system-wide policy for computing
the session_id that matches how the user perceives the life time of an
application.

Filtering
=========

With rules as outlined above, we can filter by source, destination,
interface and member. Filtering by interface and member will be
problematic in KDBus because they are part of the opaque payload that
KDBus knows nothing about. When relying on these message attributes for
filtering, a different approach will be needed if and/or when the
services switches to KDBus.

Right now it looks like KDBus will not be a drop-in replacement anyway
and is unlikely to be in Tizen 3.0, so this shouldn't prevent us from
extending user-space routing via dbus-daemon. Canonical is also moving
ahead with their AppArmor patches for dbus-daemon.

There is a comment in the D-Bus man page about deny rules that says:
        
        send_destination and receive_sender rules mean that messages may not be 
sent to or received from the
        *owner* of the given name, not that they may not be sent *to that 
name*. That is, if a connection owns
        services A, B, C, and sending to A is denied, sending to B or C will 
not work either.

This also applies to allow rules. What that means is that all well-known
names in rules and messages get resolved to the bus name before
comparison. If a client is granted the right to send a message to
org.example.service1 currently owned by client :1.10, then it is also
allowed to send to that client using the name org.example.service2, if
that name is also owned by the service.

In practice this is not a problem, because we can and should always
include at least the interface in rules.

Existing Smack policy patches
=============================

We have Smack policy patches in Tizen. However, those pre-date Cynara
and rely on Smack rules being set up for each app to control what it has
access to. With the switch to Cynara, those rules will no longer be set
up (?).

The only use that I see for these patches is to override a default deny
for processes running with "User" label without involving Cynara. If we
manage to call Cynara from dbus-daemon, we can use the Cynara daemon
also for this particular aspect. For this to work, we only need a rule
that causes cynara_check(client = "User", privilege = "dbus")
to return OK.

My proposal is to drop these non-upstream Smack policy patches completely.

Broadcasts
==========

Broadcasting confidential information is problematic, as explained in my
comment on AMB's security requirements. A D-Bus service cannot control
which clients receive a signal unless it explicitly specifies only a
single recipient. In the current D-Bus subscriber model, this is not
possible because the service doesn't even get to know who is interested.

The dbus-daemon can route signals more selectively only to privileged
recipients. However, the rule matching cannot filter by message content.
The popular PropertyChanged signal cannot be filtered selectively.

This is only relevant if these signals are part of the API which we want
exposed to less-privileged clients. For privileged clients the default
allow rule would still let the signal through.

Specific dbus-daemon behavior
=============================

dbus-daemon evaluates policy rules in bus_client_policy_check_can_send()
and bus_client_policy_check_can_receive().

Both methods are supposed to return a response immediately without
blocking. My current thinking is that they must be extended to return a
"don't know yet" result. This must abort processing for the time being,
just like an out-of-memory error would. How this works for each call
chain varies.

For can_send it affects the processing of the data coming from a
specific client. This data queue can be frozen while waiting for a
response from Cynara. This is an advantage over handling the Cynara
check in the recipient, because it prevents a malicious or broken client
from sending further messages while its previous message gets considered.

can_receive is more difficult. We don't want to block the sender,
because it might have to service other clients. So we may have to
tentatively accept the message for the recipient, block the queue
delivering data to it and then later repeat the receive check.

This can cause messages to pile up inside dbus-daemon, in particular
when we apply this to broadcasts. This behavior can also already be observed in
normal D-Bus when a client subscribes to a signal and the stops reading
the data: memory consumption in dbus-daemon keeps going up until it
eventually hits the per-client memory limit (rather large though!) and
drops messages.

Handling rejections and user interaction
========================================

At the moment, dbus-daemon generates a
org.freedesktop.DBus.Error.AccessDenied error reply when a method call
gets rejected by the daemon. This is somewhat cryptic:
$ dbus-send --print-reply --dest=org.example.service1 /example/path 
org.example.Echo.SayHello
Error org.freedesktop.DBus.Error.AccessDenied: Rejected send message, 1 matched 
rules; type="method_call", sender=":1.8" (uid=1001 pid=13960 comm="dbus-send 
--print-reply --dest=org.example.service") interface="org.example.Echo" 
member="SayHello" error name="(unset)" requested_reply="0" 
destination="org.example.service1" (uid=57618 pid=13950 comm="/usr/bin/python 
/work/dbus/service.py ")

It includes information about the destination (uid, pid, comm) that we
might not want leaked to the caller (?).

It would be nicer if the AccessDenied error contained information about
all failed Cynara privilege checks, because then the app developer would
know what he needs to request in the manifest.

This problem is not limited to D-Bus. When an arbitrary service does a
Cynara check and that check fails, what should it tell the caller?

It was mentioned recently that a granted privilege may get revoked at
any time. How can an app be informed about that? Should it simply assume
that its next method call will succeed and only notify the user when
that fails?

Likewise, under which circumstances will the Cynara daemon pop up a request to
the user to grant access? As pointed out in one of the comments on the
LWN article about Cynara, asking the users for consent is problematic:
https://lwn.net/Articles/602303/

In D-Bus, when an app sets up a watch, that operation itself will not
cause a check call to Cynara. It's only receiving a signal matching the
watch filter which may trigger that. So in this case, the user
interaction may happen long after the app started up, and the app cannot
know whether the watch is really having the desired result. If the check
fails, dbus-daemon drops the signal message and delivers nothing to the app.

Prototyping D-Bus rule sets
===========================

It is possible to evaluate the proposed concept without patching
dbus-daemon. It is helpful (but not necessary) to recompile dbus-daemon
with --enable-developer or at least --enable-verbose-mode.

Attached are a sessions.conf and some configs which need to go into a
session.d subdirectory relative to the session.conf. The user.conf and
app.conf must be modified to contain your main user account resp. some
second test account.

Then run:

DBUS_VERBOSE=1 dbus-daemon --config-file=session.conf --nofork --print-address &
addr=unix:abstract=... (from dbus-daemon output)
DBUS_SESSION_BUS_ADDRESS=$addr ,/service.py &
DBUS_SESSION_BUS_ADDRESS=$addr dbus-send --print-reply 
--dest=org.example.service1 /example/path org.example.Echo.SayHello

When running dbus-send as less privileged test user, calling some
methods work, others get rejected. Eavesdropping with dbus-monitor works
as normal user and gets nothing as test user.

The test user cannot own well-know bus names (and thus cannot
impersonate system services) and is protected from most (all!?)
undesirable messages that a hostile app might send to it.

To test that selectively receiving signals works, run:

DBUS_SESSION_BUS_ADDRESS=$addr dbus-monitor "type='signal'"
DBUS_SESSION_BUS_ADDRESS=$addr dbus-send --print-reply 
--dest=org.example.service1 /example/path org.example.Echo.SayHello
DBUS_SESSION_BUS_ADDRESS=$addr dbus-send --print-reply 
--dest=org.example.service1 /example/path org.example.Time.Time

The normal user will get both signals, the test user only the Time
signal.

Next steps
==========

It would be good to get feedback from other D-Bus service developers
and/or maintainers whether writing such rule sets for their service is
possible and sufficient. John and Kevron already have the AR to check
this for AMB.

We also need to continue the Cynara API discussion. This determines what
changes are needed in dbus-daemon.

-- 
Best Regards, Patrick Ohly

The content of this message is my personal opinion only and although
I am an employee of Intel, the statements I make here in no way
represent Intel's position on the issue, nor am I authorized to speak
on behalf of Intel on this matter.



<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
 "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd";>
<busconfig>
  <!-- Our well-known bus type, don't change this -->
  <type>session</type>

  <listen>unix:tmpdir=/tmp</listen>

  <policy context="default">
    <!-- For testing only: all users can connect to this session bus -->
    <allow user="*"/>

    <!-- Holes must be punched in service configuration files for
         name ownership and sending method calls -->
    <deny own="*"/>
    <deny send_type="method_call"/>

    <!-- Reply messages (method returns, errors) are allowed by default. -->
    <allow send_requested_reply="true" send_type="method_return"/>
    <allow send_requested_reply="true" send_type="error"/>
    <allow receive_requested_reply="true" receive_type="method_return"/>
    <allow receive_requested_reply="true" receive_type="error"/>

    <!-- Signals are denied by default. -->
    <deny send_type="signal"/>

    <!-- No other messages may be received by default. -->
    <deny receive_type="method_call"/>
    <deny receive_type="error"/>
    <deny receive_type="signal"/>

    <!-- Reply messages may be received by default -->
    <allow receive_type="method_return"/>

    <!-- Allow anyone to talk to the message bus -->
    <allow send_destination="org.freedesktop.DBus"/>
    <!-- But disallow some specific bus services -->
    <deny send_destination="org.freedesktop.DBus"
          send_interface="org.freedesktop.DBus"
          send_member="UpdateActivationEnvironment"/>
  </policy>

  <!-- Config files are placed here that among other things, 
       further restrict the above policy for specific services. -->
  <includedir>session.d</includedir>

  <!-- This is included last so local configuration can override what's 
       in this standard file -->
  <include ignore_missing="yes">session-local.conf</include>

  <include if_selinux_enabled="yes" 
selinux_root_relative="yes">contexts/dbus_contexts</include>

  <!-- For the session bus, override the default relatively-low limits 
       with essentially infinite limits, since the bus is just running 
       as the user anyway, using up bus resources is not something we need 
       to worry about. In some cases, we do set the limits lower than 
       "all available memory" if exceeding the limit is almost certainly a bug, 
       having the bus enforce a limit is nicer than a huge memory leak. But the 
       intent is that these limits should never be hit. -->

  <!-- the memory limits are 1G instead of say 4G because they can't exceed 
32-bit signed int max -->
  <limit name="max_incoming_bytes">10000</limit>
  <limit name="max_incoming_unix_fds">250000000</limit>
  <limit name="max_outgoing_bytes">10000</limit>
  <limit name="max_outgoing_unix_fds">250000000</limit>
  <limit name="max_message_size">1000000000</limit>
  <limit name="max_message_unix_fds">1024</limit>
  <limit name="service_start_timeout">120000</limit>  
  <limit name="auth_timeout">240000</limit>
  <limit name="max_completed_connections">100000</limit>  
  <limit name="max_incomplete_connections">10000</limit>
  <limit name="max_connections_per_user">100000</limit>
  <limit name="max_pending_service_starts">10000</limit>
  <limit name="max_names_per_connection">50000</limit>
  <limit name="max_match_rules_per_connection">50000</limit>
  <limit name="max_replies_per_connection">50000</limit>

</busconfig>
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
 "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd";>
<busconfig>

  <!--

  This file grants rights selectively.

  When testing without Cynara, this policy gets applied to processes of
  a second user.

  With Cynara, these rules would be in the default context and of the
  form <check privilege="<some privilege>">, which Cynara needs to
  turn into <allow> for all processes with that privilege.

  Normally we would have one such file per D-Bus service which gets
  exposed to less-privileged apps. Services without such a file will
  only be available to "User" processes (via user.conf).

  -->

  <policy user="tester">
    <!--
    Enables calling *all* methods provided by the owner of the well-known name.
    Better be more selective.
    -->
    <!-- allow send_destination="org.example.service1" send_type="method_call"/ 
-->

    <!--
    Beware that this will match also when the app uses dest=:x.yz (if
    that bus name currently owns the well-known name) or
    dest=org.example.service2 (if the services owns that bus name in
    addition to org.example.service1).
    -->
    <allow send_destination="org.example.service1" 
send_interface="org.example.Echo" send_member="SayHello"/>

    <allow send_destination="org.example.service2" 
send_interface="org.example.Time"/>

    <allow receive_type="signal" receive_sender="org.example.service1" 
receive_interface="org.example.Time"/>
  </policy>

</busconfig>
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
 "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd";>
<busconfig>

  <!--

  This file grants all the normal rights to processes with full access
  to D-Bus.

  When testing without Cynara, only processes at a console get these
  rights.

  With Cynara, these rules would be in the default context and of the
  form <check privilege="dbus">, which Cynara needs to into <allow>
  for all processes running with the "User" Smack label.

  -->

  <policy user="pohly">
    <allow own="*"/>

    <allow send_type="method_call"/>
    <allow send_type="signal"/>
    <allow receive_type="method_call"/>
    <allow receive_type="signal"/>

    <!-- Copied verbatim from normal D-Bus session.conf. Needed to enabled 
eavesdropping. -->
    <allow send_destination="*" eavesdrop="true"/>
    <allow eavesdrop="true"/>
  </policy>

</busconfig>
#! /usr/bin/python

from dbus.mainloop.glib import DBusGMainLoop
import gobject
import dbus
import dbus.service
import time

DBusGMainLoop(set_as_default=True)
bus = dbus.SessionBus()

class Example(dbus.service.Object):
    def __init__(self, path):
        dbus.service.Object.__init__(self, bus, path)

    @dbus.service.method(dbus_interface='org.example.Echo',
                         in_signature='', out_signature='s',
                         sender_keyword='sender')
    def SayHello(self, sender=None):
        result = 'Hello, %s!' % sender
        self.EchoSignal(result)
        return result
        # -> something like 'Hello, :1.1!'

    @dbus.service.method(dbus_interface='org.example.Echo',
                         in_signature='', out_signature='s',
                         sender_keyword='sender')
    def SayHi(self, sender=None):
        result = 'Hi, %s!' % sender
        self.EchoSignal(result)
        return result
        # -> something like 'Hi, :1.1!'

    @dbus.service.signal(dbus_interface='org.example.Echo',
                         signature='s')
    def EchoSignal(self, message):
        pass

    @dbus.service.method(dbus_interface='org.example.Time',
                         in_signature='', out_signature='s')
    def Time(self):
        result = time.ctime(time.time())
        self.TimeSignal(result)
        return result

    @dbus.service.signal(dbus_interface='org.example.Time',
                         signature='s')
    def TimeSignal(self, time):
        pass

example = Example('/example/path')

name1 = dbus.service.BusName('org.example.service1')
name2 = dbus.service.BusName('org.example.service2')

loop = gobject.MainLoop()
loop.run()
_______________________________________________
Dev mailing list
Dev@lists.tizen.org
https://lists.tizen.org/listinfo/dev

Reply via email to