This commit is contained in:
Tobias Bengfort 2026-03-16 21:34:43 +00:00 committed by GitHub
commit 985f2c4d70
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 388 additions and 8 deletions

View file

@ -18,6 +18,7 @@ struct action {
*/
uint32_t type; /* enum action_type */
uint32_t permissions; /* enum lab_permission */
struct wl_list args; /* struct action_arg.link */
};

View file

@ -2,8 +2,15 @@
#ifndef LABWC_SPAWN_H
#define LABWC_SPAWN_H
#include <stdbool.h>
#include <sys/types.h>
/**
* set_cloexec - set file descriptor to close on exit
* @fd: file descriptor
*/
bool set_cloexec(int fd);
/**
* spawn_primary_client - execute asynchronously
* @command: command to be executed
@ -14,7 +21,7 @@ pid_t spawn_primary_client(const char *command);
* spawn_async_no_shell - execute asynchronously
* @command: command to be executed
*/
void spawn_async_no_shell(char const *command);
void spawn_async_no_shell(char const *command, int socket_fd);
/**
* spawn_piped - execute asynchronously

View file

@ -76,6 +76,7 @@ struct rcxml {
enum tearing_mode allow_tearing;
bool auto_enable_outputs;
bool reuse_output_mode;
uint32_t default_permissions;
bool xwayland_persistence;
bool primary_selection;
char *prompt_command;
@ -199,6 +200,8 @@ struct rcxml {
struct wl_list window_rules; /* struct window_rule.link */
struct wl_list autostart; /* struct action.link */
/* Menu */
unsigned int menu_ignore_button_release_period;
bool menu_show_icons;

View file

@ -299,6 +299,7 @@ struct server {
struct wlr_tablet_manager_v2 *tablet_manager;
struct wlr_security_context_manager_v1 *security_context_manager_v1;
struct xi_socket_manager_v1 *xi_socket_manager_v1;
/* Set when in cycle (alt-tab) mode */
struct cycle_state cycle;

13
include/permissions.h Normal file
View file

@ -0,0 +1,13 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef LABWC_PERMISSIONS_H
#define LABWC_PERMISSIONS_H
#include <wayland-server-core.h>
#include <stdbool.h>
uint32_t permissions_from_interface_name(const char *s);
int permissions_context_create(struct wl_display *display, uint32_t permissions);
bool permissions_context_clone(struct wl_client *client, int fd);
bool permissions_check(const struct wl_client *client, const struct wl_interface *iface);
#endif

View file

@ -0,0 +1,21 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef LABWC_PROTOCOLS_XI_SOCKET_MANAGER_H
#define LABWC_PROTOCOLS_XI_SOCKET_MANAGER_H
#include <wayland-server-core.h>
struct xi_socket_manager_v1 {
struct wl_global *global;
struct {
struct wl_signal destroy;
struct wl_signal register_socket;
} events;
struct wl_listener display_destroy;
};
struct xi_socket_manager_v1 *xi_socket_manager_v1_create(
struct wl_display *display);
#endif

View file

@ -28,6 +28,7 @@ server_protocols = [
'cosmic-workspace-unstable-v1.xml',
'wlr-layer-shell-unstable-v1.xml',
'wlr-output-power-management-unstable-v1.xml',
'xi-socket-manager-unstable-v1.xml',
]
server_protos_src = []

View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="xi_socket_manager_unstable_v1">
<copyright>
Copyright © 2026 Tobias Bengfort
Permission to use, copy, modify, distribute, and sell this
software and its documentation for any purpose is hereby granted
without fee, provided that the above copyright notice appear in
all copies and that both that copyright notice and this permission
notice appear in supporting documentation, and that the name of
the copyright holders not be used in advertising or publicity
pertaining to distribution of the software without specific,
written prior permission. The copyright holders make no
representations about the suitability of this software for any
purpose. It is provided "as is" without express or implied
warranty.
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
</copyright>
<interface name="xi_socket_manager_v1" version="1">
<description summary="register privileged sockets">
If a client has received a privileged socket via WAYLAND_SOCKET, it can
use this interface to register additional privileged sockets to pass to
child processes.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the manager object">
Destroy the manager. This doesn't destroy objects created with the
manager.
</description>
</request>
<request name="register_socket">
<description summary="register a socket with the same permissions as the current one">
Registers a new socket with the same permissions as the current one.
The socket must be created by the client. After registraton, it can then
be passed to a child process via WAYLAND_SOCKET.
</description>
<arg name="fd" type="fd"/>
</request>
</interface>
</protocol>

View file

@ -26,6 +26,7 @@
#include "menu/menu.h"
#include "output.h"
#include "output-virtual.h"
#include "permissions.h"
#include "regions.h"
#include "ssd.h"
#include "theme.h"
@ -337,6 +338,9 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char
if (!strcmp(argument, "command") || !strcmp(argument, "execute")) {
action_arg_add_str(action, "command", content);
goto cleanup;
} else if (!strcmp(argument, "permission")) {
action->permissions |= permissions_from_interface_name(content);
goto cleanup;
}
break;
case ACTION_TYPE_MOVE_TO_EDGE:
@ -605,6 +609,7 @@ action_create(const char *action_name)
struct action *action = znew(*action);
action->type = action_type;
action->permissions = 0;
wl_list_init(&action->args);
return action;
}
@ -1117,7 +1122,8 @@ run_action(struct view *view, struct server *server, struct action *action,
struct buf cmd = BUF_INIT;
buf_add(&cmd, action_get_str(action, "command", NULL));
buf_expand_tilde(&cmd);
spawn_async_no_shell(cmd.data);
int socket_fd = permissions_context_create(server->wl_display, action->permissions);
spawn_async_no_shell(cmd.data, socket_fd);
buf_reset(&cmd);
break;
}

