From f5257fe37de4932ce6697155e02f79af8596270c Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Thu, 27 Jul 2023 13:13:47 +0200 Subject: [PATCH] [very wip] Add unprivileged wayland socket Follow-up from - #1003 This PR creates a second wayland listening socket ($XDG_RUNTIME_DIR/wayland-unpriv) when there is at least one protocol blocked in rc.xml. The blocked protocols are only blocked for the new socket, the usual wayland socket allows all protocols. The idea is to use the unprivileged socket for bind-mounts in sandboxes and similar. Trusted applications are running against the usual wayland socket and thus are able to use privileged protocols (e.g. layershell, screen recording, foreign-toplevel) whereas clients within a sandbox are prevented to use those protocols. Related: - #1002 This PR is very much work in progress. - [ ] Solve TODO / FIXUP comments: - [ ] Add close-on-exec fallbacks for the wayland socket - [ ] Add lockfile for the wayland socket - [ ] Add close-on-exec for client connections - [ ] docs --- include/server-unpriv.h | 11 +++ src/meson.build | 1 + src/server-unpriv.c | 169 ++++++++++++++++++++++++++++++++++++++++ src/server.c | 18 +++-- 4 files changed, 194 insertions(+), 5 deletions(-) create mode 100644 include/server-unpriv.h create mode 100644 src/server-unpriv.c diff --git a/include/server-unpriv.h b/include/server-unpriv.h new file mode 100644 index 00000000..d8c8eee0 --- /dev/null +++ b/include/server-unpriv.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef LABWC_SERVER_UNPRIV_H +#define LABWC_SERVER_UNPRIV_H + +struct server; +struct wl_client; + +void unpriv_socket_start(struct server *server); +bool is_unpriv_client(const struct wl_client *wl_client); + +#endif /* LABWC_UNPRIV_H */ diff --git a/src/meson.build b/src/meson.build index 391e9cdc..9c0f505c 100644 --- a/src/meson.build +++ b/src/meson.build @@ -19,6 +19,7 @@ labwc_sources = files( 'resistance.c', 'seat.c', 'server.c', + 'server-unpriv.c', 'session-lock.c', 'touch.c', 'theme.c', diff --git a/src/server-unpriv.c b/src/server-unpriv.c new file mode 100644 index 00000000..98549070 --- /dev/null +++ b/src/server-unpriv.c @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include +#include +#include +#include "common/list.h" +#include "common/mem.h" +#include "labwc.h" +#include "server-unpriv.h" + +static struct unpriv_socket { + char *path; + int listen_fd; + struct wl_event_source *event_source; + struct wl_listener on_display_destroy; + + struct wl_list clients; +} unpriv_socket; + +struct unpriv_client { + struct wl_client *wl_client; + struct wl_listener on_destroy; + + struct wl_list link; +}; + +static void +unpriv_socket_finish(void) +{ + if (unpriv_socket.listen_fd > 0) { + close(unpriv_socket.listen_fd); + unpriv_socket.listen_fd = 0; + } + if (unpriv_socket.event_source) { + wl_event_source_remove(unpriv_socket.event_source); + unpriv_socket.event_source = NULL; + } + if (unpriv_socket.path) { + unlink(unpriv_socket.path); + zfree(unpriv_socket.path); + } +} + +static void +on_unpriv_client_destroy(struct wl_listener *listener, void *data) +{ + struct unpriv_client *client = wl_container_of(listener, client, on_destroy); + wl_list_remove(&client->on_destroy.link); + wl_list_remove(&client->link); + free(client); + wlr_log(WLR_DEBUG, "unpriv client destroyed"); +} + +static int +on_unpriv_socket_connect(int fd, uint32_t mask, void *data) +{ + struct wl_display *display = data; + + if (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR)) { + wlr_log(WLR_DEBUG, "unpriv socket going down, cleaning up"); + unpriv_socket_finish(); + return 0; + } + + /* FIXME: cloexec / accept4 */ + if (mask & WL_EVENT_READABLE) { + int client_fd = accept(fd, NULL, NULL); + if (client_fd < 0) { + wlr_log(WLR_ERROR, "Failed to accept unpriv client"); + return 0; + }; + + struct unpriv_client *client = znew(*client); + client->wl_client = wl_client_create(display, client_fd); + if (!client->wl_client) { + wlr_log(WLR_ERROR, "Failed to create unpriv client"); + close(client_fd); + free(client); + return 0; + } + + wlr_log(WLR_DEBUG, "Accepted new unpriv client: %i", client_fd); + wl_list_append(&unpriv_socket.clients, &client->link); + client->on_destroy.notify = on_unpriv_client_destroy; + wl_client_add_destroy_listener(client->wl_client, &client->on_destroy); + } + return 0; +} + +static void +on_display_destroy(struct wl_listener *listener, void *data) +{ + wl_list_remove(&unpriv_socket.on_display_destroy.link); + unpriv_socket_finish(); +} + +void +unpriv_socket_start(struct server *server) +{ + wl_list_init(&unpriv_socket.clients); + + /* FIXME: lockfile, CLOEXEC fallback */ + + struct sockaddr_un addr = { .sun_family = AF_LOCAL }; + int addr_size = snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/%s", + getenv("XDG_RUNTIME_DIR"), "wayland-unpriv") + 1; + + if (addr_size > (int)sizeof(addr.sun_path)) { + addr.sun_path[sizeof(addr.sun_path) - 1] = '\0'; + wlr_log(WLR_ERROR, "Truncated unpriv socket path to %s", addr.sun_path); + } + + /* + * TODO: use close-on-exec fallback like + * https://gitlab.freedesktop.org/wayland/wayland/-/blob/main/src/wayland-os.c#L74 + */ + int fd = socket(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (fd < 0) { + wlr_log(WLR_ERROR, "Failed to create unpriv socket"); + return; + } + unpriv_socket.listen_fd = fd; + wlr_log(WLR_ERROR, "Got unpriv fd: %i", fd); + + /* TODO: why offsetof(addr, sun_path) + strlen(addr.sun_path) ? */ + if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + wlr_log(WLR_ERROR, "Failed to bind unpriv socket"); + goto error; + } + unpriv_socket.path = xstrdup(addr.sun_path); + + /* A Backlog of 16 seems more than enough for a unprivileged socket */ + if (listen(fd, 16) < 0) { + wlr_log(WLR_ERROR, "Failed to listen in unpriv socket"); + goto error; + } + + unpriv_socket.event_source = wl_event_loop_add_fd( + server->wl_event_loop, fd, WL_EVENT_READABLE, + on_unpriv_socket_connect, server->wl_display); + if (!unpriv_socket.event_source) { + wlr_log(WLR_ERROR, "Failed to add unpriv socket to loop"); + goto error; + } + + unpriv_socket.on_display_destroy.notify = on_display_destroy; + wl_display_add_destroy_listener(server->wl_display, &unpriv_socket.on_display_destroy); + + wlr_log(WLR_INFO, "Unprivileged socket opened at '%s'", addr.sun_path); + return; + +error: + unpriv_socket_finish(); +} + +bool +is_unpriv_client(const struct wl_client *wl_client) +{ + if (unpriv_socket.listen_fd <= 0) { + /* TODO: we could return true here to block the protocols for the main socket */ + return false; + } + struct unpriv_client *unpriv_client; + wl_list_for_each(unpriv_client, &unpriv_socket.clients, link) { + if (wl_client == unpriv_client->wl_client) { + return true; + } + } + return false; +} diff --git a/src/server.c b/src/server.c index 620c7fb9..63a7bf8c 100644 --- a/src/server.c +++ b/src/server.c @@ -24,6 +24,7 @@ #include "layers.h" #include "menu/menu.h" #include "regions.h" +#include "server-unpriv.h" #include "theme.h" #include "view.h" #include "workspaces.h" @@ -180,11 +181,14 @@ server_global_filter(const struct wl_client *client, const struct wl_global *glo } } #endif - struct blocked_protocol *proto; - wl_list_for_each(proto, &rc.blocked_protocols, link) { - if (!strcmp(iface->name, proto->interface_name)) { - wlr_log(WLR_INFO, "blocking protocol %s", proto->interface_name); - return false; + if (is_unpriv_client(client)) { + struct blocked_protocol *proto; + wl_list_for_each(proto, &rc.blocked_protocols, link) { + if (!strcmp(iface->name, proto->interface_name)) { + wlr_log(WLR_INFO, "blocking protocol %s", + proto->interface_name); + return false; + } } } @@ -485,6 +489,10 @@ server_start(struct server *server) } else { wlr_log(WLR_DEBUG, "WAYLAND_DISPLAY=%s", socket); } + + if (!wl_list_empty(&rc.blocked_protocols)) { + unpriv_socket_start(server); + } } void