dbus-daemon, passing fd and SELinux

David Sommerseth dbus at lists.topphemmelig.net
Wed Nov 7 14:22:26 UTC 2018


I'm still working on the OpenVPN 3 Linux project, the brand new OpenVPN client
for Linux based on D-Bus.  And things are progressing well.  We've wanted to
reduce the privileges for each component as much as possible and we're now
splitting out the tun/tap device management, including IP address, route and
DNS configuration to a separate service.  When we reach our goal, this will
run as an openvpn user with CAP_NET_ADMIN and maybe a few other additional
capabilities.  This should be all needed to create TUN devices and configure
them and the related networking stuff.

We currently call this the netcfg service (but essentially, it is more like a
fairly generic VPN API for Linux, and it is being modelled after the Android
VPN API as inspiration).

The VPN client process is its own D-Bus service (one per VPN tunnel running),
which calls an Establish() D-Bus method on this netcfg service after the tun
configuration has been passed over to the service.  This Establish() method
will return a FD which is tied to the tun device created by the Linux kernel.
The VPN client process uses this FD to pass traffic to/from the tun device.

So essentially it reads network packets from this FD, encrypts them and sends
them to the tcp/udp socket which is used for the VPN server connection.  And
packets received on the tcp/udp socket is decrypted and written back to the FD
provided by the Establish() call in netcfg.

But ... on Fedora 28 and RHEL 7 (and presumably all other distros with
SELinux) this fails.

The raw SELinux denial is:
type=AVC msg=audit(1541594038.506:688):
 avc:  denied  { read write }
 for pid=4880 comm="dbus-daemon"
     path="/dev/net/tun" dev="devtmpfs" ino=2711

type=SYSCALL msg=audit(1541594038.506:688): arch=x86_64
     syscall=recvmsg success=yes exit=EBADRQC
     a0=63 a1=7ffc08dc7630 a2=40000000 a3=9
     items=0 ppid=1 pid=4880
     uid=81 gid=81 euid=81 suid=81 fsuid=81 egid=81 sgid=81 fsgid=81
     tty=(none) ses=4294967295
     comm=dbus-daemon exe=/usr/bin/dbus-daemon
     subj=system_u:system_r:system_dbusd_t:s0-s0:c0.c1023 key=(null)

As SELinux entries like this can be quite cryptic if you're not familiar with
SELinux, I'll decode it a bit.

So this is a _denial_ where SELinux denied the dbus-daemon to do read/write
operations on /dev/net/tun.  The source (dbus-daemon) runs under the
system_dbusd_t context (scontext) and it tries to target a chr_file (tclass)
labelled with the tun_ap_device_t context (tcontext).  So, this is the default
SELinux targeted policy on RHEL/Fedora.

The dbus-daemon tried to do a recvmsg() syscall which returned EBADRQC, which
I believe means "Invalid Request Code".  This is anyhow something the kernel
returned, so I presume it is correct behaviour.

This situation happens after the netcfg service calls
g_dbus_method_invocation_return_value_with_unix_fd_list() with a properly
populated GUnixFDList.

If I switch SELinux to permissive mode, everything works fine.  The same
denial is still logged, but it is allowed to complete by SELinux.

As far as I've understood, the dbus-daemon needs to do the recvmsg()/sendmsg()
dance on the FD to be able to pass the FD from the service to the caller.

So to my questions:

- When this error occurs (dbus-daemon being denied the recvmsg() call), the
  netcfg service hits the GBusNameLostCallback function and the service is
  being shutdown.  Why does this happen?  Is this expected?

- Is there a way to grab more info anywhere /why/ the GBusNameLostCallback
  event occurred?

- What would be the best approach to recover from such a situation?  Would
  it be feasible to try to call g_bus_own_name_on_connection() again to
  re-establish the service on the D-Bus?  What will happen to all the objects
  which was already registered on the D-Bus for this service?

kind regards,

David Sommerseth

More information about the dbus mailing list