Label-based security on privileged globals

This adds a command (security_label) which is used to allow or deny access to
privileged interfaces on a client-by-client basis. If no security configuration
it present, all privileged operations are allowed to all clients.

 - "security_label deny default *" will deny clients access to all privileged
   operations
 - "security_label set default layer_shell" will overwrite the access list for
   the label "default" and only allow access to the layer_shell interface
 - "security_label permit recorder screencopy_manager" allows connections that
   have the label "recorder" access to screencopy_manager in addition to the
   current permissions of the "recorder" label

If a client does not have a label or if the label's permissions were not
defined using security_label, the permissions for the "default" label are used;
if no definition for "default" is present, all interfaces are allowed.

Using permit or deny on a new label does not copy the default.

The security configuration state is reset on a config reload (similar to the
assign and for_window lists).  Currently, the security policy is only enforced
during the binding or enumeration of global resources; existing handles to
privileged interfaces are not invalided by a change in policy, and existing
clients are not informed of the presence of newly available interfaces.
This commit is contained in:
Daniel De Graaf 2021-02-18 23:03:51 -05:00
parent e840cc72db
commit c292679c5c
11 changed files with 317 additions and 4 deletions

View file

@ -308,5 +308,6 @@ sway_cmd cmd_ipc_events;
sway_cmd cmd_ipc_event_cmd; sway_cmd cmd_ipc_event_cmd;
sway_cmd cmd_sandbox_socket; sway_cmd cmd_sandbox_socket;
sway_cmd cmd_security_label;
#endif #endif

View file

