Issue
If we open a python interpreter normally and enter the following:
import dbus
bus = dbus.SessionBus()
bus.list_names()
We see all the services on the user's session dbus. Now suppose we wanted to do some root-only things in the same script to determine information to pass through dbus, so we run the interpreter with sudo python
and run the same thing, we only see a short list of items on the root user's session dbus, and attempting to connect to anything that was on the user dbus with get_object
produces a not found error accordingly.
So far I've tried inserting
import os
os.seteuid(int(os.environ['SUDO_UID']))
But this only makes SessionBus()
give a org.freedesktop.DBus.Error.NoReply
so this is probably nonsense. Is there a way to connect to a user's dbus service as a super user, with the python dbus bindings?
Solution
I have little knowledge about DBus, but that question got me curious.
TL;DR: Use dbus.bus.BusConnection
with the socket address for the target user and seteuid
for gaining access.
First question: What socket does DBus connect to for the session bus?
$ cat list_bus.py
import dbus
print(dbus.SessionBus().list_names())
$ strace -o list_bus.trace python3 list_bus.py
$ grep ^connect list_bus.trace
connect(3, {sa_family=AF_UNIX, sun_path="/run/user/1000/bus"}, 20) = 0
Maybe it relies on environment variables for this? Gotcha!
$ env|grep /run/user/1000/bus
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
Stracing the behaviour from the root account it appears that it does not know the address to connect to. Googling for the variable name got me to the D-Bus Specification, section "Well-known Message Bus Instances".
Second question: Can we connect directly to the socket without having the D-Bus library guess the right address? The dbus-python tutorial states:
For special purposes, you might use a non-default Bus, or a connection which isn’t a Bus at all, using some new API added in dbus-python 0.81.0.
Looking at the changelog, this appears to refer to these:
Bus has a superclass dbus.bus.BusConnection (a connection to a bus daemon, but without the shared-connection semantics or any deprecated API) for the benefit of those wanting to subclass bus daemon connections
Let's try this out:
$ python3
Python 3.9.2 (default, Feb 28 2021, 17:03:44)
>>> from dbus.bus import BusConnection
>>> len(BusConnection("unix:path=/run/user/1000/bus").list_names())
145
How about root access?
# python3
>>> from dbus.bus import BusConnection
>>> len(BusConnection("unix:path=/run/user/1000/bus").list_names())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3/dist-packages/dbus/bus.py", line 124, in __new__
bus = cls._new_for_bus(address_or_type, mainloop=mainloop)
dbus.exceptions.DBusException: org.freedesktop.DBus.Error.NoReply: Did not
receive a reply. Possible causes include: the remote application did not send
a reply, the message bus security policy blocked the reply, the reply timeout
expired, or the network connection was broken.
>>> import os
>>> os.seteuid(1000)
>>> len(BusConnection("unix:path=/run/user/1000/bus").list_names())
143
So this answers the question: Use BusConnection
instead of SessionBus
and specify the address explicitly, combined with seteuid
to gain access.
Bonus: Connect as root without seteuid
Still I'd like to know if it is possible
to access the bus directly as root user, without resorting to seteuid
. After
a few search queries, I found a systemd ticket
with this remark:
dbus-daemon is the component enforcing access ... (but you can drop an xml policy file in, to make it so).
This led me to an askubuntu question discussing how to modify the site local session bus policy.
Just to play with it, I ran this in one terminal:
$ cp /usr/share/dbus-1/session.conf session.conf
$ (edit session.conf to modify the include for local customization)
$ diff /usr/share/dbus-1/session.conf session.conf
50c50
< <include ignore_missing="yes">/etc/dbus-1/session-local.conf</include>
---
> <include ignore_missing="yes">session-local.conf</include>
$ cat > session-local.conf
<busconfig>
<policy context="mandatory">
<allow user="root"/>
</policy>
</busconfig>
$ dbus-daemon --config-file session.conf --print-address
unix:abstract=/tmp/dbus-j0r67hLIuh,guid=d100052e45d06f248242109262325b98
$ dbus-daemon --config-file session.conf --print-address
unix:abstract=/tmp/dbus-j0r67hLIuh,guid=d100052e45d06f248242109262325b98
In another terminal, I can not attach to this bus as a root user:
# python3
Python 3.9.2 (default, Feb 28 2021, 17:03:44)
>>> from dbus.bus import BusConnection
>>> address = "unix:abstract=/tmp/dbus-j0r67hLIuh,guid=d100052e45d06f248242109262325b98"
>>> BusConnection(address).list_names()
dbus.Array([dbus.String('org.freedesktop.DBus'), dbus.String(':1.0')], signature=dbus.Signature('s'))
This should also enable accessing all session busses on the system, when installing session-local.conf
globally:
# cp session-local.conf /etc/dbus-1/session-local.conf
# kill -HUP 1865 # reload config of my users session dbus-daemon
# python3
>>> from dbus.bus import BusConnection
>>> len(BusConnection("unix:path=/run/user/1000/bus").list_names())
143
And it works - now root can connect to any session bus without resorting to seteuid
. Don't forget to
# rm /etc/dbus-1/session-local.conf
if your root user does not need this power.
Answered By - Bluehorn Answer Checked By - Mary Flores (WPSolving Volunteer)