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_sandbox_socket;
sway_cmd cmd_security_label;
#endif

View file

@ -14,6 +14,7 @@
#include "swaynag.h"
#include "tree/container.h"
#include "sway/input/tablet.h"
#include "sway/security.h"
#include "sway/tree/root.h"
#include "wlr-layer-shell-unstable-v1-protocol.h"
@ -286,6 +287,11 @@ struct output_config {
enum config_dpms dpms_state;
};
struct security_config {
char* name;
security_perm_mask_t permitted;
};
/**
* Stores size of gaps for each side
*/
@ -488,6 +494,7 @@ struct sway_config {
list_t *criteria;
list_t *no_focus;
list_t *active_bar_modifiers;
list_t *security_configs;
struct sway_mode *current_mode;
struct bar_config *current_bar;
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_data_device_manager *data_device_manager;
struct wlr_gamma_control_manager_v1 *gamma_control_manager;
struct sway_input_manager *input;
@ -98,6 +99,10 @@ struct sway_server {
struct wlr_xdg_activation_v1 *xdg_activation_v1;
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
// regardless of readiness.
size_t txn_timeout_ms;

View file

@ -83,6 +83,7 @@ static const struct cmd_handler handlers[] = {
{ "popup_during_fullscreen", cmd_popup_during_fullscreen },
{ "sandbox_socket", cmd_sandbox_socket },
{ "seat", cmd_seat },
{ "security_label", cmd_security_label },
{ "set", cmd_set },
{ "show_marks", cmd_show_marks },
{ "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);
}
static void free_security_config(struct security_config *cfg) {
free(cfg->name);
free(cfg);
}
void free_config(struct sway_config *config) {
if (!config) {
return;
@ -151,6 +156,12 @@ void free_config(struct sway_config *config) {
}
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->active_bar_modifiers);
list_free_items_and_destroy(config->config_chain);

View file

@ -6,6 +6,7 @@ sway_sources = files(
'ipc-json.c',
'ipc-server.c',
'main.c',
'security.c',
'server.c',
'swaynag.c',
'xdg_activation_v1.c',
@ -98,6 +99,7 @@ sway_sources = files(
'commands/seat/pointer_constraint.c',
'commands/seat/shortcuts_inhibitor.c',
'commands/seat/xcursor_theme.c',
'commands/security_label.c',
'commands/set.c',
'commands/show_marks.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/output.h"
#include "sway/server.h"
#include "sway/security.h"
#include "sway/tree/root.h"
#if HAVE_XWAYLAND
#include "sway/xwayland.h"
@ -107,7 +108,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);
server->new_output.notify = handle_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");
}
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);
@ -211,6 +212,8 @@ bool server_init(struct sway_server *server) {
wl_signal_add(&server->xdg_activation_v1->events.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
char name_candidate[16];
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
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>
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
@ -955,6 +961,59 @@ The following attributes may be matched with:
expression. If the value is \_\_focused\_\_, then all the views on the
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
*sway*(1) *sway-input*(5) *sway-output*(5) *sway-bar*(5) *sway-ipc*(7)