dbus-broker icon indicating copy to clipboard operation
dbus-broker copied to clipboard

Proposal: namespaces in Dbus

Open Loara opened this issue 1 month ago • 1 comments

Hi, I have prepared a little draft about a Namespace implementation for Dbus, useful especially to allow sandboxed and isolated process to access session bus with a fine grain control about the services. It moreover allows you to both share access to services on multiple namespaces and providing dummy services on a restricted namespace, to give the illusion to a sandbox process of being in a privileged environment.

The main advantage over other tools like xdg-dbus-proxy is that it is fully configurable inside the bus without writing complex policy rules or running many instances of xdg-dbus-proxy if you want to implement a tree-like structure with multiple levels of privileges.

Here my draft of such protocol. I have attached as a file instead of writing because it is a bit long. It is a Markdown file, but if you do not want to download it then I can paste it here.

draft.md

Loara avatar Nov 24 '25 17:11 Loara

(this is an inline copy of draft.md)

Dbus namespace draft 0.1

This is a draft to implement namespaces in dbus-broker. This is only a draft, any modification to improve it is welcomed. I prefer to first document well the planned behaviour of the software and the API and only after implementing it.

Namespace architecture

A namespace in Dbus is just a collection of connections that regulates how the various entities comunicate each other, and can be used to prevent communications between two or more different components.

Namespaces can be nested, and there exists a special namespace called root that contains all the other namespaces. Moreover, if namespace A contains B and B contains C, then A contains C by transitivity too.

A namespace cannot span on several different namespaces: if A and B are different namespaces then exactly one of the following situation may happen:

  • A contains B;
  • B contains A;
  • A and B are disjoint namespaces.

Therefore, we can always organize namespaces in a graph which is a directed tree with respect the inclusion relation with the root namespace as the tree root.

Namespaces and connections

Each connection to the bus is associated to a single namespace. Every connection believes that their namespace is the root namespace, and eventual connections associated to containing namespaces visible to the current process are seen by it as belonging to its namespace.

When a process connects to Dbus through a TCP/Unix socket the Dbus server automatically associates an already created namespace, it do not create new namespaces automatically (but this may be changed in future).

The server can use the autentication mechanism to choose the right namespace to which associate the connection. To preserve backward compatibility, the Dbus server may associate every connection made through the initial socket to the root namespace, but a privilegied user (in this document we intend with the word privilegied user any entity that has "sufficient privilegies" to do that thing) can tell Dbus to listen on an additional socket and associate all the connections on that socket on a different namespace. This is not required if a mechanism for moving already established connections between namespaces is implemented.

Message filtering

So each connection is associated with a single namespace. Connections in the same namespace can communicate freely, or at least without any additional limitation. But also connections in different namespace can interact, even with limitations. The limitations provided by namespaces for inter-process-communications exists only at connection/bus name level, they do not perform object filtering. If a process X can send requests to process Y without being blocked by namespaces, then it can perform requests on all Y objects allowed by Y.

Message filtering rules for namespaces are the following:

  • a process can send method calls to every other process in the same namespace and in other namespaces contained in its namespace;
  • a process can send method returns, signals and errors to every other process in the same namespace and in other namespaces containing its namespace.

It could seem to be a bad design to allow sending messages to other connections in a wider namespace. However, method replies should be allowed to be delivered in containing namespaces for consistence with method calling in contained namespaces. Moreover, we allow in this way connections to register and receive signals from conections in a nested contained namespace if they want.

We also point that connections in disjoint namespaces are never allowed to comunicate directly (clearly they can comunicate indirectly through a different connection in another namespace containing both their namespaces).

Additionally, the Dbus daemon performs translation of well-known named associated to connections in contained namespaces (explained in a next section) on reply/signals/errors sent to connections in containing namespaces, thus a connection in a contained namespace cannot impersonate another service in another namespace. Moreover, the Dbus daemon hides any well-known name associated to connections in containing namespaces to connections in contained ones, so that if a connection receives a method call from a connection in a containing namespace it would only see its unique name, not its eventual well-known names.

Let's consider the following example with the following namespaces:

root --\
  |    |
  V    V
nameA nameB
  |
  V
nameC
  |
  V
nameD

and the following connections (for simplicity we do not follow Dbus naming conventions here):

  • :p-root in root namespace with well-known name (WKN) N.Root;
  • :p-a in nameA with WKN N.A;
  • :p-a2 in nameA with WKN N.A2;
  • :p-b in nameB with WKN N.B;
  • :p-c in nameC with WKN N.C;
  • :p-d in nameD with WKN N.D.

Then connection :p-a can send call messages to and receiving reply/signals/errors from the following connection names, taking in account the Dbus name translation:

:p-a2
N.A2
:p-c
nameC/N.C
:p-d
nameC/nameD/N.D

Connection :p-a can send reply/signals/errors to and receiving calls from the following connection names, taking again in account Dbus name translation:

:p-a2
N.A2
:p-root

Notice that :p-a cannot know that :p-root has the WKN N.Root through send/received messages. Moreover, it is impossible for :p-a to send/receive anything to/from :p-b since the namespaces are disjoint. On the contrary, connection :p-a cannot ever know if namespace nameB exists or not and if there are connections associated with it without the complicity of some other connection in N.Root.

As suggested by this example, unique connection names must be unique among every namespace on the same bus. To avoid leaking information about hidden namespace, you should assign random unique bus names to connections.

Well-known names assignemt and translation

