From de700e2a3a02ab6947ddfd90ffd178b42272abb1 Mon Sep 17 00:00:00 2001 From: Tobias Bengfort Date: Thu, 18 Sep 2025 12:49:45 +0200 Subject: [PATCH 1/5] allow to reuse set_cloexec --- include/common/spawn.h | 7 +++++++ src/common/spawn.c | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/include/common/spawn.h b/include/common/spawn.h index 43123269..3f766eff 100644 --- a/include/common/spawn.h +++ b/include/common/spawn.h @@ -2,8 +2,15 @@ #ifndef LABWC_SPAWN_H #define LABWC_SPAWN_H +#include #include +/** + * 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 diff --git a/src/common/spawn.c b/src/common/spawn.c index 898c4800..9468b2b9 100644 --- a/src/common/spawn.c +++ b/src/common/spawn.c @@ -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); From 065485a08df680cdf17d2443fe68ade4b8e85466 Mon Sep 17 00:00:00 2001 From: Tobias Bengfort Date: Sat, 6 Sep 2025 10:43:26 +0200 Subject: [PATCH 2/5] implement permission infrastructure --- include/permissions.h | 12 ++++ src/meson.build | 1 + src/permissions.c | 139 ++++++++++++++++++++++++++++++++++++++++++ src/server.c | 5 ++ 4 files changed, 157 insertions(+) create mode 100644 include/permissions.h create mode 100644 src/permissions.c diff --git a/include/permissions.h b/include/permissions.h new file mode 100644 index 00000000..afd97c8c --- /dev/null +++ b/include/permissions.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef LABWC_PERMISSIONS_H +#define LABWC_PERMISSIONS_H + +#include +#include + +uint32_t permissions_from_interface_name(const char *s); +int permissions_context_create(struct wl_display *display, uint32_t permissions); +bool permissions_check(const struct wl_client *client, const struct wl_interface *iface); + +#endif diff --git a/src/meson.build b/src/meson.build index 330b5daf..693c162b 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,6 +1,7 @@ labwc_sources = files( 'action.c', 'buffer.c', + 'permissions.c', 'debug.c', 'desktop.c', 'dnd.c', diff --git a/src/permissions.c b/src/permissions.c new file mode 100644 index 00000000..914e9963 --- /dev/null +++ b/src/permissions.c @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: GPL-2.0-only +#define _POSIX_C_SOURCE 200809L +#include "permissions.h" +#include +#include +#include +#include +#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; +} + +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; + } + + struct wl_client *client = wl_client_create(display, sv[0]); + if (!client) { + wlr_log(WLR_ERROR, "failed to create client"); + close(sv[0]); + close(sv[1]); + return -1; + } + + struct permissions_context *context = znew(*context); + context->permissions = permissions; + context->fd = sv[0]; + context->destroy.notify = permissions_context_handle_destroy; + wl_client_add_destroy_listener(client, &context->destroy); + + return sv[1]; +} + +bool +permissions_check(const struct wl_client *client, const struct wl_interface *iface) +{ + uint32_t permissions = permissions_get(client); + uint32_t required = permissions_from_interface_name(iface->name); + return (permissions & required) == required; +} diff --git a/src/server.c b/src/server.c index abaaaf0b..5cfbbe80 100644 --- a/src/server.c +++ b/src/server.c @@ -58,6 +58,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" @@ -314,6 +315,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( From f084a99f29d97b3e701a54fba9a947c10d3cbeb6 Mon Sep 17 00:00:00 2001 From: Tobias Bengfort Date: Sat, 6 Sep 2025 10:44:13 +0200 Subject: [PATCH 3/5] add permissions to actions --- include/action.h | 1 + include/common/spawn.h | 2 +- src/action.c | 8 +++++++- src/common/spawn.c | 12 +++++++++++- src/config/session.c | 6 +++--- src/main.c | 2 +- 6 files changed, 24 insertions(+), 7 deletions(-) diff --git a/include/action.h b/include/action.h index 7692e383..2964f63a 100644 --- a/include/action.h +++ b/include/action.h @@ -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 */ }; diff --git a/include/common/spawn.h b/include/common/spawn.h index 3f766eff..9145c727 100644 --- a/include/common/spawn.h +++ b/include/common/spawn.h @@ -21,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 diff --git a/src/action.c b/src/action.c index a351160d..ea68f2ee 100644 --- a/src/action.c +++ b/src/action.c @@ -26,6 +26,7 @@ #include "osd.h" #include "output.h" #include "output-virtual.h" +#include "permissions.h" #include "regions.h" #include "ssd.h" #include "theme.h" @@ -335,6 +336,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, "permissions")) { + action->permissions |= permissions_from_interface_name(content); + goto cleanup; } break; case ACTION_TYPE_MOVE_TO_EDGE: @@ -551,6 +555,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; } @@ -1063,7 +1068,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; } diff --git a/src/common/spawn.c b/src/common/spawn.c index 9468b2b9..1cb4dadd 100644 --- a/src/common/spawn.c +++ b/src/common/spawn.c @@ -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); diff --git a/src/config/session.c b/src/config/session.c index 515b6803..d0885c6e 100644 --- a/src/config/session.c +++ b/src/config/session.c @@ -232,12 +232,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); @@ -303,7 +303,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) { diff --git a/src/main.c b/src/main.c index 755a0a0c..1ed49677 100644 --- a/src/main.c +++ b/src/main.c @@ -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); } } From 089a362d7397bd52536cae90d1eb09cb1d06c86e Mon Sep 17 00:00:00 2001 From: Tobias Bengfort Date: Sat, 6 Sep 2025 12:46:52 +0200 Subject: [PATCH 4/5] add autostart with permissions --- include/config/rcxml.h | 2 ++ src/config/rcxml.c | 3 +++ src/config/session.c | 9 +++++++++ 3 files changed, 14 insertions(+) diff --git a/include/config/rcxml.h b/include/config/rcxml.h index 50874571..8243f2cb 100644 --- a/include/config/rcxml.h +++ b/include/config/rcxml.h @@ -187,6 +187,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; diff --git a/src/config/rcxml.c b/src/config/rcxml.c index 6ed1522b..ef092dbf 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -1042,6 +1042,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")) { @@ -1358,6 +1360,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); diff --git a/src/config/session.c b/src/config/session.c index d0885c6e..5113156f 100644 --- a/src/config/session.c +++ b/src/config/session.c @@ -11,6 +11,7 @@ #include #include #include +#include "action.h" #include "common/buf.h" #include "common/dir.h" #include "common/file-helpers.h" @@ -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) { @@ -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"); } From 0f112b7463302c4cbbf36b826ca8279b21cf58c7 Mon Sep 17 00:00:00 2001 From: Tobias Bengfort Date: Thu, 18 Sep 2025 13:49:03 +0200 Subject: [PATCH 5/5] add default permissions --- include/config/rcxml.h | 1 + src/config/rcxml.c | 4 ++++ src/permissions.c | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/include/config/rcxml.h b/include/config/rcxml.h index 8243f2cb..a5e14b49 100644 --- a/include/config/rcxml.h +++ b/include/config/rcxml.h @@ -71,6 +71,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; diff --git a/src/config/rcxml.c b/src/config/rcxml.c index ef092dbf..95a80f17 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -31,6 +31,7 @@ #include "config/touch.h" #include "labwc.h" #include "osd.h" +#include "permissions.h" #include "regions.h" #include "ssd.h" #include "translate.h" @@ -1095,6 +1096,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")) { @@ -1390,6 +1393,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; diff --git a/src/permissions.c b/src/permissions.c index 914e9963..a0f9b00a 100644 --- a/src/permissions.c +++ b/src/permissions.c @@ -133,7 +133,7 @@ permissions_context_create(struct wl_display *display, uint32_t permissions) bool permissions_check(const struct wl_client *client, const struct wl_interface *iface) { - uint32_t permissions = permissions_get(client); + uint32_t permissions = permissions_get(client) | rc.default_permissions; uint32_t required = permissions_from_interface_name(iface->name); return (permissions & required) == required; }