@ -14,6 +14,7 @@
#include "swaynag.h" #include "swaynag.h"
#include "tree/container.h" #include "tree/container.h"
#include "sway/input/tablet.h" #include "sway/input/tablet.h"
#include "sway/security.h"
#include "sway/tree/root.h" #include "sway/tree/root.h"
#include "wlr-layer-shell-unstable-v1-protocol.h" #include "wlr-layer-shell-unstable-v1-protocol.h"
@ -286,6 +287,11 @@ struct output_config {
enum config_dpms dpms_state; enum config_dpms dpms_state;
}; };
struct security_config {
char* name;
security_perm_mask_t permitted;
};
/** /**
* Stores size of gaps for each side * Stores size of gaps for each side
*/ */
@ -488,6 +494,7 @@ struct sway_config {
list_t *criteria; list_t *criteria;
list_t *no_focus; list_t *no_focus;
list_t *active_bar_modifiers; list_t *active_bar_modifiers;
list_t *security_configs;
struct sway_mode *current_mode; struct sway_mode *current_mode;
struct bar_config *current_bar; struct bar_config *current_bar;
uint32_t floating_mod; uint32_t floating_mod;

27
include/sway/security.h Normal file
View file

@ -0,0 +1,27 @@
#pragma once
enum security_perm {
PRIV_DATA_CONTROL_MANAGER,
PRIV_FOREIGN_TOPLEVEL_MANAGER,
PRIV_GAMMA_CONTROL_MANAGER,
PRIV_INPUT_INHIBIT,
PRIV_INPUT_KEYBOARD_SHORTCUTS_INHIBIT,
PRIV_INPUT_METHOD,
PRIV_INPUT_VIRTUAL_KEYBOARD,
PRIV_INPUT_VIRTUAL_POINTER,
PRIV_LAYER_SHELL,
PRIV_OUTPUT_MANAGER,
PRIV_OUTPUT_POWER_MANAGER,
PRIV_SCREENSHOT,
PRIV_SESSION_LOCK,
PRIV_LAST, /* not an actual permission */
};
typedef uint32_t security_perm_mask_t;
bool security_global_filter(const struct wl_client *client, const struct wl_global *global, void *data);
/**
* Get permission value associated with the given name, or PRIV_LAST if there is none
*/
enum security_perm security_get_perm_by_name(const char *name);

View file

@ -44,6 +44,7 @@ struct sway_server {
struct wlr_linux_dmabuf_v1 *linux_dmabuf_v1; struct wlr_linux_dmabuf_v1 *linux_dmabuf_v1;
struct wlr_data_device_manager *data_device_manager; struct wlr_data_device_manager *data_device_manager;
struct wlr_gamma_control_manager_v1 *gamma_control_manager;
struct sway_input_manager *input; struct sway_input_manager *input;
@ -98,6 +99,10 @@ struct sway_server {
struct wlr_xdg_activation_v1 *xdg_activation_v1; struct wlr_xdg_activation_v1 *xdg_activation_v1;
struct wl_listener xdg_activation_v1_request_activate; struct wl_listener xdg_activation_v1_request_activate;
struct wlr_export_dmabuf_manager_v1 *dmabuf_manager;
struct wlr_screencopy_manager_v1 *screencopy_manager;
struct wlr_data_control_manager_v1 *data_control_manager;
// The timeout for transactions, after which a transaction is applied // The timeout for transactions, after which a transaction is applied
// regardless of readiness. // regardless of readiness.
size_t txn_timeout_ms; size_t txn_timeout_ms;

View file

@ -83,6 +83,7 @@ static const struct cmd_handler handlers[] = {
{ "popup_during_fullscreen", cmd_popup_during_fullscreen }, { "popup_during_fullscreen", cmd_popup_during_fullscreen },
{ "sandbox_socket", cmd_sandbox_socket }, { "sandbox_socket", cmd_sandbox_socket },
{ "seat", cmd_seat }, { "seat", cmd_seat },
{ "security_label", cmd_security_label },
{ "set", cmd_set }, { "set", cmd_set },
{ "show_marks", cmd_show_marks }, { "show_marks", cmd_show_marks },
{ "smart_borders", cmd_smart_borders }, { "smart_borders", cmd_smart_borders },

View file

@ -0,0 +1,72 @@
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <string.h>
#include <strings.h>
#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 cmd_results *cmd_security_label(int argc, char **argv) {
int i;
struct cmd_results *error = NULL;
if ((error = checkarg(argc, "security_label", EXPECTED_AT_LEAST, 2))) {
return error;
}
char* op = argv[0];
char* who = argv[1];
uint32_t privs = 0;
enum { SET, PERMIT, DENY } mode;
if (strcmp(op, "set") == 0) {
mode = SET;
} else if (strcmp(op, "permit") == 0) {
mode = PERMIT;
} else if (strcmp(op, "deny") == 0) {
mode = DENY;
} else {
return cmd_results_new(CMD_INVALID, "Invalid operation: %s", op);
}
for(i = 2; i < argc; i++) {
char* arg = argv[i];
enum security_perm priv = security_get_perm_by_name(arg);
if (priv != PRIV_LAST)
privs |= 1 << priv;
else if (strcmp(arg, "*") == 0)
privs |= ~0;
else
return cmd_results_new(CMD_INVALID, "Unknown permission: %s", arg);
}
if (!config->security_configs)
config->security_configs = create_list();
for (i = 0; i < config->security_configs->length; i++) {
struct security_config *cfg = config->security_configs->items[i];
if (strcmp(cfg->name, who) == 0)
break;
}
if (i == config->security_configs->length) {
struct security_config *cfg = calloc(1, sizeof(*cfg));
cfg->name = strdup(who);
list_add(config->security_configs, cfg);
}
struct security_config *cfg = config->security_configs->items[i];
switch (mode) {
case SET:
cfg->permitted = privs;
break;
case PERMIT:
cfg->permitted |= privs;
break;
case DENY:
cfg->permitted &= ~privs;
break;
}
return cmd_results_new(CMD_SUCCESS, NULL);
}

View file

@ -85,6 +85,11 @@ static void free_mode(struct sway_mode *mode) {
free(mode); free(mode);
} }
static void free_security_config(struct security_config *cfg) {
free(cfg->name);
free(cfg);
}
void free_config(struct sway_config *config) { void free_config(struct sway_config *config) {
if (!config) { if (!config) {
return; return;
@ -151,6 +156,12 @@ void free_config(struct sway_config *config) {
} }
list_free(config->criteria); list_free(config->criteria);
} }
if (config->security_configs) {
for (int i = 0; i < config->security_configs->length; ++i) {
free_security_config(config->security_configs->items[i]);
}
list_free(config->security_configs);
}
list_free(config->no_focus); list_free(config->no_focus);
list_free(config->active_bar_modifiers); list_free(config->active_bar_modifiers);
list_free_items_and_destroy(config->config_chain); list_free_items_and_destroy(config->config_chain);

View file

@ -6,6 +6,7 @@ sway_sources = files(
'ipc-json.c', 'ipc-json.c',
'ipc-server.c', 'ipc-server.c',
'main.c', 'main.c',
'security.c',
'server.c', 'server.c',
'swaynag.c', 'swaynag.c',
'xdg_activation_v1.c', 'xdg_activation_v1.c',
@ -98,6 +99,7 @@ sway_sources = files(
'commands/seat/pointer_constraint.c', 'commands/seat/pointer_constraint.c',
'commands/seat/shortcuts_inhibitor.c', 'commands/seat/shortcuts_inhibitor.c',
'commands/seat/xcursor_theme.c', 'commands/seat/xcursor_theme.c',
'commands/security_label.c',
'commands/set.c', 'commands/set.c',
'commands/show_marks.c', 'commands/show_marks.c',
'commands/shortcuts_inhibitor.c', 'commands/shortcuts_inhibitor.c',

125
sway/security.c Normal file
View file

@ -0,0 +1,125 @@
#define _XOPEN_SOURCE 500 // for strdup
#define _POSIX_C_SOURCE 200112L
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <wayland-server-core.h>
#include <wlr/backend.h>
#include <wlr/backend/headless.h>
#include <wlr/backend/multi.h>
#include <wlr/backend/session.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_compositor.h>
#include <wlr/types/wlr_data_control_v1.h>
#include <wlr/types/wlr_export_dmabuf_v1.h>
#include <wlr/types/wlr_gamma_control_v1.h>
#include <wlr/types/wlr_idle.h>
#include <wlr/types/wlr_layer_shell_v1.h>
#include <wlr/types/wlr_pointer_constraints_v1.h>
#include <wlr/types/wlr_primary_selection_v1.h>
#include <wlr/types/wlr_relative_pointer_v1.h>
#include <wlr/types/wlr_screencopy_v1.h>
#include <wlr/types/wlr_server_decoration.h>
#include <wlr/types/wlr_tablet_v2.h>
#include <wlr/types/wlr_viewporter.h>
#include <wlr/types/wlr_xcursor_manager.h>
#include <wlr/types/wlr_xdg_decoration_v1.h>
#include <wlr/types/wlr_xdg_output_v1.h>
#include "config.h"
#include "list.h"
#include "log.h"
#include "stringop.h"
#include "sway/client_label.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"
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;
// with no security config, quickly default to allow all
if (config->security_configs == NULL || config->security_configs->length == 0)
return true;
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->foreign_toplevel_manager->global)
perm = PRIV_FOREIGN_TOPLEVEL_MANAGER;
else if (global == server->dmabuf_manager->global)
perm = PRIV_SCREENSHOT;
else if (global == server->screencopy_manager->global )
perm = PRIV_SCREENSHOT;
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 if (global == server->session_lock->global)
perm = PRIV_SESSION_LOCK;
else
return true;
security_perm_mask_t priv_bit = 1 << perm;
const char* label = wl_client_label_get((struct wl_client*)client);
if (label != NULL) {
for (int i = 0; i < config->security_configs->length; i++) {
struct security_config *cfg = config->security_configs->items[i];
if (lenient_strcmp(cfg->name, label) == 0) {
return !!(priv_bit & cfg->permitted);
}
}
}
for (int i = 0; i < config->security_configs->length; i++) {
struct security_config *cfg = config->security_configs->items[i];
if (lenient_strcmp(cfg->name, "default") == 0) {
return !!(priv_bit & cfg->permitted);
}
}
return true;
}
static const char* const perm_names[PRIV_LAST] = {
"data_control",
"foreign_toplevel_manager",
"gamma_control",
"input_inhibit",
"input_keyboard_shortcuts_inhibit",
"input_method",
"input_virtual_keyboard",
"input_virtual_pointer",
"layer_shell",
"output_manager",
"output_power_manager",
"screenshot",
"session_lock",
};
enum security_perm security_get_perm_by_name(const char *name)
{
int i;
for(i = 0; i < PRIV_LAST; i++) {
if (0 == strcmp(name, perm_names[i]))
return i;
}
return PRIV_LAST;
}

View file

@ -42,6 +42,7 @@
#include "sway/input/input-manager.h" #include "sway/input/input-manager.h"
#include "sway/output.h" #include "sway/output.h"
#include "sway/server.h" #include "sway/server.h"
#include "sway/security.h"
#include "sway/tree/root.h" #include "sway/tree/root.h"
#if HAVE_XWAYLAND #if HAVE_XWAYLAND
#include "sway/xwayland.h" #include "sway/xwayland.h"
@ -107,7 +108,7 @@ bool server_init(struct sway_server *server) {
server->data_device_manager = server->data_device_manager =
wlr_data_device_manager_create(server->wl_display); 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);
server->new_output.notify = handle_new_output; server->new_output.notify = handle_new_output;
wl_signal_add(&server->backend->events.new_output, &server->new_output); wl_signal_add(&server->backend->events.new_output, &server->new_output);
@ -194,9 +195,9 @@ bool server_init(struct sway_server *server) {
sway_log(SWAY_INFO, "VR will not be available"); sway_log(SWAY_INFO, "VR will not be available");
} }
wlr_export_dmabuf_manager_v1_create(server->wl_display); server->dmabuf_manager = wlr_export_dmabuf_manager_v1_create(server->wl_display);
wlr_screencopy_manager_v1_create(server->wl_display); server->screencopy_manager = wlr_screencopy_manager_v1_create(server->wl_display);
wlr_data_control_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_primary_selection_v1_device_manager_create(server->wl_display);
wlr_viewporter_create(server->wl_display); wlr_viewporter_create(server->wl_display);
@ -211,6 +212,8 @@ bool server_init(struct sway_server *server) {
wl_signal_add(&server->xdg_activation_v1->events.request_activate, wl_signal_add(&server->xdg_activation_v1->events.request_activate,
&server->xdg_activation_v1_request_activate); &server->xdg_activation_v1_request_activate);
wl_display_set_global_filter(server->wl_display, security_global_filter, server);
// Avoid using "wayland-0" as display socket // Avoid using "wayland-0" as display socket
char name_candidate[16]; char name_candidate[16];
for (int i = 1; i <= 32; ++i) { for (int i = 1; i <= 32; ++i) {

View file

@ -746,6 +746,12 @@ The default colors are:
dialog will not be rendered. If _leave_fullscreen_, the view will exit dialog will not be rendered. If _leave_fullscreen_, the view will exit
fullscreen mode and the dialog will be rendered. fullscreen mode and the dialog will be rendered.
*security_label* [set|permit|deny] <label> <permissions...>
Create a security label with the given permissions. See *SECURITY* for
details. Labels whose permissions are not defined using this command
will use the permissions assigned to the "default" label, which in turn
defaults to allowing all interfaces.
*set* $<name> <value> *set* $<name> <value>
Sets variable $_name_ to _value_. You can use the new variable in the Sets variable $_name_ to _value_. You can use the new variable in the
arguments of future commands. When the variable is used, it can be escaped arguments of future commands. When the variable is used, it can be escaped
@ -955,6 +961,59 @@ The following attributes may be matched with:
expression. If the value is \_\_focused\_\_, then all the views on the expression. If the value is \_\_focused\_\_, then all the views on the
currently focused workspace matches. currently focused workspace matches.
# SECURITY
Some wayland interfaces can be restricted for security reasons. Restrictions
are defined per-client by the client's label. A client's label can be set using
the _--label_ argument to *exec* or by connecting to a labeled *sandbox_socket*.
Clients without a label are assigned the "default" label. The following is a
list of the permissions and what they allow clients to do:
*data_control*
Access the clipboard and selection when not focused. Used by clipboard
managers.
*foreign_toplevel_manager*
View and manage the list of open windows.
*gamma_control*
Control how outputs manage colors.
*input_inhibit*
Prevent any input events from being sent to other clients. Used by
lockscreens.
*input_keyboard_shortcuts_inhibit*
Disable keyboard shortcuts while a surface is focused. Used by remote
desktop clients.
*input_method*
Act as a text input device - send text to other applications as if typed
by the user.
*input_virtual_keyboard*
Act as a keyboard to other applications.
*input_virtual_pointer*
Act as a pointer to other applications.
*layer_shell*
Create bars, docks and full-screen overlays without the normal window
management capabilities. Used by backgrounds, launchers, desktops, and
other "utility" clients.
*output_manager*
Enable, disable, and control the positions and resolutions of outputs.
*output_power_manager*
Turn outputs on or off.
*screenshot*
Take screenshots using export_dmabuf or screencopy.
*session_lock*
Lock the screen and display a lockscreen surface.
# SEE ALSO # SEE ALSO
*sway*(1) *sway-input*(5) *sway-output*(5) *sway-bar*(5) *sway-ipc*(7) *sway*(1) *sway-input*(5) *sway-output*(5) *sway-bar*(5) *sway-ipc*(7)