View file

@ -24,7 +24,7 @@ reset_signals_and_limits(void)
signal(SIGPIPE, SIG_DFL);
}
static bool
bool
set_cloexec(int fd)
{
int flags = fcntl(fd, F_GETFD);
@ -43,10 +43,11 @@ set_cloexec(int fd)
}
void
spawn_async_no_shell(char const *command)
spawn_async_no_shell(char const *command, int socket_fd)
{
GError *err = NULL;
gchar **argv = NULL;
char socket_str[32];
assert(command);
@ -73,6 +74,12 @@ spawn_async_no_shell(char const *command)
reset_signals_and_limits();
setsid();
if (socket_fd != -1) {
snprintf(socket_str, sizeof(socket_str), "%d", socket_fd);
if (setenv("WAYLAND_SOCKET", socket_str, 1) != 0) {
wlr_log(WLR_ERROR, "unable to setenv() WAYLAND_SOCKET");
}
}
grandchild = fork();
if (grandchild == 0) {
execvp(argv[0], argv);
@ -84,6 +91,9 @@ spawn_async_no_shell(char const *command)
default:
break;
}
if (socket_fd != -1) {
close(socket_fd);
}
waitpid(child, NULL, 0);
out:
g_strfreev(argv);

View file

@ -31,6 +31,7 @@
#include "config/touch.h"
#include "cycle.h"
#include "labwc.h"
#include "permissions.h"
#include "regions.h"
#include "ssd.h"
#include "translate.h"
@ -1046,6 +1047,8 @@ entry(xmlNode *node, char *nodename, char *content)
/* handle nested nodes */
if (!strcasecmp(nodename, "margin")) {
fill_usable_area_override(node);
} else if (!strcasecmp(nodename, "autostart")) {
append_parsed_actions(node, &rc.autostart);
} else if (!strcasecmp(nodename, "keybind.keyboard")) {
fill_keybind(node);
} else if (!strcasecmp(nodename, "context.mouse")) {
@ -1100,6 +1103,8 @@ entry(xmlNode *node, char *nodename, char *content)
set_bool(content, &rc.auto_enable_outputs);
} else if (!strcasecmp(nodename, "reuseOutputMode.core")) {
set_bool(content, &rc.reuse_output_mode);
} else if (!strcasecmp(nodename, "permission.core")) {
rc.default_permissions |= permissions_from_interface_name(content);
} else if (!strcasecmp(nodename, "xwaylandPersistence.core")) {
set_bool(content, &rc.xwayland_persistence);
} else if (!strcasecmp(nodename, "primarySelection.core")) {
@ -1424,6 +1429,7 @@ rcxml_init(void)
if (!has_run) {
wl_list_init(&rc.usable_area_overrides);
wl_list_init(&rc.autostart);
wl_list_init(&rc.keybinds);
wl_list_init(&rc.mousebinds);
wl_list_init(&rc.libinput_categories);
@ -1453,6 +1459,7 @@ rcxml_init(void)
rc.allow_tearing = LAB_TEARING_DISABLED;
rc.auto_enable_outputs = true;
rc.reuse_output_mode = false;
rc.default_permissions = 0;
rc.xwayland_persistence = false;
rc.primary_selection = true;

View file

@ -11,6 +11,7 @@
#include <wlr/backend/drm.h>
#include <wlr/backend/multi.h>
#include <wlr/util/log.h>
#include "action.h"
#include "common/buf.h"
#include "common/dir.h"
#include "common/file-helpers.h"
@ -232,12 +233,12 @@ update_activation_env(struct server *server, bool initialize)
char *cmd =
strdup_printf("dbus-update-activation-environment %s",
initialize ? env_keys : env_unset_keys);
spawn_async_no_shell(cmd);
spawn_async_no_shell(cmd, -1);
free(cmd);
cmd = strdup_printf("systemctl --user %s %s",
initialize ? "import-environment" : "unset-environment", env_keys);
spawn_async_no_shell(cmd);
spawn_async_no_shell(cmd, -1);
free(cmd);
free(env_keys);
@ -286,6 +287,13 @@ session_environment_init(void)
paths_destroy(&paths);
}
static void
session_run_rc_autostart(struct server *server)
{
wlr_log(WLR_INFO, "run autostart actions");
actions_run(NULL, server, &rc.autostart, NULL);
}
void
session_run_script(const char *script)
{
@ -303,7 +311,7 @@ session_run_script(const char *script)
}
wlr_log(WLR_INFO, "run session script %s", path->string);
char *cmd = strdup_printf("sh %s", path->string);
spawn_async_no_shell(cmd);
spawn_async_no_shell(cmd, -1);
free(cmd);
if (!should_merge_config) {
@ -318,6 +326,7 @@ session_autostart_init(struct server *server)
{
/* Update dbus and systemd user environment, each may fail gracefully */
update_activation_env(server, /* initialize */ true);
session_run_rc_autostart(server);
session_run_script("autostart");
}

View file

@ -145,7 +145,7 @@ idle_callback(void *data)
session_autostart_init(ctx->server);
if (ctx->startup_cmd) {
spawn_async_no_shell(ctx->startup_cmd);
spawn_async_no_shell(ctx->startup_cmd, -1);
}
}

View file

@ -1,6 +1,7 @@
labwc_sources = files(
'action.c',
'buffer.c',
'permissions.c',
'debug.c',
'desktop.c',
'dnd.c',

160
src/permissions.c Normal file
View file

@ -0,0 +1,160 @@
// SPDX-License-Identifier: GPL-2.0-only
#define _POSIX_C_SOURCE 200809L
#include "permissions.h"
#include <glib.h>
#include <sys/socket.h>
#include <unistd.h>
#include <wlr/util/log.h>
#include "common/mem.h"
#include "common/spawn.h"
#include "config/rcxml.h"
struct permissions_context {
uint32_t permissions;
int fd;
struct wl_listener destroy;
};
enum lab_permission {
LAB_PERM_DRM_LEASE = 1 << 0,
LAB_PERM_GAMMA = 1 << 1,
LAB_PERM_OUTPUT = 1 << 2,
LAB_PERM_OUTPUT_POWER = 1 << 3,
LAB_PERM_INPUT_METHOD = 1 << 4,
LAB_PERM_VIRTUAL_POINTER = 1 << 5,
LAB_PERM_VIRTUAL_KEYBOARD = 1 << 6,
LAB_PERM_DMABUF = 1 << 7,
LAB_PERM_SCREENCOPY = 1 << 8,
LAB_PERM_DATA_CONTROL = 1 << 9,
LAB_PERM_IDLE_NOTIFIER = 1 << 10,
LAB_PERM_WORKSPACE = 1 << 11,
LAB_PERM_FOREIGN_TOPLEVEL = 1 << 12,
LAB_PERM_SESSION_LOCK = 1 << 13,
LAB_PERM_LAYER_SHELL = 1 << 14,
};
static void
permissions_context_handle_destroy(struct wl_listener *listener, void *data)
{
struct permissions_context *context = wl_container_of(listener, context, destroy);
close(context->fd);
zfree(context);
}
static uint32_t
permissions_get(const struct wl_client *client)
{
struct wl_listener *listener = wl_client_get_destroy_listener(
(struct wl_client *)client, permissions_context_handle_destroy);
if (!listener) {
return 0;
}
struct permissions_context *context = wl_container_of(listener, context, destroy);
return context->permissions;
}
static bool
context_create(struct wl_display *display, uint32_t permissions, int fd)
{
struct wl_client *client = wl_client_create(display, fd);
if (!client) {
wlr_log(WLR_ERROR, "failed to create client");
return false;
}
struct permissions_context *context = znew(*context);
context->permissions = permissions;
context->fd = fd;
context->destroy.notify = permissions_context_handle_destroy;
wl_client_add_destroy_listener(client, &context->destroy);
return true;
}
uint32_t
permissions_from_interface_name(const char *name)
{
if (!strcmp(name, "wp_drm_lease_device_v1")) {
return LAB_PERM_DRM_LEASE;
} else if (!strcmp(name, "zwlr_gamma_control_manager_v1")) {
return LAB_PERM_GAMMA;
} else if (!strcmp(name, "zwlr_output_manager_v1")) {
return LAB_PERM_OUTPUT;
} else if (!strcmp(name, "zwlr_output_power_manager_v1")) {
return LAB_PERM_OUTPUT_POWER;
} else if (!strcmp(name, "zwp_input_method_manager_v2")) {
return LAB_PERM_INPUT_METHOD;
} else if (!strcmp(name, "zwlr_virtual_pointer_manager_v1")) {
return LAB_PERM_VIRTUAL_POINTER;
} else if (!strcmp(name, "zwp_virtual_keyboard_manager_v1")) {
return LAB_PERM_VIRTUAL_KEYBOARD;
} else if (!strcmp(name, "zwlr_export_dmabuf_manager_v1")) {
return LAB_PERM_DMABUF;
} else if (!strcmp(name, "zwlr_screencopy_manager_v1")) {
return LAB_PERM_SCREENCOPY;
} else if (!strcmp(name, "zwlr_data_control_manager_v1")) {
return LAB_PERM_DATA_CONTROL;
} else if (!strcmp(name, "ext_idle_notifier_v1")) {
return LAB_PERM_IDLE_NOTIFIER;
} else if (!strcmp(name, "zcosmic_workspace_manager_v1")) {
return LAB_PERM_WORKSPACE;
} else if (!strcmp(name, "zwlr_foreign_toplevel_manager_v1")) {
return LAB_PERM_FOREIGN_TOPLEVEL;
} else if (!strcmp(name, "ext_foreign_toplevel_list_v1")) {
return LAB_PERM_FOREIGN_TOPLEVEL;
} else if (!strcmp(name, "ext_session_lock_manager_v1")) {
return LAB_PERM_SESSION_LOCK;
} else if (!strcmp(name, "zwlr_layer_shell_v1")) {
return LAB_PERM_LAYER_SHELL;
} else {
return 0;
}
}
int
permissions_context_create(struct wl_display *display, uint32_t permissions)
{
if (permissions == 0) {
return -1;
}
int sv[2];
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) != 0) {
wlr_log_errno(WLR_ERROR, "socketpair failed");
return -1;
}
if (!set_cloexec(sv[0])) {
close(sv[0]);
close(sv[1]);
return -1;
}
if (!context_create(display, permissions, sv[0])) {
close(sv[0]);
close(sv[1]);
return -1;
}
return sv[1];
}
bool
permissions_context_clone(struct wl_client *client, int fd)
{
struct wl_display *display = wl_client_get_display(client);
uint32_t permissions = permissions_get(client);
if (permissions == 0) {
return false;
}
return context_create(display, permissions, fd);
}
bool
permissions_check(const struct wl_client *client, const struct wl_interface *iface)
{
uint32_t permissions = permissions_get(client) | rc.default_permissions;
uint32_t required = permissions_from_interface_name(iface->name);
return (permissions & required) == required;
}

View file

@ -4,3 +4,4 @@ labwc_sources += files(
subdir('cosmic_workspaces')
subdir('ext-workspace')
subdir('xi-socket-manager')

View file

@ -0,0 +1,3 @@
labwc_sources += files(
'xi_socket_manager_v1.c',
)

View file

@ -0,0 +1,76 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <stdlib.h>
#include <protocols/xi_socket_manager_v1.h>
#include "permissions.h"
#include "xi-socket-manager-unstable-v1-protocol.h"
#define XI_SOCKET_MANAGER_V1_VERSION 1
static void
manager_handle_destroy(struct wl_client *client, struct wl_resource *resource)
{
wl_resource_destroy(resource);
}
static void
manager_handle_register_socket(struct wl_client *client,
struct wl_resource *manager_resource, int fd)
{
permissions_context_clone(client, fd);
}
static const struct xi_socket_manager_v1_interface manager_impl = {
.destroy = manager_handle_destroy,
.register_socket = manager_handle_register_socket,
};
static void
manager_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id)
{
struct xi_socket_manager_v1 *manager = data;
struct wl_resource *resource =
wl_resource_create(client, &xi_socket_manager_v1_interface, version, id);
if (!resource) {
wl_client_post_no_memory(client);
return;
}
wl_resource_set_implementation(resource, &manager_impl, manager, NULL);
}
static void
handle_display_destroy(struct wl_listener *listener, void *data)
{
struct xi_socket_manager_v1 *manager =
wl_container_of(listener, manager, display_destroy);
wl_signal_emit_mutable(&manager->events.destroy, manager);
wl_global_destroy(manager->global);
wl_list_remove(&manager->display_destroy.link);
free(manager);
}
struct xi_socket_manager_v1 *
xi_socket_manager_v1_create(struct wl_display *display)
{
struct xi_socket_manager_v1 *manager = calloc(1, sizeof(*manager));
if (!manager) {
return NULL;
}
manager->global = wl_global_create(display,
&xi_socket_manager_v1_interface,
XI_SOCKET_MANAGER_V1_VERSION, manager, manager_bind);
if (!manager->global) {
free(manager);
return NULL;
}
wl_signal_init(&manager->events.destroy);
wl_signal_init(&manager->events.register_socket);
manager->display_destroy.notify = handle_display_destroy;
wl_display_add_destroy_listener(display, &manager->display_destroy);
return manager;
}

View file

@ -60,6 +60,7 @@
#include "menu/menu.h"
#include "output.h"
#include "output-virtual.h"
#include "permissions.h"
#include "regions.h"
#include "resize-indicator.h"
#include "scaled-buffer/scaled-buffer.h"
@ -69,6 +70,7 @@
#include "view.h"
#include "workspaces.h"
#include "xwayland.h"
#include "protocols/xi_socket_manager_v1.h"
#define LAB_EXT_DATA_CONTROL_VERSION 1
#define LAB_EXT_FOREIGN_TOPLEVEL_LIST_VERSION 1
@ -288,6 +290,7 @@ allow_for_sandbox(const struct wlr_security_context_v1_state *security_state,
"zwp_idle_inhibit_manager_v1",
"zwp_pointer_constraints_v1",
"zxdg_output_manager_v1",
"xi_socket_manager_v1",
};
for (size_t i = 0; i < ARRAY_SIZE(allowed_protocols); i++) {
@ -317,6 +320,10 @@ server_global_filter(const struct wl_client *client, const struct wl_global *glo
}
#endif
if (!permissions_check(client, iface)) {
return false;
}
/* Do not allow security_context_manager_v1 to clients with a security context attached */
const struct wlr_security_context_v1_state *security_context =
wlr_security_context_manager_v1_lookup_client(
@ -663,6 +670,8 @@ server_init(struct server *server)
LAB_EXT_DATA_CONTROL_VERSION);
server->security_context_manager_v1 =
wlr_security_context_manager_v1_create(server->wl_display);
server->xi_socket_manager_v1 =
xi_socket_manager_v1_create(server->wl_display);
wlr_viewporter_create(server->wl_display);
wlr_single_pixel_buffer_manager_v1_create(server->wl_display);
wlr_fractional_scale_manager_v1_create(server->wl_display,