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.
This commit is contained in:
Thomas Haller 2021-07-21 16:31:48 +02:00
parent f137b32d31
commit de5dddccbe
No known key found for this signature in database
GPG key ID: 29C2366E4DFC5728
8 changed files with 271 additions and 35 deletions

View file

@ -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

View file

@ -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. */

View file

@ -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

View file

@ -3,3 +3,97 @@
#include "src/core/nm-default-daemon.h"
#include "nm-sudo-call.h"
#include <gio/gunixfdlist.h>
#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));
}

View file

@ -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__ */

View file

@ -3,3 +3,56 @@
#include "libnm-glib-aux/nm-default-glib-i18n-lib.h"
#include "nm-sudo-utils.h"
#include <sys/socket.h>
#include <sys/un.h>
/*****************************************************************************/
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;
}
}

View file

@ -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__ */

View file

@ -2,6 +2,8 @@
#include "libnm-glib-aux/nm-default-glib-i18n-prog.h"
#include <gio/gunixfdlist.h>
#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;