From 4f4b335737b6198b5f0be2d2a5f8057eb4f9360f Mon Sep 17 00:00:00 2001 From: Daniel De Graaf Date: Sun, 6 Sep 2020 18:45:00 -0400 Subject: [PATCH] Security v2 --- include/sway/commands.h | 2 + include/sway/security.h | 24 +++++++ include/sway/server.h | 5 ++ sway/commands.c | 1 + sway/commands/security.c | 129 ++++++++++++++++++++++++++++++++++ sway/meson.build | 2 + sway/security.c | 145 +++++++++++++++++++++++++++++++++++++++ sway/server.c | 11 +-- 8 files changed, 315 insertions(+), 4 deletions(-) create mode 100644 include/sway/security.h create mode 100644 sway/commands/security.c create mode 100644 sway/security.c diff --git a/include/sway/commands.h b/include/sway/commands.h index 07730f980..f7105743e 100644 --- a/include/sway/commands.h +++ b/include/sway/commands.h @@ -302,4 +302,6 @@ sway_cmd cmd_ipc_cmd; sway_cmd cmd_ipc_events; sway_cmd cmd_ipc_event_cmd; +sway_cmd cmd_security; + #endif diff --git a/include/sway/security.h b/include/sway/security.h new file mode 100644 index 000000000..6e9561d53 --- /dev/null +++ b/include/sway/security.h @@ -0,0 +1,24 @@ +#pragma once + +enum security_perm { + PRIV_LAYER_SHELL, + PRIV_OUTPUT_MANAGER, + PRIV_INPUT_METHOD, + PRIV_TEXT_INPUT, + PRIV_FOREIGN_TOPLEVEL_MANAGER, + PRIV_DMABUF_MANAGER, + PRIV_SCREENCOPY_MANAGER, + PRIV_DATA_CONTROL_MANAGER, + PRIV_GAMMA_CONTROL_MANAGER, + PRIV_INPUT_INHIBIT, + PRIV_INPUT_KEYBOARD_SHORTCUTS_INHIBIT, + PRIV_INPUT_VIRTUAL_KEYBOARD, + PRIV_INPUT_VIRTUAL_POINTER, + PRIV_OUTPUT_POWER_MANAGER, + PRIV_LAST, /* not an actual permission */ +}; + +bool security_global_filter(const struct wl_client *client, const struct wl_global *global, void *data); + +void security_set_permit(struct wl_client *client, uint32_t allowed); +uint32_t security_get_permit(const struct wl_client *client); diff --git a/include/sway/server.h b/include/sway/server.h index 0f5e3ab20..a0f051acc 100644 --- a/include/sway/server.h +++ b/include/sway/server.h @@ -37,6 +37,7 @@ struct sway_server { struct wl_listener compositor_new_surface; struct wlr_data_device_manager *data_device_manager; + struct wlr_gamma_control_manager_v1 *gamma_control_manager; struct sway_input_manager *input; @@ -85,6 +86,10 @@ struct sway_server { struct wlr_text_input_manager_v3 *text_input; struct wlr_foreign_toplevel_manager_v1 *foreign_toplevel_manager; + struct wlr_export_dmabuf_manager_v1 *dmabuf_manager; + struct wlr_screencopy_manager_v1 *screencopy_manager; + struct wlr_data_control_manager_v1 *data_control_manager; + size_t txn_timeout_ms; list_t *transactions; list_t *dirty_nodes; diff --git a/sway/commands.c b/sway/commands.c index fe1e98b53..98d4f97c6 100644 --- a/sway/commands.c +++ b/sway/commands.c @@ -81,6 +81,7 @@ static struct cmd_handler handlers[] = { { "output", cmd_output }, { "popup_during_fullscreen", cmd_popup_during_fullscreen }, { "seat", cmd_seat }, + { "security", cmd_security }, { "set", cmd_set }, { "show_marks", cmd_show_marks }, { "smart_borders", cmd_smart_borders }, diff --git a/sway/commands/security.c b/sway/commands/security.c new file mode 100644 index 000000000..fee8438d8 --- /dev/null +++ b/sway/commands/security.c @@ -0,0 +1,129 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include "sway/commands.h" +#include "sway/security.h" +#include "sway/tree/container.h" +#include "sway/tree/view.h" +#include "list.h" +#include "log.h" + +struct find_by_cid_data { + uint32_t cid; + struct wl_client *target; +}; + +static void find_by_cid_iter(struct sway_container *container, void* data) { + struct find_by_cid_data *ctx = data; + if (!container->view) + return; + if (ctx->cid != container->node.id) + return; + if (!container->view->surface) + return; + ctx->target = wl_resource_get_client(container->view->surface->resource); +} + +static void do_cmd(struct wl_client *target, int mode, uint32_t privs) { + uint32_t allow; + switch (mode) { + case 0: + security_set_permit(target, privs); + break; + case 1: + allow = security_get_permit(target); + allow |= privs; + security_set_permit(target, allow); + break; + case 2: + allow = security_get_permit(target); + allow &= ~privs; + security_set_permit(target, allow); + break; + } +} + +struct cmd_results *cmd_security(int argc, char **argv) { + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "security", EXPECTED_AT_LEAST, 3))) { + return error; + } + + char* op = argv[0]; + char* who = argv[1]; + uint32_t privs = 0; + int mode; + if (strcmp(op, "set") == 0) { + mode = 0; + } else if (strcmp(op, "permit") == 0) { + mode = 1; + } else if (strcmp(op, "deny") == 0) { + mode = 2; + } else { + return cmd_results_new(CMD_INVALID, "Invalid operation", op); + } + + for(int i = 2; i < argc; i++) { + char* arg = argv[i]; + if (strcmp(arg, "*") == 0) + privs |= ~0; + else if (strcmp(arg, "layer_shell") == 0) + privs |= 1 << PRIV_LAYER_SHELL; + else if (strcmp(arg, "output_manager") == 0) + privs |= 1 << PRIV_OUTPUT_MANAGER; + else if (strcmp(arg, "input_method") == 0) + privs |= 1 << PRIV_INPUT_METHOD; + else if (strcmp(arg, "text_input") == 0) + privs |= 1 << PRIV_TEXT_INPUT; + else if (strcmp(arg, "foreign_toplevel_manager") == 0) + privs |= 1 << PRIV_FOREIGN_TOPLEVEL_MANAGER; + else if (strcmp(arg, "dmabuf_manager") == 0) + privs |= 1 << PRIV_DMABUF_MANAGER; + else if (strcmp(arg, "screencopy_manager") == 0) + privs |= 1 << PRIV_SCREENCOPY_MANAGER; + else if (strcmp(arg, "data_control_manager") == 0) + privs |= 1 << PRIV_DATA_CONTROL_MANAGER; + else if (strcmp(arg, "gamma_control_manager") == 0) + privs |= 1 << PRIV_GAMMA_CONTROL_MANAGER; + else if (strcmp(arg, "input_inhibit") == 0) + privs |= 1 << PRIV_INPUT_INHIBIT; + else if (strcmp(arg, "input_keyboard_shortcuts_inhibit") == 0) + privs |= 1 << PRIV_INPUT_KEYBOARD_SHORTCUTS_INHIBIT; + else if (strcmp(arg, "input_virtual_keyboard") == 0) + privs |= 1 << PRIV_INPUT_VIRTUAL_KEYBOARD; + else if (strcmp(arg, "input_virtual_pointer") == 0) + privs |= 1 << PRIV_INPUT_VIRTUAL_POINTER; + else if (strcmp(arg, "output_power_manager") == 0) + privs |= 1 << PRIV_OUTPUT_POWER_MANAGER; + else + return cmd_results_new(CMD_INVALID, "Unknown permission", arg); + } + + if (strcmp(who, "*") == 0) { + do_cmd(NULL, mode, privs); + } else if (strncmp(who, "cid=", 4) == 0) { + struct find_by_cid_data data = { + .cid = strtoul(who + 4, NULL, 0), + }; + root_for_each_container(find_by_cid_iter, &data); + if (!data.target) + return cmd_results_new(CMD_INVALID, "Client not found", who); + do_cmd(data.target, mode, privs); + } else if (strncmp(who, "pid=", 4) == 0) { + pid_t target_pid = strtoul(who + 4, NULL, 0); + struct wl_client* client; + struct wl_list *clients = wl_display_get_client_list(server.wl_display); + wl_client_for_each(client, clients) { + pid_t pid; + wl_client_get_credentials(client, &pid, NULL, NULL); + if (pid == target_pid) + do_cmd(client, mode, privs); + } + } else { + // TODO support other criteria? Only secure things like pid + return cmd_results_new(CMD_INVALID, "Invalid target exmpression", who); + } + + return cmd_results_new(CMD_SUCCESS, NULL); +} diff --git a/sway/meson.build b/sway/meson.build index 0db458362..df161fd60 100644 --- a/sway/meson.build +++ b/sway/meson.build @@ -6,6 +6,7 @@ sway_sources = files( 'ipc-json.c', 'ipc-server.c', 'main.c', + 'security.c', 'server.c', 'swaynag.c', 'xdg_decoration.c', @@ -96,6 +97,7 @@ sway_sources = files( 'commands/seat/pointer_constraint.c', 'commands/seat/shortcuts_inhibitor.c', 'commands/seat/xcursor_theme.c', + 'commands/security.c', 'commands/set.c', 'commands/show_marks.c', 'commands/shortcuts_inhibitor.c', diff --git a/sway/security.c b/sway/security.c new file mode 100644 index 000000000..c9f1173ad --- /dev/null +++ b/sway/security.c @@ -0,0 +1,145 @@ +#define _POSIX_C_SOURCE 200112L +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "config.h" +#include "list.h" +#include "log.h" +#include "sway/config.h" +#include "sway/desktop/idle_inhibit_v1.h" +#include "sway/input/input-manager.h" +#include "sway/output.h" +#include "sway/server.h" +#include "sway/security.h" +#include "sway/tree/root.h" + +struct security_permit { + uint32_t allowed; + const struct wl_client *client; + struct wl_listener on_destroy; +}; + +static uint32_t security_allow_default = ~0; +static list_t *security_permit_list; + +bool security_global_filter(const struct wl_client *client, const struct wl_global *global, void *data) +{ + struct sway_server *server = data; + enum security_perm perm; + + if (global == server->layer_shell->global) + perm = PRIV_LAYER_SHELL; + else if (global == server->output_manager_v1->global) + perm = PRIV_OUTPUT_MANAGER; + else if (global == server->input_method->global) + perm = PRIV_INPUT_METHOD; + else if (global == server->text_input->global) + perm = PRIV_TEXT_INPUT; + else if (global == server->foreign_toplevel_manager->global) + perm = PRIV_FOREIGN_TOPLEVEL_MANAGER; + else if (global == server->dmabuf_manager->global) + perm = PRIV_DMABUF_MANAGER; + else if (global == server->screencopy_manager->global ) + perm = PRIV_SCREENCOPY_MANAGER; + else if (global == server->data_control_manager->global) + perm = PRIV_DATA_CONTROL_MANAGER; + else if (global == server->gamma_control_manager->global) + perm = PRIV_GAMMA_CONTROL_MANAGER; + else if (global == server->input->inhibit->global) + perm = PRIV_INPUT_INHIBIT; + else if (global == server->input->keyboard_shortcuts_inhibit->global) + perm = PRIV_INPUT_KEYBOARD_SHORTCUTS_INHIBIT; + else if (global == server->input->virtual_keyboard->global) + perm = PRIV_INPUT_VIRTUAL_KEYBOARD; + else if (global == server->input->virtual_pointer->global) + perm = PRIV_INPUT_VIRTUAL_POINTER; + else if (global == server->output_power_manager_v1->global) + perm = PRIV_OUTPUT_POWER_MANAGER; + else + return true; + + uint32_t priv_bit = 1 << perm; + uint32_t allowed = security_get_permit(client); + if (allowed & priv_bit) + return true; + + return false; +} + +static void security_client_destroy(struct wl_listener *listener, void *client_v) +{ + const struct wl_client *client = client_v; + if (!security_permit_list) + return; + + for (int i = 0; i < security_permit_list->length; ++i) { + struct security_permit *item = security_permit_list->items[i]; + if (client != item->client) + continue; + list_del(security_permit_list, i); + free(item); + return; + } +} + + +uint32_t security_get_permit(const struct wl_client *client) +{ + if (client && security_permit_list) { + for (int i = 0; i < security_permit_list->length; ++i) { + struct security_permit *item = security_permit_list->items[i]; + if (client == item->client) + return item->allowed; + } + } + return security_allow_default; +} + +void security_set_permit(struct wl_client *client, uint32_t allowed) { + struct security_permit *found = NULL; + if (client == NULL) { + security_allow_default = allowed; + return; + } + if (!security_permit_list) + security_permit_list = create_list(); + for (int i = 0; i < security_permit_list->length; ++i) { + struct security_permit *item = security_permit_list->items[i]; + if (client == item->client) { + found = item; + break; + } + } + if (!found) { + found = malloc(sizeof(*found)); + found->client = client; + found->on_destroy.notify = security_client_destroy; + list_add(security_permit_list, found); + wl_client_add_destroy_listener(client, &found->on_destroy); + } + found->allowed = allowed; +} diff --git a/sway/server.c b/sway/server.c index ff848450d..3ecac5c24 100644 --- a/sway/server.c +++ b/sway/server.c @@ -34,6 +34,7 @@ #include "sway/input/input-manager.h" #include "sway/output.h" #include "sway/server.h" +#include "sway/security.h" #include "sway/tree/root.h" #if HAVE_XWAYLAND #include "sway/xwayland.h" @@ -68,7 +69,7 @@ bool server_init(struct sway_server *server) { server->data_device_manager = wlr_data_device_manager_create(server->wl_display); - wlr_gamma_control_manager_v1_create(server->wl_display); + server->gamma_control_manager = wlr_gamma_control_manager_v1_create(server->wl_display); wlr_gtk_primary_selection_device_manager_create(server->wl_display); server->new_output.notify = handle_new_output; @@ -145,12 +146,14 @@ bool server_init(struct sway_server *server) { server->foreign_toplevel_manager = wlr_foreign_toplevel_manager_v1_create(server->wl_display); - wlr_export_dmabuf_manager_v1_create(server->wl_display); - wlr_screencopy_manager_v1_create(server->wl_display); - wlr_data_control_manager_v1_create(server->wl_display); + server->dmabuf_manager = wlr_export_dmabuf_manager_v1_create(server->wl_display); + server->screencopy_manager = wlr_screencopy_manager_v1_create(server->wl_display); + server->data_control_manager = wlr_data_control_manager_v1_create(server->wl_display); wlr_primary_selection_v1_device_manager_create(server->wl_display); wlr_viewporter_create(server->wl_display); + wl_display_set_global_filter(server->wl_display, security_global_filter, server); + server->socket = wl_display_add_socket_auto(server->wl_display); if (!server->socket) { sway_log(SWAY_ERROR, "Unable to open wayland socket");