From de5dddccbe81eba866226e95435ed7f90c7d1b98 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Wed, 21 Jul 2021 16:31:48 +0200 Subject: [PATCH] core: get file descriptor to ovsdb unix socket from nm-sudo To talk to ovsdb, we use the unix socket at /var/run/openvswitch/db.sock. But that socket is owned by another user and NetworkManager would need dac_override capability to open it. We want to drop dac_override, but we still need to talk to ovsdb. Add a GetFD() method to nm-sudo. We still first try to open the socket directly. Maybe it just works. Note that SELinux may block passing file descriptors from nm-sudo. If it doesn't work for you, test with SELinux permissive mode and wait for an SELinux update. --- Makefile.am | 2 + src/core/devices/ovs/nm-ovsdb.c | 101 +++++++++++++++++++++----------- src/core/meson.build | 5 +- src/core/nm-sudo-call.c | 94 +++++++++++++++++++++++++++++ src/core/nm-sudo-call.h | 9 +++ src/libnm-base/nm-sudo-utils.c | 53 +++++++++++++++++ src/libnm-base/nm-sudo-utils.h | 9 +++ src/nm-sudo/nm-sudo.c | 33 ++++++++++- 8 files changed, 271 insertions(+), 35 deletions(-) diff --git a/Makefile.am b/Makefile.am index 7a6f56199a..8f658b52c0 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2664,6 +2664,8 @@ $(src_core_libNetworkManagerTest_la_OBJECTS): $(src_libnm_core_public_mkenums_h) # itself, but by the plugins (that are dlopened). We need to explicitly include # them during linking. networkmanager_undefined_symbols = \ + nm_sudo_call_get_fd \ + nm_sudo_utils_open_fd \ $(NULL) noinst_PROGRAMS += src/core/NetworkManager-all-sym diff --git a/src/core/devices/ovs/nm-ovsdb.c b/src/core/devices/ovs/nm-ovsdb.c index 4f72e9fa9d..58455ca719 100644 --- a/src/core/devices/ovs/nm-ovsdb.c +++ b/src/core/devices/ovs/nm-ovsdb.c @@ -17,6 +17,7 @@ #include "devices/nm-device.h" #include "nm-manager.h" #include "nm-setting-ovs-external-ids.h" +#include "nm-sudo-call.h" /*****************************************************************************/ @@ -118,9 +119,8 @@ enum { static guint signals[LAST_SIGNAL] = {0}; typedef struct { - GSocketClient * client; GSocketConnection *conn; - GCancellable * cancellable; + GCancellable * conn_cancellable; char buf[4096]; /* Input buffer */ size_t bufp; /* Last decoded byte in the input buffer. */ GString * input; /* JSON stream waiting for decoding. */ @@ -2223,7 +2223,7 @@ ovsdb_disconnect(NMOvsdb *self, gboolean retry, gboolean is_disposing) nm_assert(!retry || !is_disposing); - if (!priv->client) + if (!priv->conn && !priv->conn_cancellable) return; _LOGD("disconnecting from ovsdb, retry %d", retry); @@ -2250,10 +2250,9 @@ ovsdb_disconnect(NMOvsdb *self, gboolean retry, gboolean is_disposing) priv->bufp = 0; g_string_truncate(priv->input, 0); g_string_truncate(priv->output, 0); - g_clear_object(&priv->client); g_clear_object(&priv->conn); nm_clear_g_free(&priv->db_uuid); - nm_clear_g_cancellable(&priv->cancellable); + nm_clear_g_cancellable(&priv->conn_cancellable); if (retry) ovsdb_try_connect(self); @@ -2348,32 +2347,77 @@ _monitor_bridges_cb(NMOvsdb *self, json_t *result, GError *error, gpointer user_ } static void -_client_connect_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) +_ovsdb_connect_complete_with_fd(NMOvsdb *self, int fd_take) { - GSocketClient * client = G_SOCKET_CLIENT(source_object); - NMOvsdb * self = NM_OVSDB(user_data); - NMOvsdbPrivate * priv; - GError * error = NULL; - GSocketConnection *conn; - - conn = g_socket_client_connect_finish(client, res, &error); - if (conn == NULL) { - if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) - _LOGI("%s", error->message); + NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); + gs_unref_object GSocket *socket = NULL; + gs_free_error GError *error = NULL; + socket = g_socket_new_from_fd(nm_steal_fd(&fd_take), &error); + if (!socket) { + _LOGT("connect: failure to open socket for new FD: %s", error->message); ovsdb_disconnect(self, FALSE, FALSE); - g_clear_error(&error); return; } - priv = NM_OVSDB_GET_PRIVATE(self); - priv->conn = conn; - g_clear_object(&priv->cancellable); + priv->conn = g_socket_connection_factory_create_connection(socket); + g_clear_object(&priv->conn_cancellable); ovsdb_read(self); ovsdb_next_command(self); } +static void +_ovsdb_connect_sudo_cb(int fd_take, GError *error, gpointer user_data) +{ + nm_auto_close int fd = fd_take; + NMOvsdb * self; + + if (nm_utils_error_is_cancelled(error)) + return; + + self = user_data; + + if (error) { + _LOGT("connect: failure to get FD from nm-sudo: %s", error->message); + ovsdb_disconnect(self, FALSE, FALSE); + return; + } + + _LOGT("connect: connected successfully with FD from nm-sudo"); + _ovsdb_connect_complete_with_fd(self, nm_steal_fd(&fd)); +} + +static void +_ovsdb_connect_idle(gpointer user_data, GCancellable *cancellable) +{ + NMOvsdb * self; + NMOvsdbPrivate * priv; + nm_auto_close int fd = -1; + gs_free_error GError *error = NULL; + + if (g_cancellable_is_cancelled(cancellable)) + return; + + self = user_data; + priv = NM_OVSDB_GET_PRIVATE(self); + + fd = nm_sudo_utils_open_fd(NM_SUDO_GET_FD_TYPE_OVSDB_SOCKET, &error); + if (fd < 0) { + _LOGT("connect: opening %s failed (\"%s\"). Retry with nm-sudo", + NM_OVSDB_SOCKET, + error->message); + nm_sudo_call_get_fd(NM_SUDO_GET_FD_TYPE_OVSDB_SOCKET, + priv->conn_cancellable, + _ovsdb_connect_sudo_cb, + self); + return; + } + + _LOGT("connect: opening %s succeeded", NM_OVSDB_SOCKET); + _ovsdb_connect_complete_with_fd(self, nm_steal_fd(&fd)); +} + /** * ovsdb_try_connect: * @@ -2385,22 +2429,13 @@ static void ovsdb_try_connect(NMOvsdb *self) { NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); - GSocketAddress *addr; - if (priv->client) + if (priv->conn || priv->conn_cancellable) return; - /* TODO: This should probably be made configurable via NetworkManager.conf */ - addr = g_unix_socket_address_new(RUNSTATEDIR "/openvswitch/db.sock"); - - priv->client = g_socket_client_new(); - priv->cancellable = g_cancellable_new(); - g_socket_client_connect_async(priv->client, - G_SOCKET_CONNECTABLE(addr), - priv->cancellable, - _client_connect_cb, - self); - g_object_unref(addr); + _LOGT("connect: start connecting socket %s on idle", NM_OVSDB_SOCKET); + priv->conn_cancellable = g_cancellable_new(); + nm_utils_invoke_on_idle(priv->conn_cancellable, _ovsdb_connect_idle, self); /* Queue a monitor call before any other command, ensuring that we have an up * to date view of existing bridged that we need for add and remove ops. */ diff --git a/src/core/meson.build b/src/core/meson.build index fc94def205..f935ed1606 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -275,7 +275,10 @@ subdir('settings/plugins') # itself, but by the plugins (that are dlopened). We need to explicitly include # them during linking. networkmanager_undefined_symbols_args = [] -foreach s: [] +foreach s: [ + 'nm_sudo_call_get_fd', + 'nm_sudo_utils_open_fd', + ] networkmanager_undefined_symbols_args += ['-u', s] endforeach diff --git a/src/core/nm-sudo-call.c b/src/core/nm-sudo-call.c index deeab52cb2..6f9ca4f138 100644 --- a/src/core/nm-sudo-call.c +++ b/src/core/nm-sudo-call.c @@ -3,3 +3,97 @@ #include "src/core/nm-default-daemon.h" #include "nm-sudo-call.h" + +#include + +#include "nm-dbus-manager.h" + +/*****************************************************************************/ + +static void +_nm_sudo_call_get_fd_cb(GObject *source, GAsyncResult *res, gpointer user_data) +{ + NMSudoCallGetFDCallback callback; + gpointer callback_data; + gs_unref_variant GVariant *ret = NULL; + gs_free_error GError *error = NULL; + gs_unref_object GUnixFDList *fd_list = NULL; + gs_free int * fd_arr = NULL; + + nm_utils_user_data_unpack(user_data, &callback, &callback_data); + + ret = g_dbus_connection_call_with_unix_fd_list_finish(G_DBUS_CONNECTION(source), + &fd_list, + res, + &error); + + if (error) { + callback(-1, error, callback_data); + return; + } + + if (!fd_list || g_unix_fd_list_get_length(fd_list) != 1) { + nm_utils_error_set(&error, + NM_UTILS_ERROR_UNKNOWN, + "Unexpectedly not one FD is returned by nm-sudo GetFD()"); + callback(-1, error, callback_data); + return; + } + + fd_arr = g_unix_fd_list_steal_fds(fd_list, NULL); + + /* we transfer ownership of the file descriptor! */ + callback(fd_arr[0], NULL, callback_data); +} + +static gboolean +_nm_sudo_call_get_fd_fail_on_idle(gpointer user_data) +{ + gs_unref_object GCancellable *cancellable = NULL; + NMSudoCallGetFDCallback callback; + gpointer callback_data; + gs_free_error GError *error = NULL; + + nm_utils_user_data_unpack(user_data, &cancellable, &callback, &callback_data); + + if (!g_cancellable_set_error_if_cancelled(cancellable, &error)) + nm_utils_error_set(&error, NM_UTILS_ERROR_UNKNOWN, "Cannot talk to nm-sudo without D-Bus"); + + callback(-1, error, callback_data); + return G_SOURCE_REMOVE; +} + +void +nm_sudo_call_get_fd(NMSudoGetFDType fd_type, + GCancellable * cancellable, + NMSudoCallGetFDCallback callback, + gpointer user_data) +{ + GDBusConnection *dbus_connection; + + nm_assert(NM_IN_SET(fd_type, NM_SUDO_GET_FD_TYPE_OVSDB_SOCKET)); + nm_assert(!cancellable || G_IS_CANCELLABLE(cancellable)); + nm_assert(callback); + + dbus_connection = NM_MAIN_DBUS_CONNECTION_GET; + + if (!dbus_connection) { + nm_g_idle_add(_nm_sudo_call_get_fd_fail_on_idle, + nm_utils_user_data_pack(g_object_ref(cancellable), callback, user_data)); + return; + } + + g_dbus_connection_call_with_unix_fd_list(dbus_connection, + NM_SUDO_DBUS_BUS_NAME, + NM_SUDO_DBUS_OBJECT_PATH, + NM_SUDO_DBUS_IFACE_NAME, + "GetFD", + g_variant_new("(u)", fd_type), + G_VARIANT_TYPE("()"), + G_DBUS_CALL_FLAGS_NONE, + 10000, + NULL, + cancellable, + _nm_sudo_call_get_fd_cb, + nm_utils_user_data_pack(callback, user_data)); +} diff --git a/src/core/nm-sudo-call.h b/src/core/nm-sudo-call.h index 74b0a28cbb..741ae19b5b 100644 --- a/src/core/nm-sudo-call.h +++ b/src/core/nm-sudo-call.h @@ -3,4 +3,13 @@ #ifndef __NM_SUDO_CALL_H__ #define __NM_SUDO_CALL_H__ +#include "libnm-base/nm-sudo-utils.h" + +typedef void (*NMSudoCallGetFDCallback)(int fd_take, GError *error, gpointer user_data); + +void nm_sudo_call_get_fd(NMSudoGetFDType fd_type, + GCancellable * cancellable, + NMSudoCallGetFDCallback callback, + gpointer user_data); + #endif /* __NM_SUDO_CALL_H__ */ diff --git a/src/libnm-base/nm-sudo-utils.c b/src/libnm-base/nm-sudo-utils.c index 6ae1e78cd7..18bd0051bd 100644 --- a/src/libnm-base/nm-sudo-utils.c +++ b/src/libnm-base/nm-sudo-utils.c @@ -3,3 +3,56 @@ #include "libnm-glib-aux/nm-default-glib-i18n-lib.h" #include "nm-sudo-utils.h" + +#include +#include + +/*****************************************************************************/ + +int +nm_sudo_utils_open_fd(NMSudoGetFDType fd_type, GError **error) +{ + nm_auto_close int fd = -1; + int r; + int errsv; + + switch (fd_type) { + case NM_SUDO_GET_FD_TYPE_OVSDB_SOCKET: + { + struct sockaddr_un sock; + + memset(&sock, 0, sizeof(sock)); + sock.sun_family = AF_UNIX; + G_STATIC_ASSERT_EXPR(NM_STRLEN(NM_OVSDB_SOCKET) + 1 < sizeof(sock.sun_path)); + if (g_strlcpy(sock.sun_path, NM_OVSDB_SOCKET, sizeof(sock.sun_path)) + >= sizeof(sock.sun_path)) + nm_assert_not_reached(); + + fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (fd < 0) { + errsv = NM_ERRNO_NATIVE(errno); + g_set_error(error, G_IO_ERROR, g_io_error_from_errno(errsv), "error creating socket"); + return -errsv; + } + + r = connect(fd, + (const struct sockaddr *) &sock, + G_STRUCT_OFFSET(struct sockaddr_un, sun_path) + NM_STRLEN(NM_OVSDB_SOCKET) + 1); + if (r != 0) { + errsv = NM_ERRNO_NATIVE(errno); + g_set_error(error, + G_IO_ERROR, + g_io_error_from_errno(errsv), + "error connecting socket (%s)", + nm_strerror_native(errsv)); + return -errsv; + } + + return nm_steal_fd(&fd); + } + case NM_SUDO_GET_FD_TYPE_NONE: + default: + nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, "invalid fd_type"); + return -EINVAL; + } +} diff --git a/src/libnm-base/nm-sudo-utils.h b/src/libnm-base/nm-sudo-utils.h index cffea48dbc..01597fe467 100644 --- a/src/libnm-base/nm-sudo-utils.h +++ b/src/libnm-base/nm-sudo-utils.h @@ -11,4 +11,13 @@ /*****************************************************************************/ +#define NM_OVSDB_SOCKET RUNSTATEDIR "/openvswitch/db.sock" + +typedef enum { + NM_SUDO_GET_FD_TYPE_NONE = 0, + NM_SUDO_GET_FD_TYPE_OVSDB_SOCKET = 1, +} NMSudoGetFDType; + +int nm_sudo_utils_open_fd(NMSudoGetFDType fd_type, GError **error); + #endif /* __NM_SUDO_UTILS_H__ */ diff --git a/src/nm-sudo/nm-sudo.c b/src/nm-sudo/nm-sudo.c index 5be29d5a96..7dd22b1c69 100644 --- a/src/nm-sudo/nm-sudo.c +++ b/src/nm-sudo/nm-sudo.c @@ -2,6 +2,8 @@ #include "libnm-glib-aux/nm-default-glib-i18n-prog.h" +#include + #include "c-list/src/c-list.h" #include "libnm-glib-aux/nm-logging-base.h" #include "libnm-glib-aux/nm-shared-utils.h" @@ -98,6 +100,28 @@ _handle_ping(GlobalData *gl, GDBusMethodInvocation *invocation, const char *arg) g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", msg)); } +static void +_handle_get_fd(GlobalData *gl, GDBusMethodInvocation *invocation, guint32 fd_type) +{ + nm_auto_close int fd = -1; + gs_unref_object GUnixFDList *fd_list = NULL; + gs_free_error GError *error = NULL; + + if (fd_type != (NMSudoGetFDType) fd_type) + fd_type = NM_SUDO_GET_FD_TYPE_NONE; + + fd = nm_sudo_utils_open_fd(fd_type, &error); + if (fd < 0) { + g_dbus_method_invocation_take_error(invocation, g_steal_pointer(&error)); + return; + } + + fd_list = g_unix_fd_list_new_from_array(&fd, 1); + nm_steal_fd(&fd); + + g_dbus_method_invocation_return_value_with_unix_fd_list(invocation, NULL, fd_list); +} + /*****************************************************************************/ static gboolean @@ -260,6 +284,7 @@ _bus_method_call(GDBusConnection * connection, { GlobalData *gl = user_data; const char *arg_s; + guint32 arg_u; nm_assert(nm_streq(object_path, NM_SUDO_DBUS_OBJECT_PATH)); nm_assert(nm_streq(interface_name, NM_SUDO_DBUS_IFACE_NAME)); @@ -286,6 +311,9 @@ _bus_method_call(GDBusConnection * connection, if (nm_streq(method_name, "Ping")) { g_variant_get(parameters, "(&s)", &arg_s); _handle_ping(gl, invocation, arg_s); + } else if (nm_streq(method_name, "GetFD")) { + g_variant_get(parameters, "(u)", &arg_u); + _handle_get_fd(gl, invocation, arg_u); } else nm_assert_not_reached(); } @@ -296,7 +324,10 @@ static GDBusInterfaceInfo *const interface_info = NM_DEFINE_GDBUS_INTERFACE_INFO NM_DEFINE_GDBUS_METHOD_INFO( "Ping", .in_args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("arg", "s"), ), - .out_args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("arg", "s"), ), ), ), ); + .out_args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("arg", "s"), ), ), + NM_DEFINE_GDBUS_METHOD_INFO("GetFD", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("fd_type", "u"), ), ), ), ); typedef struct { GlobalData *gl;