Connections in a nested namespace must not know if a connection in a containing namespace has a well-known name, because it is not allowed to call its methods and then it would discover it has been restricted in a non-root namespace. On the contrary, a connection should know the well-known name of connections in a contained namespace and it must be aware that the name doesn't belong to a connection in the same namespace.

To enforce these rules, the bus should allow a connection to have a WKN already assigned in another namespace, but that name will be seen differently in other namespaces.

Consider still the previous example, and assume that :p-d has already received its WKN N.D. Then :p-d would believe that it has received the N.D name, however for :p-c the WKN of :p-d is nameD/N.D instead. Analougsly, :p-a would see nameC/nameD/N.D and :p-root then nameA/nameC/nameD/N.D (:p-b cannot see :p-d).

Now assume there is another connection :p-dpre inside namespace nameC that have already the WKN N.D before :p-d. Now if the Dbus daemon would deny the :p-d request for name N.D because it is already assigned then :p-d would discover that he is in a nested namespace. Instead, the Dbus daemon should grant :p-d the required name, which is N.D only in nameD namespace but is different in other namespaces. In particular, :p-dpre would see that the name nameD/N.D has been assigned to :p-d which is different from its WKN N.D.

If now :p-dpre wants to call a method in a object of nameD/N.D then it would send a method call message to the Dbus daemon from the address N.D to the address nameD/N.D. Dbus would see that nameD/N.D is the WKN of :p-d which namespace is nameD. Now since nameD is contained in the namespace of :p-dpre (nameC) Dbus would allow the call, but before delivering the message to :p-d it first perform name translation. In particular, it replaces the sending address from N.D into :p-dpre and the destination address from nameD/N.D into N.D. Then, :p-d would see a method call message sent by :p-dpre (which he believes to belong to its namespace nameD) to its WKN N.D. The reply messages and signal issued by :p-d are translated in the opposite way, with the sending address replaced from N.D into nameD/N.D if received by :p-dpre and :p-c, nameC/nameD/N.D if received by :p-a and so on.

A simple namespace interface

Here there is a simple namespace interface named org.namespace.Namespace0 implemented by the /org/freedesktop/Dbus/Namespace0/Root object at the org.freedesktop.Dbus well-known name.

Every contained namespace should be represented as a nested object of /org/freedesktop/Dbus/Namespace0/Root which still implements the org.namespace.Namespace0 interface. Moreover, connections in nested namespaces should be able to access the org.freedesktop.Dbus connection and its object. /org/freedesktop/Dbus.

The object /org/freedesktop/Dbus/Namespace0/Root is still visible to connections in nested namespaces, but it doesn't point to the relative /org/freedesktop/Dbus/Namespace0/Root in the containing namespace. Instead, the object /org/freedesktop/Dbus/Namespace0/Root accessed by connections in nameA namespace should point to the /org/freedesktop/Dbus/Namespace0/Root/nameA object in the containing namespace.

Here is a drafy of the org.namespace.Namespace0 interface.

<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<interface name="org.namespace.Namespace0">
	<method name="CreateNamespace">
		<arg name="name" type="s" />		
		<arg name="base" type="o" />
	</method>
	<method name="DeleteNamespaceRec">
		<!--
			Delete empty namespaces recursively.

			A namespace can be removed only if there are
			no connections associated to it
			and to all its contained namespaces
		-->
		<arg name="namespace" type="s" />
	</method>
	<method name="MoveConnection">
		<!-- 
			Moves a connection into a new namespace.

			Less useful than AssignFD, may be removed.
		-->
		<arg name="namespace" type="o" />
		<arg name="connection" type="s" />
	</method>
	<method name="CloneConnection">
		<!-- 
			Clone a connection WKN to make it fully
			visible in a different namespace.

			The owner of the connection name is not
			aware of the connection cloning, as 
			received method call from address is translated.

			Signals are emitted from all the cloned interfaces.

			More care should be put in sending method returns
			and errors. 
		-->
		<arg name="namespace" type="o" />
		<arg name="connection" type="s" />
	</method>
	<method name="AssignFD">
		<!-- 
			Assigns a file descriptor to a namespace,
			new connections coming from that socket
			are automatically assigned to the specified
			namespace.
		-->
		<arg name="namespace" type="o" />
		<arg name="fd" type="h" />
	</method>
	<signal name="NewNamespace">
		<arg name="namespace" type="o" />
	</signal>
	<signal name="RemNamespace">
		<arg name="namespace" type="o" />
	</signal>
</interface>

Remember that, for any namespace name, the WKNs org.freedesktop.Dbus and name/org.freedesktop.Dbus are different names associated to the same connection with its object mapped differently. Therefore, a same event could result in multiple signals issued by these different WKN (for example, the creation of a namespace nameB in nameA would result in two signal issued: one from /org/freedesktop/Dbus/RootNamespace1/nameA object at org.freedesktop.Dbus connection about the creation of namespace nameA/nameB and another from /org/freedesktop/Dbus/RootNamespace1 object in nameA/org.freedesktop.Dbus connection (seen as org.freedesktop.Dbus in namespace nameA) about the creation of namespace nameB.

Further improvments

  • To harden the bus a connection can send a message rensponse/error only to connections in containing namespaces that have sent a message call to it before. But this means the bus should keep track of all the message calls between different namespaces, which is not optimal also because a client can perform the same security checks.

dvdhrm avatar Nov 27 '25 13:11 dvdhrm