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