This commit is contained in:
Tobias Bengfort 2026-04-14 22:23:48 +01:00 committed by GitHub
commit 3b0d4e219d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 327 additions and 11 deletions

View file

@ -18,6 +18,7 @@ struct action {
*/
uint32_t type; /* enum action_type */
uint32_t allowed_interfaces;
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

@ -202,6 +202,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

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

12
include/permissions.h Normal file
View file

@ -0,0 +1,12 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef LABWC_PERMISSIONS_H
#define LABWC_PERMISSIONS_H
#include <wayland-server-core.h>
#include <stdbool.h>
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_EXT_SOCKET_MANAGER_H
#define LABWC_PROTOCOLS_EXT_SOCKET_MANAGER_H
#include <wayland-server-core.h>
struct ext_socket_manager_v1 {
struct wl_global *global;
struct {
struct wl_signal destroy;
struct wl_signal register_socket;
} events;
struct wl_listener display_destroy;
};
struct ext_socket_manager_v1 *ext_socket_manager_v1_create(
struct wl_display *display);
#endif

View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="ext_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="ext_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 privileges 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

@ -16,6 +16,7 @@ wayland_scanner_server = generator(
server_protocols = [
'wlr-layer-shell-unstable-v1.xml',
'wlr-output-power-management-unstable-v1.xml',
'ext-socket-manager-unstable-v1.xml',
]
server_protos_src = []

View file

@ -27,6 +27,7 @@
#include "menu/menu.h"
#include "output.h"
#include "output-virtual.h"
#include "permissions.h"
#include "regions.h"
#include "show-desktop.h"
#include "ssd.h"
@ -304,6 +305,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, "allow")) {
action->allowed_interfaces |= parse_privileged_interface(content);
goto cleanup;
}
break;
case ACTION_TYPE_MOVE_TO_EDGE:
@ -572,6 +576,7 @@ action_create(const char *action_name)
struct action *action = znew(*action);
action->type = action_type;
action->allowed_interfaces = 0;
wl_list_init(&action->args);
return action;
}
@ -1079,7 +1084,12 @@ run_action(struct view *view, 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 = -1;
if (action->allowed_interfaces) {
socket_fd = permissions_context_create(
server.wl_display, action->allowed_interfaces);
}
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

@ -1083,6 +1083,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")) {
@ -1477,6 +1479,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);

View file

@ -11,6 +11,7 @@
#include <wlr/backend/multi.h>
#include <wlr/config.h>
#include <wlr/util/log.h>
#include "action.h"
#include "common/buf.h"
#include "common/dir.h"
#include "common/file-helpers.h"
@ -219,12 +220,12 @@ execute_update(const char *env_keys, const char *env_unset_keys, 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);
}
@ -303,6 +304,13 @@ session_environment_init(void)
paths_destroy(&paths);
}
static void
session_run_rc_autostart(void)
{
wlr_log(WLR_INFO, "run autostart actions");
actions_run(NULL, &rc.autostart, NULL);
}
void
session_run_script(const char *script)
{
@ -320,7 +328,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) {
@ -335,6 +343,7 @@ session_autostart_init(void)
{
/* Update dbus and systemd user environment, each may fail gracefully */
update_activation_env(/* initialize */ true);
session_run_rc_autostart();
session_run_script("autostart");
}

View file

@ -153,7 +153,7 @@ idle_callback(void *data)
session_autostart_init();
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',
@ -56,5 +57,6 @@ subdir('foreign-toplevel')
subdir('img')
subdir('input')
subdir('menu')
subdir('protocols')
subdir('scaled-buffer')
subdir('ssd')

102
src/permissions.c Normal file
View file

@ -0,0 +1,102 @@
// 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;
};
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;
}
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.allowed_interfaces;
uint32_t required = parse_privileged_interface(iface->name);
return (permissions & required) == required;
}

View file

@ -0,0 +1,76 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <stdlib.h>
#include <protocols/ext_socket_manager_v1.h>
#include "permissions.h"
#include "ext-socket-manager-unstable-v1-protocol.h"
#define EXT_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 ext_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 ext_socket_manager_v1 *manager = data;
struct wl_resource *resource =
wl_resource_create(client, &ext_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 ext_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 ext_socket_manager_v1 *
ext_socket_manager_v1_create(struct wl_display *display)
{
struct ext_socket_manager_v1 *manager = calloc(1, sizeof(*manager));
if (!manager) {
return NULL;
}
manager->global = wl_global_create(display,
&ext_socket_manager_v1_interface,
EXT_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

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

View file

@ -0,0 +1 @@
subdir('ext-socket-manager')

View file

@ -66,6 +66,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"
@ -75,6 +76,7 @@
#include "view.h"
#include "workspaces.h"
#include "xwayland.h"
#include "protocols/ext_socket_manager_v1.h"
#define LAB_EXT_DATA_CONTROL_VERSION 1
#define LAB_EXT_FOREIGN_TOPLEVEL_LIST_VERSION 1
@ -262,6 +264,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",
"ext_socket_manager_v1",
};
for (size_t i = 0; i < ARRAY_SIZE(allowed_protocols); i++) {
@ -294,8 +297,7 @@ server_global_filter(const struct wl_client *client, const struct wl_global *glo
}
#endif
uint32_t iface_id = parse_privileged_interface(iface->name);
if (iface_id && !(iface_id & rc.allowed_interfaces)) {
if (!permissions_check(client, iface)) {
return false;
}
@ -318,7 +320,7 @@ server_global_filter(const struct wl_client *client, const struct wl_global *glo
* This ensures that our lists are in sync with what
* protocols labwc supports.
*/
if (!allow && !iface_id) {
if (!allow && !parse_privileged_interface(iface->name)) {
wlr_log(WLR_ERROR, "Blocking unknown protocol %s", iface->name);
} else if (!allow) {
wlr_log(WLR_DEBUG, "Blocking %s for security context %s->%s->%s",
@ -688,6 +690,8 @@ server_init(void)
LAB_EXT_DATA_CONTROL_VERSION);
server.security_context_manager_v1 =
wlr_security_context_manager_v1_create(server.wl_display);
server.ext_socket_manager_v1 =
ext_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,