Unable to connect to DBus session bus after dropping privileges from a setuid binary
simon.mcvittie at collabora.co.uk
Mon May 23 11:23:09 UTC 2016
On 19/05/16 10:17, Manish wrote:
> On Wed, 2016-05-18 at 18:52 +0100, Simon McVittie wrote:
>> Have you considered carrying out your root-required actions in a
>> separate process that is not the one that connects to the session
>> bus? A popular pattern for this is to have a service that runs on
>> the system bus (like NetworkManager, ConnMan, udisks, etc.) and have
>> ordinary user processes submit requests to it. If required, it can
>> use polkit (formerly PolicyKit) to determine whether the request
>> should be obeyed or rejected.
> I have considered that, but given my requirements it may not be
> feasible. It is still being discussed and not completely off the
Another way you can potentially do your root-required actions is to have
your main process that connects to the session bus, and then have a
single-purpose setuid helper that it runs (via posix_spawn() or
equivalent) when it needs to do a privileged action. For example,
spice-gtk has spice-client-glib-usb-acl-helper which works like this.
Again, please note that this is a privileged process, which needs to be
extremely careful (to the point of paranoia) to avoid being part of a
security vulnerability. For example, spice-client-glib-usb-acl-helper
had a root privilege escalation vulnerability because it used libdbus
without clearing the environment (CVE-2012-4425).
I would strongly recommend using the approach involving a system bus
service, like NetworkManager: it reduces your attack surface
considerably, making it less likely that you'll have to issue updates to
patch a privilege escalation vulnerability.
>> It might also help to read the human-readable message that should have
>> accompanied this machine-readable error name (in libdbus they're
>> DBusError.message and DBusError.name respectively).
> It's dbus_bus_get as mentioned above. Here's the error that I get.
> DBusError.name = org.freedesktop.DBus.Error.NotSupported
> DBusError.message = Unable to autolaunch when setuid
If you're seeing that, then libdbus is ignoring DBUS_SESSION_BUS_ADDRESS
and falling back to the hard-coded default "autolaunch:", then refusing
to do that either, in both cases because it thinks you're setuid.
This appears to be because you have used setuid() and not setresuid().
The check is a bit less simplistic than I had thought: if the system has
a getresuid() function, we assert that the real, effective *and saved*
uids are all the same, and also the equivalent for primary groups.
To be absolutely sure of what open source software is doing, don't
believe the maintainer, read the source code :-) In this case the
function to search for is _dbus_check_setuid(). You'll notice that we
also use issetuid() if available (that's a *BSD thing, Linux doesn't
have that function).
>> Note that libdbus deliberately does not trust the environment when it
>> detects that it was used in a setuid or similarly privileged binary, to
>> mitigate security vulnerabilities caused when setuid binaries do not
>> sanitize their (attacker-supplied) environment variables (CVE-2012-3524).
> I've already read about the exploit, and your statement "NOTE: libdbus
> maintainers state that this is a vulnerability in the applications that
> do not cleanse environment variables, not in libdbus itself: "we do not
> support use of libdbus in setuid binaries that do not sanitize their
> environment before their first call into libdbus."
> This is why I wrote to this mailing list, to try and understand what is
> meant by cleansing the environment variables?
What we mean by cleansing the environment is:
If you are writing setuid or otherwise security-sensitive software, it's
a very good idea to read that whole document. The central principle is
that you need to think in terms of the worst corner-cases that could
possibly happen, because that's what an attacker will set up in order to
exploit your software.
> If libdbus ignores environment variables in case of a setuid process
> then how would one provide the correct $DBUS_SESSION_BUS_ADDRESS
> variable to it?
One way is to stop using dbus_bus_get(), and instead get the session bus
address from a trusted source, then use dbus_connection_open_private()
followed by dbus_bus_register() (the combination of those two is
essentially equivalent to dbus_bus_get_private()). You are responsible
for the security of the code you use to find a trusted session bus
address and ensure that it really came from the expected source.
Another way is to extract the parts of the environment you need, filter
their values using a "whitelist" approach (reject anything not matching
a known-good pattern), clearenv(), put the good parts back, and drop
privileges irrevocably by using setresuid(). Again, you will have to
take responsibility for the code that scrubs the environment, and please
note that filtering out known-bad environment variables like
LD_LIBRARY_PATH, or known-bad values, is not enough. This is basically
what happens if you run something like "sudo -u otheruser dbus-monitor".
(Note that if we switch _dbus_check_setuid() to use _secure_getenv() on
GNU platforms, which we will probably do in a future release, your
process will still be flagged as running in a potentially insecure
execution context even after calling setresuid(), so _secure_getenv()
still won't trust the environment. You would have to exec() a non-setuid
subprocess instead, like what happens when sudo exec()s dbus-monitor in
my example above.)
In either case, bear in mind that using an attacker-chosen session bus
address can result in connecting to a socket that they control (and
potentially putting misguided trust in D-Bus services visible via that
socket), and can also result in arbitrary code execution (e.g. via the
unixexec: transport). So don't do that, and in general, be very careful.
Collabora Ltd. <http://www.collabora.com/>
More information about the dbus