From 6adf19fedaa51315f98737c600872a62f068b883 Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Wed, 26 Jul 2023 02:35:37 +0200 Subject: [PATCH 1/2] [wip] feature: Allow blocking wayland protocols Missing: - [ ] docs (including it only applying to new windows after --reconfigure) - [ ] discuss config naming and format --- include/config/rcxml.h | 6 ++++++ src/config/rcxml.c | 29 +++++++++++++++++++++++++++++ src/server.c | 8 ++++++++ 3 files changed, 43 insertions(+) diff --git a/include/config/rcxml.h b/include/config/rcxml.h index 88a2ee0f..8e14a250 100644 --- a/include/config/rcxml.h +++ b/include/config/rcxml.h @@ -31,6 +31,11 @@ struct window_switcher_field { struct wl_list link; /* struct rcxml.window_switcher.fields */ }; +struct blocked_protocol { + struct wl_list link; /* struct rcxml.blocked_protocols */ + char *interface_name; +}; + struct rcxml { char *config_dir; @@ -94,6 +99,7 @@ struct rcxml { } window_switcher; struct wl_list window_rules; /* struct window_rule.link */ + struct wl_list blocked_protocols; /* struct blocked_protocol.link */ }; extern struct rcxml rc; diff --git a/src/config/rcxml.c b/src/config/rcxml.c index f0af0b51..2bad6665 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -37,6 +37,7 @@ static bool in_mousebind; static bool in_libinput_category; static bool in_window_switcher_field; static bool in_window_rules; +static bool in_blocked_protocols; static struct usable_area_override *current_usable_area_override; static struct keybind *current_keybind; @@ -166,6 +167,16 @@ fill_window_rule(char *nodename, char *content) } } +static void +fill_blocked_protocols(char *nodename, char *content) +{ + if (!strcasecmp(nodename, "name.interface.blockedProtocols")) { + struct blocked_protocol *proto = znew(*proto); + proto->interface_name = xstrdup(content); + wl_list_append(&rc.blocked_protocols, &proto->link); + } +} + static void fill_window_switcher_field(char *nodename, char *content) { @@ -536,6 +547,10 @@ entry(xmlNode *node, char *nodename, char *content) fill_window_rule(nodename, content); return; } + if (in_blocked_protocols) { + fill_blocked_protocols(nodename, content); + return; + } /* handle nodes without content, e.g. */ if (!strcmp(nodename, "default.keyboard")) { @@ -736,6 +751,12 @@ xml_tree_walk(xmlNode *node) in_window_rules = false; continue; } + if (!strcasecmp((char *)n->name, "blockedProtocols")) { + in_blocked_protocols = true; + traverse(n); + in_blocked_protocols = false; + continue; + } traverse(n); } } @@ -776,6 +797,7 @@ rcxml_init(void) wl_list_init(&rc.regions); wl_list_init(&rc.window_switcher.fields); wl_list_init(&rc.window_rules); + wl_list_init(&rc.blocked_protocols); } has_run = true; @@ -1318,6 +1340,13 @@ rcxml_finish(void) rule_destroy(rule); } + struct blocked_protocol *proto, *proto_tmp; + wl_list_for_each_safe(proto, proto_tmp, &rc.blocked_protocols, link) { + wl_list_remove(&proto->link); + zfree(proto->interface_name); + zfree(proto); + } + /* Reset state vars for starting fresh when Reload is triggered */ current_usable_area_override = NULL; current_keybind = NULL; diff --git a/src/server.c b/src/server.c index c781f53d..620c7fb9 100644 --- a/src/server.c +++ b/src/server.c @@ -180,7 +180,15 @@ 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; + } + } + wlr_log(WLR_DEBUG, "protocol not blocked: %s", iface->name); return true; } 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 2/2] [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