Merge pull request #981 from SirCmpwn/security

Security features
This commit is contained in:
Drew DeVault 2016-12-04 08:30:40 -05:00 committed by GitHub
commit 5778c59a2f
20 changed files with 1159 additions and 44 deletions

View file

@ -45,9 +45,11 @@ option(enable-swaybar "Enables the swaybar utility" YES)
option(enable-swaygrab "Enables the swaygrab utility" YES)
option(enable-swaymsg "Enables the swaymsg utility" YES)
option(enable-gdk-pixbuf "Use Pixbuf to support more image formats" YES)
option(enable-binding-event "Enables binding event subscription" YES)
option(zsh-completions "Zsh shell completions" NO)
option(default-wallpaper "Installs the default wallpaper" YES)
option(ld-library-path "Configures sway's default LD_LIBRARY_PATH" "/usr/lib")
add_definitions(-D_LD_LIBRARY_PATH="${ld-library-path}")
find_package(JsonC REQUIRED)
find_package(PCRE REQUIRED)
@ -83,9 +85,6 @@ if (enable-gdk-pixbuf)
else()
message(STATUS "Building without gdk-pixbuf, only png images supported.")
endif()
if(enable-binding-event)
add_definitions(-DSWAY_BINDING_EVENT=1)
endif()
include_directories(include)

52
config.d/security.in Normal file
View file

@ -0,0 +1,52 @@
# sway security rules
#
# Read sway-security(7) for details on how to secure your sway install.
#
# You MUST read this man page if you intend to attempt to secure your sway
# installation.
# Configures which programs are allowed to use which sway features
permit * fullscreen keyboard mouse ipc
permit __PREFIX__/bin/swaylock lock
permit __PREFIX__/bin/swaybar panel
permit __PREFIX__/bin/swaybg background
permit __PREFIX__/bin/swaygrab screenshot
# Configures which IPC features are enabled
ipc {
command enabled
outputs enabled
workspaces enabled
tree enabled
marks enabled
bar-config enabled
inputs enabled
events {
workspace enabled
output enabled
mode enabled
window enabled
modifier enabled
input enabled
binding disabled
}
}
# Limits the contexts from which certain commands are permitted
commands {
* all
fullscreen binding criteria
bindsym config
exit binding
kill binding
# You should not change these unless you know what you're doing - it could
# cripple your security
reload binding
restart binding
permit config
reject config
ipc config
}

View file

@ -195,10 +195,4 @@ bar {
}
}
# You may want this:
#
# include ~/.config/sway/conf.d/*
#
# Protip:
#
# include ~/.config/sway/`hostname`/*
include __SYSCONFDIR__/sway/config.d/*

View file

@ -1,6 +1,8 @@
#ifndef _SWAY_IPC_H
#define _SWAY_IPC_H
#define event_mask(ev) (1 << (ev & 0x7F))
enum ipc_command_type {
IPC_COMMAND = 0,
IPC_GET_WORKSPACES = 1,

View file

@ -18,7 +18,10 @@ enum cmd_status {
CMD_BLOCK_MODE,
CMD_BLOCK_BAR,
CMD_BLOCK_BAR_COLORS,
CMD_BLOCK_INPUT
CMD_BLOCK_INPUT,
CMD_BLOCK_COMMANDS,
CMD_BLOCK_IPC,
CMD_BLOCK_IPC_EVENTS,
};
/**
@ -51,13 +54,17 @@ int sp_index;
/**
* Parse and handles a command.
*/
struct cmd_results *handle_command(char *command);
struct cmd_results *handle_command(char *command, enum command_context context);
/**
* Parse and handles a command during config file loading.
*
* Do not use this under normal conditions.
*/
struct cmd_results *config_command(char *command, enum cmd_status block);
/*
* Parses a command policy rule.
*/
struct cmd_results *config_commands_command(char *exec);
/**
* Allocates a cmd_results object.
@ -93,6 +100,7 @@ sway_cmd cmd_client_unfocused;
sway_cmd cmd_client_urgent;
sway_cmd cmd_client_placeholder;
sway_cmd cmd_client_background;
sway_cmd cmd_commands;
sway_cmd cmd_debuglog;
sway_cmd cmd_exec;
sway_cmd cmd_exec_always;
@ -112,6 +120,7 @@ sway_cmd cmd_gaps;
sway_cmd cmd_hide_edge_borders;
sway_cmd cmd_include;
sway_cmd cmd_input;
sway_cmd cmd_ipc;
sway_cmd cmd_kill;
sway_cmd cmd_layout;
sway_cmd cmd_log_colors;
@ -122,6 +131,8 @@ sway_cmd cmd_new_float;
sway_cmd cmd_new_window;
sway_cmd cmd_orientation;
sway_cmd cmd_output;
sway_cmd cmd_permit;
sway_cmd cmd_reject;
sway_cmd cmd_reload;
sway_cmd cmd_resize;
sway_cmd cmd_scratchpad;
@ -182,4 +193,8 @@ sway_cmd input_cmd_pointer_accel;
sway_cmd input_cmd_scroll_method;
sway_cmd input_cmd_tap;
sway_cmd cmd_ipc_cmd;
sway_cmd cmd_ipc_events;
sway_cmd cmd_ipc_event_cmd;
#endif

View file

@ -103,9 +103,6 @@ struct pid_workspace {
time_t *time_added;
};
void pid_workspace_add(struct pid_workspace *pw);
void free_pid_workspace(struct pid_workspace *pw);
struct bar_config {
/**
* One of "dock", "hide", "invisible"
@ -184,6 +181,52 @@ enum edge_border_types {
E_BOTH /**< hide vertical and horizontal edge borders */
};
enum command_context {
CONTEXT_CONFIG = 1,
CONTEXT_BINDING = 2,
CONTEXT_IPC = 4,
CONTEXT_CRITERIA = 8,
CONTEXT_ALL = 0xFFFFFFFF,
};
struct command_policy {
char *command;
uint32_t context;
};
enum secure_feature {
FEATURE_LOCK = 1,
FEATURE_PANEL = 2,
FEATURE_BACKGROUND = 4,
FEATURE_SCREENSHOT = 8,
FEATURE_FULLSCREEN = 16,
FEATURE_KEYBOARD = 32,
FEATURE_MOUSE = 64,
FEATURE_IPC = 128,
};
struct feature_policy {
char *program;
uint32_t features;
};
enum ipc_feature {
IPC_FEATURE_COMMAND = 1,
IPC_FEATURE_GET_WORKSPACES = 2,
IPC_FEATURE_GET_OUTPUTS = 4,
IPC_FEATURE_GET_TREE = 8,
IPC_FEATURE_GET_MARKS = 16,
IPC_FEATURE_GET_BAR_CONFIG = 32,
IPC_FEATURE_GET_VERSION = 64,
IPC_FEATURE_GET_INPUTS = 128,
IPC_FEATURE_EVENT_WORKSPACE = 256,
IPC_FEATURE_EVENT_OUTPUT = 512,
IPC_FEATURE_EVENT_MODE = 1024,
IPC_FEATURE_EVENT_WINDOW = 2048,
IPC_FEATURE_EVENT_BINDING = 4096,
IPC_FEATURE_EVENT_INPUT = 8192
};
/**
* The configuration struct. The result of loading a config file.
*/
@ -252,8 +295,16 @@ struct sway_config {
int32_t floating_maximum_height;
int32_t floating_minimum_width;
int32_t floating_minimum_height;
// Security
list_t *command_policies;
list_t *feature_policies;
uint32_t ipc_policy;
};
void pid_workspace_add(struct pid_workspace *pw);
void free_pid_workspace(struct pid_workspace *pw);
/**
* Loads the main config from the given path. is_active should be true when
* reloading the config.

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

@ -0,0 +1,14 @@
#ifndef _SWAY_SECURITY_H
#define _SWAY_SECURITY_H
#include <unistd.h>
#include "sway/config.h"
enum secure_feature get_feature_policy(pid_t pid);
enum command_context get_command_policy(const char *cmd);
const char *command_policy_str(enum command_context context);
struct feature_policy *alloc_feature_policy(const char *program);
struct command_policy *alloc_command_policy(const char *command);
#endif

View file

@ -35,6 +35,7 @@ add_executable(sway
output.c
workspace.c
border.c
security.c
)
add_definitions(
@ -54,6 +55,7 @@ target_link_libraries(sway
${PANGO_LIBRARIES}
${JSONC_LIBRARIES}
m
cap
)
install(
@ -62,13 +64,34 @@ install(
DESTINATION bin
COMPONENT runtime
)
add_custom_target(configs ALL)
function(add_config name source destination)
add_custom_command(
OUTPUT ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${name}
COMMAND sed -r
's?__PREFIX__?${CMAKE_INSTALL_PREFIX}?g\; s?__SYSCONFDIR__?${CMAKE_INSTALL_FULL_SYSCONFDIR}?g'
${PROJECT_SOURCE_DIR}/${source}.in > ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${name}
DEPENDS ${PROJECT_SOURCE_DIR}/${source}.in
COMMENT "Generating config file ${source}"
)
install(
FILES ${PROJECT_SOURCE_DIR}/config
DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/sway/
FILES ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${name}
DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/${destination}
COMPONENT configuration
)
add_custom_target(config-${name} DEPENDS ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${name})
add_dependencies(configs config-${name})
endfunction()
add_config(config config sway)
add_config(security config.d/security sway/config.d)
add_manpage(sway 1)
add_manpage(sway 5)
add_manpage(sway-input 5)
add_manpage(sway-bar 5)
add_manpage(sway-security 7)

View file

@ -26,6 +26,7 @@
#include "sway/input_state.h"
#include "sway/criteria.h"
#include "sway/ipc-server.h"
#include "sway/security.h"
#include "sway/input.h"
#include "sway/border.h"
#include "stringop.h"
@ -158,6 +159,7 @@ static struct cmd_handler handlers[] = {
{ "client.placeholder", cmd_client_placeholder },
{ "client.unfocused", cmd_client_unfocused },
{ "client.urgent", cmd_client_urgent },
{ "commands", cmd_commands },
{ "debuglog", cmd_debuglog },
{ "default_orientation", cmd_orientation },
{ "exec", cmd_exec },
@ -178,6 +180,7 @@ static struct cmd_handler handlers[] = {
{ "hide_edge_borders", cmd_hide_edge_borders },
{ "include", cmd_include },
{ "input", cmd_input },
{ "ipc", cmd_ipc },
{ "kill", cmd_kill },
{ "layout", cmd_layout },
{ "log_colors", cmd_log_colors },
@ -187,6 +190,8 @@ static struct cmd_handler handlers[] = {
{ "new_float", cmd_new_float },
{ "new_window", cmd_new_window },
{ "output", cmd_output },
{ "permit", cmd_permit },
{ "reject", cmd_reject },
{ "reload", cmd_reload },
{ "resize", cmd_resize },
{ "scratchpad", cmd_scratchpad },
@ -288,6 +293,26 @@ static struct cmd_handler bar_colors_handlers[] = {
{ "urgent_workspace", bar_colors_cmd_urgent_workspace },
};
static struct cmd_handler ipc_handlers[] = {
{ "bar-config", cmd_ipc_cmd },
{ "command", cmd_ipc_cmd },
{ "events", cmd_ipc_events },
{ "inputs", cmd_ipc_cmd },
{ "marks", cmd_ipc_cmd },
{ "outputs", cmd_ipc_cmd },
{ "tree", cmd_ipc_cmd },
{ "workspaces", cmd_ipc_cmd },
};
static struct cmd_handler ipc_event_handlers[] = {
{ "binding", cmd_ipc_event_cmd },
{ "input", cmd_ipc_event_cmd },
{ "mode", cmd_ipc_event_cmd },
{ "output", cmd_ipc_event_cmd },
{ "window", cmd_ipc_event_cmd },
{ "workspace", cmd_ipc_event_cmd },
};
static int handler_compare(const void *_a, const void *_b) {
const struct cmd_handler *a = _a;
const struct cmd_handler *b = _b;
@ -307,10 +332,17 @@ static struct cmd_handler *find_handler(char *line, enum cmd_status block) {
sizeof(bar_colors_handlers) / sizeof(struct cmd_handler),
sizeof(struct cmd_handler), handler_compare);
} else if (block == CMD_BLOCK_INPUT) {
sway_log(L_DEBUG, "looking at input handlers");
res = bsearch(&d, input_handlers,
sizeof(input_handlers) / sizeof(struct cmd_handler),
sizeof(struct cmd_handler), handler_compare);
} else if (block == CMD_BLOCK_IPC) {
res = bsearch(&d, ipc_handlers,
sizeof(ipc_handlers) / sizeof(struct cmd_handler),
sizeof(struct cmd_handler), handler_compare);
} else if (block == CMD_BLOCK_IPC_EVENTS) {
res = bsearch(&d, ipc_event_handlers,
sizeof(ipc_event_handlers) / sizeof(struct cmd_handler),
sizeof(struct cmd_handler), handler_compare);
} else {
res = bsearch(&d, handlers,
sizeof(handlers) / sizeof(struct cmd_handler),
@ -319,7 +351,7 @@ static struct cmd_handler *find_handler(char *line, enum cmd_status block) {
return res;
}
struct cmd_results *handle_command(char *_exec) {
struct cmd_results *handle_command(char *_exec, enum command_context context) {
// Even though this function will process multiple commands we will only
// return the last error, if any (for now). (Since we have access to an
// error string we could e.g. concatonate all errors there.)
@ -393,6 +425,16 @@ struct cmd_results *handle_command(char *_exec) {
free_argv(argc, argv);
goto cleanup;
}
if (!(get_command_policy(argv[0]) & context)) {
if (results) {
free_cmd_results(results);
}
results = cmd_results_new(CMD_INVALID, cmd,
"Permission denied for %s via %s", cmd,
command_policy_str(context));
free_argv(argc, argv);
goto cleanup;
}
struct cmd_results *res = handler->handle(argc-1, argv+1);
if (res->status != CMD_SUCCESS) {
free_argv(argc, argv);
@ -458,6 +500,83 @@ struct cmd_results *config_command(char *exec, enum cmd_status block) {
} else {
results = cmd_results_new(CMD_INVALID, argv[0], "This command is shimmed, but unimplemented");
}
cleanup:
free_argv(argc, argv);
return results;
}
struct cmd_results *config_commands_command(char *exec) {
struct cmd_results *results = NULL;
int argc;
char **argv = split_args(exec, &argc);
if (!argc) {
results = cmd_results_new(CMD_SUCCESS, NULL, NULL);
goto cleanup;
}
// Find handler for the command this is setting a policy for
char *cmd = argv[0];
if (strcmp(cmd, "}") == 0) {
results = cmd_results_new(CMD_BLOCK_END, NULL, NULL);
goto cleanup;
}
struct cmd_handler *handler = find_handler(cmd, CMD_BLOCK_END);
if (!handler && strcmp(cmd, "*") != 0) {
char *input = cmd ? cmd : "(empty)";
results = cmd_results_new(CMD_INVALID, input, "Unknown/invalid command");
goto cleanup;
}
enum command_context context = 0;
struct {
char *name;
enum command_context context;
} context_names[] = {
{ "config", CONTEXT_CONFIG },
{ "binding", CONTEXT_BINDING },
{ "ipc", CONTEXT_IPC },
{ "criteria", CONTEXT_CRITERIA },
{ "all", CONTEXT_ALL },
};
for (int i = 1; i < argc; ++i) {
size_t j;
for (j = 0; j < sizeof(context_names) / sizeof(context_names[0]); ++j) {
if (strcmp(context_names[j].name, argv[i]) == 0) {
break;
}
}
if (j == sizeof(context_names) / sizeof(context_names[0])) {
results = cmd_results_new(CMD_INVALID, cmd,
"Invalid command context %s", argv[i]);
goto cleanup;
}
context |= context_names[j].context;
}
struct command_policy *policy = NULL;
for (int i = 0; i < config->command_policies->length; ++i) {
struct command_policy *p = config->command_policies->items[i];
if (strcmp(p->command, cmd) == 0) {
policy = p;
break;
}
}
if (!policy) {
policy = alloc_command_policy(cmd);
list_add(config->command_policies, policy);
}
policy->context = context;
sway_log(L_INFO, "Set command policy for %s to %d",
policy->command, policy->context);
results = cmd_results_new(CMD_SUCCESS, NULL, NULL);
cleanup:
free_argv(argc, argv);
return results;

23
sway/commands/commands.c Normal file
View file

@ -0,0 +1,23 @@
#include <stdbool.h>
#include <string.h>
#include "sway/commands.h"
#include "sway/config.h"
#include "list.h"
#include "log.h"
struct cmd_results *cmd_commands(int argc, char **argv) {
struct cmd_results *error = NULL;
if ((error = checkarg(argc, "commands", EXPECTED_EQUAL_TO, 1))) {
return error;
}
if (strcmp(argv[0], "{") != 0) {
return cmd_results_new(CMD_FAILURE, "commands", "Expected block declaration");
}
if (!config->reading) {
return cmd_results_new(CMD_FAILURE, "commands", "Can only be used in config file.");
}
return cmd_results_new(CMD_BLOCK_COMMANDS, NULL, NULL);
}

140
sway/commands/ipc.c Normal file
View file

@ -0,0 +1,140 @@
#include <stdio.h>
#include <string.h>
#include "sway/commands.h"
#include "sway/config.h"
#include "ipc.h"
#include "log.h"
#include "util.h"
struct cmd_results *cmd_ipc(int argc, char **argv) {
struct cmd_results *error = NULL;
if ((error = checkarg(argc, "ipc", EXPECTED_EQUAL_TO, 1))) {
return error;
}
if (config->reading && strcmp("{", argv[0]) != 0) {
return cmd_results_new(CMD_INVALID, "ipc",
"Expected '{' at start of IPC config definition.");
}
if (!config->reading) {
return cmd_results_new(CMD_FAILURE, "ipc", "Can only be used in config file.");
}
return cmd_results_new(CMD_BLOCK_IPC, NULL, NULL);
}
struct cmd_results *cmd_ipc_events(int argc, char **argv) {
struct cmd_results *error = NULL;
if ((error = checkarg(argc, "events", EXPECTED_EQUAL_TO, 1))) {
return error;
}
if (config->reading && strcmp("{", argv[0]) != 0) {
return cmd_results_new(CMD_INVALID, "events",
"Expected '{' at start of IPC event config definition.");
}
if (!config->reading) {
return cmd_results_new(CMD_FAILURE, "events", "Can only be used in config file.");
}
return cmd_results_new(CMD_BLOCK_IPC_EVENTS, NULL, NULL);
}
struct cmd_results *cmd_ipc_cmd(int argc, char **argv) {
struct cmd_results *error = NULL;
if ((error = checkarg(argc, "ipc", EXPECTED_EQUAL_TO, 1))) {
return error;
}
bool enabled;
if (strcmp(argv[0], "enabled") == 0) {
enabled = true;
} else if (strcmp(argv[0], "disabled") == 0) {
enabled = false;
} else {
return cmd_results_new(CMD_INVALID, argv[-1],
"Argument must be one of 'enabled' or 'disabled'");
}
struct {
char *name;
enum ipc_feature type;
} types[] = {
{ "command", IPC_FEATURE_COMMAND },
{ "workspaces", IPC_FEATURE_GET_WORKSPACES },
{ "outputs", IPC_FEATURE_GET_OUTPUTS },
{ "tree", IPC_FEATURE_GET_TREE },
{ "marks", IPC_FEATURE_GET_MARKS },
{ "bar-config", IPC_FEATURE_GET_BAR_CONFIG },
{ "inputs", IPC_FEATURE_GET_INPUTS },
};
uint32_t type = 0;
for (size_t i = 0; i < sizeof(types) / sizeof(types[0]); ++i) {
if (strcmp(types[i].name, argv[-1]) == 0) {
type = types[i].type;
break;
}
}
if (enabled) {
config->ipc_policy |= type;
sway_log(L_DEBUG, "Enabled IPC %s feature", argv[-1]);
} else {
config->ipc_policy &= ~type;
sway_log(L_DEBUG, "Disabled IPC %s feature", argv[-1]);
}
return cmd_results_new(CMD_SUCCESS, NULL, NULL);
}
struct cmd_results *cmd_ipc_event_cmd(int argc, char **argv) {
struct cmd_results *error = NULL;
if ((error = checkarg(argc, "ipc", EXPECTED_EQUAL_TO, 1))) {
return error;
}
bool enabled;
if (strcmp(argv[0], "enabled") == 0) {
enabled = true;
} else if (strcmp(argv[0], "disabled") == 0) {
enabled = false;
} else {
return cmd_results_new(CMD_INVALID, argv[-1],
"Argument must be one of 'enabled' or 'disabled'");
}
struct {
char *name;
enum ipc_feature type;
} types[] = {
{ "workspace", IPC_FEATURE_EVENT_WORKSPACE },
{ "output", IPC_FEATURE_EVENT_OUTPUT },
{ "mode", IPC_FEATURE_EVENT_MODE },
{ "window", IPC_FEATURE_EVENT_WINDOW },
{ "binding", IPC_FEATURE_EVENT_BINDING },
{ "input", IPC_FEATURE_EVENT_INPUT },
};
uint32_t type = 0;
for (size_t i = 0; i < sizeof(types) / sizeof(types[0]); ++i) {
if (strcmp(types[i].name, argv[-1]) == 0) {
type = types[i].type;
break;
}
}
if (enabled) {
config->ipc_policy |= type;
sway_log(L_DEBUG, "Enabled IPC %s event", argv[-1]);
} else {
config->ipc_policy &= ~type;
sway_log(L_DEBUG, "Disabled IPC %s event", argv[-1]);
}
return cmd_results_new(CMD_SUCCESS, NULL, NULL);
}

94
sway/commands/permit.c Normal file
View file

@ -0,0 +1,94 @@
#include <string.h>
#include "sway/commands.h"
#include "sway/config.h"
#include "sway/security.h"
#include "log.h"
static enum secure_feature get_features(int argc, char **argv,
struct cmd_results **error) {
enum secure_feature features = 0;
struct {
char *name;
enum secure_feature feature;
} feature_names[] = {
{ "lock", FEATURE_LOCK },
{ "panel", FEATURE_PANEL },
{ "background", FEATURE_BACKGROUND },
{ "screenshot", FEATURE_SCREENSHOT },
{ "fullscreen", FEATURE_FULLSCREEN },
{ "keyboard", FEATURE_KEYBOARD },
{ "mouse", FEATURE_MOUSE },
{ "ipc", FEATURE_IPC },
};
for (int i = 1; i < argc; ++i) {
size_t j;
for (j = 0; j < sizeof(feature_names) / sizeof(feature_names[0]); ++j) {
if (strcmp(feature_names[j].name, argv[i]) == 0) {
break;
}
}
if (j == sizeof(feature_names) / sizeof(feature_names[0])) {
*error = cmd_results_new(CMD_INVALID,
"permit", "Invalid feature grant %s", argv[i]);
return 0;
}
features |= feature_names[j].feature;
}
return features;
}
static struct feature_policy *get_policy(const char *name) {
struct feature_policy *policy = NULL;
for (int i = 0; i < config->feature_policies->length; ++i) {
struct feature_policy *p = config->feature_policies->items[i];
if (strcmp(p->program, name) == 0) {
policy = p;
break;
}
}
if (!policy) {
policy = alloc_feature_policy(name);
list_add(config->feature_policies, policy);
}
return policy;
}
struct cmd_results *cmd_permit(int argc, char **argv) {
struct cmd_results *error = NULL;
if ((error = checkarg(argc, "permit", EXPECTED_MORE_THAN, 1))) {
return error;
}
struct feature_policy *policy = get_policy(argv[0]);
policy->features |= get_features(argc, argv, &error);
if (error) {
return error;
}
sway_log(L_DEBUG, "Permissions granted to %s for features %d",
policy->program, policy->features);
return cmd_results_new(CMD_SUCCESS, NULL, NULL);
}
struct cmd_results *cmd_reject(int argc, char **argv) {
struct cmd_results *error = NULL;
if ((error = checkarg(argc, "reject", EXPECTED_MORE_THAN, 1))) {
return error;
}
struct feature_policy *policy = get_policy(argv[0]);
policy->features &= ~get_features(argc, argv, &error);
if (error) {
return error;
}
sway_log(L_DEBUG, "Permissions granted to %s for features %d",
policy->program, policy->features);
return cmd_results_new(CMD_SUCCESS, NULL, NULL);
}

View file

@ -167,6 +167,16 @@ void free_pid_workspace(struct pid_workspace *pw) {
free(pw);
}
void free_command_policy(struct command_policy *policy) {
free(policy->command);
free(policy);
}
void free_feature_policy(struct feature_policy *policy) {
free(policy->program);
free(policy);
}
void free_config(struct sway_config *config) {
int i;
for (i = 0; i < config->symbols->length; ++i) {
@ -211,6 +221,16 @@ void free_config(struct sway_config *config) {
}
list_free(config->output_configs);
for (i = 0; i < config->command_policies->length; ++i) {
free_command_policy(config->command_policies->items[i]);
}
list_free(config->command_policies);
for (i = 0; i < config->feature_policies->length; ++i) {
free_feature_policy(config->feature_policies->items[i]);
}
list_free(config->feature_policies);
list_free(config->active_bar_modifiers);
free_flat_list(config->config_chain);
free(config->font);
@ -321,6 +341,11 @@ static void config_defaults(struct sway_config *config) {
config->border_colors.placeholder.child_border = 0x0C0C0CFF;
config->border_colors.background = 0xFFFFFFFF;
// Security
config->command_policies = create_list();
config->feature_policies = create_list();
config->ipc_policy = UINT32_MAX;
}
static int compare_modifiers(const void *left, const void *right) {
@ -556,7 +581,13 @@ bool read_config(FILE *file, struct sway_config *config) {
free(line);
continue;
}
struct cmd_results *res = config_command(line, block);
struct cmd_results *res;
if (block == CMD_BLOCK_COMMANDS) {
// Special case
res = config_commands_command(line);
} else {
res = config_command(line, block);
}
switch(res->status) {
case CMD_FAILURE:
case CMD_INVALID:
@ -602,6 +633,30 @@ bool read_config(FILE *file, struct sway_config *config) {
}
break;
case CMD_BLOCK_COMMANDS:
if (block == CMD_BLOCK_END) {
block = CMD_BLOCK_COMMANDS;
} else {
sway_log(L_ERROR, "Invalid block '%s'", line);
}
break;
case CMD_BLOCK_IPC:
if (block == CMD_BLOCK_END) {
block = CMD_BLOCK_IPC;
} else {
sway_log(L_ERROR, "Invalid block '%s'", line);
}
break;
case CMD_BLOCK_IPC_EVENTS:
if (block == CMD_BLOCK_IPC) {
block = CMD_BLOCK_IPC_EVENTS;
} else {
sway_log(L_ERROR, "Invalid block '%s'", line);
}
break;
case CMD_BLOCK_END:
switch(block) {
case CMD_BLOCK_MODE:
@ -627,6 +682,21 @@ bool read_config(FILE *file, struct sway_config *config) {
block = CMD_BLOCK_BAR;
break;
case CMD_BLOCK_COMMANDS:
sway_log(L_DEBUG, "End of commands block");
block = CMD_BLOCK_END;
break;
case CMD_BLOCK_IPC:
sway_log(L_DEBUG, "End of IPC block");
block = CMD_BLOCK_END;
break;
case CMD_BLOCK_IPC_EVENTS:
sway_log(L_DEBUG, "End of IPC events block");
block = CMD_BLOCK_IPC;
break;
case CMD_BLOCK_END:
sway_log(L_ERROR, "Unmatched }");
break;

View file

@ -7,6 +7,7 @@
#include "sway/layout.h"
#include "sway/input_state.h"
#include "sway/extensions.h"
#include "sway/security.h"
#include "sway/ipc-server.h"
#include "log.h"
@ -68,6 +69,12 @@ void lock_surface_destructor(struct wl_resource *resource) {
static void set_background(struct wl_client *client, struct wl_resource *resource,
struct wl_resource *_output, struct wl_resource *surface) {
pid_t pid;
wl_client_get_credentials(client, &pid, NULL, NULL);
if (!(get_feature_policy(pid) & FEATURE_BACKGROUND)) {
sway_log(L_INFO, "Denying background feature to %d", pid);
return;
}
wlc_handle output = wlc_handle_from_wl_output_resource(_output);
if (!output) {
return;
@ -86,6 +93,12 @@ static void set_background(struct wl_client *client, struct wl_resource *resourc
static void set_panel(struct wl_client *client, struct wl_resource *resource,
struct wl_resource *_output, struct wl_resource *surface) {
pid_t pid;
wl_client_get_credentials(client, &pid, NULL, NULL);
if (!(get_feature_policy(pid) & FEATURE_PANEL)) {
sway_log(L_INFO, "Denying panel feature to %d", pid);
return;
}
wlc_handle output = wlc_handle_from_wl_output_resource(_output);
if (!output) {
return;
@ -111,6 +124,12 @@ static void desktop_unlock(struct wl_client *client, struct wl_resource *resourc
static void set_lock_surface(struct wl_client *client, struct wl_resource *resource,
struct wl_resource *_output, struct wl_resource *surface) {
pid_t pid;
wl_client_get_credentials(client, &pid, NULL, NULL);
if (!(get_feature_policy(pid) & FEATURE_LOCK)) {
sway_log(L_INFO, "Denying lock feature to %d", pid);
return;
}
swayc_t *output = swayc_by_handle(wlc_handle_from_wl_output_resource(_output));
swayc_t *view = swayc_by_handle(wlc_handle_from_wl_surface_resource(surface));
sway_log(L_DEBUG, "Setting lock surface to %p", view);
@ -155,6 +174,12 @@ static void desktop_ready(struct wl_client *client, struct wl_resource *resource
}
static void set_panel_position(struct wl_client *client, struct wl_resource *resource, uint32_t position) {
pid_t pid;
wl_client_get_credentials(client, &pid, NULL, NULL);
if (!(get_feature_policy(pid) & FEATURE_PANEL)) {
sway_log(L_INFO, "Denying panel feature to %d", pid);
return;
}
struct panel_config *config = find_or_create_panel_config(resource);
sway_log(L_DEBUG, "Panel position for wl_resource %p changed %d => %d", resource, config->panel_position, position);
config->panel_position = position;

View file

@ -21,6 +21,7 @@
#include "sway/criteria.h"
#include "sway/ipc-server.h"
#include "sway/input.h"
#include "sway/security.h"
#include "list.h"
#include "stringop.h"
#include "log.h"
@ -385,7 +386,7 @@ static bool handle_view_created(wlc_handle handle) {
struct criteria *crit = criteria->items[i];
sway_log(L_DEBUG, "for_window '%s' matches new view %p, cmd: '%s'",
crit->crit_raw, newview, crit->cmdlist);
struct cmd_results *res = handle_command(crit->cmdlist);
struct cmd_results *res = handle_command(crit->cmdlist, CONTEXT_CRITERIA);
if (res->status != CMD_SUCCESS) {
sway_log(L_ERROR, "Command '%s' failed: %s", res->input, res->error);
}
@ -516,8 +517,13 @@ static void handle_view_geometry_request(wlc_handle handle, const struct wlc_geo
static void handle_view_state_request(wlc_handle view, enum wlc_view_state_bit state, bool toggle) {
swayc_t *c = swayc_by_handle(view);
pid_t pid = wlc_view_get_pid(view);
switch (state) {
case WLC_BIT_FULLSCREEN:
if (!(get_feature_policy(pid) & FEATURE_FULLSCREEN)) {
sway_log(L_INFO, "Denying fullscreen to %d (%s)", pid, c->name);
break;
}
// i3 just lets it become fullscreen
wlc_view_set_state(view, state, toggle);
if (c) {
@ -579,7 +585,7 @@ static void handle_binding_command(struct sway_binding *binding) {
reload = true;
}
struct cmd_results *res = handle_command(binding->command);
struct cmd_results *res = handle_command(binding->command, CONTEXT_BINDING);
if (res->status != CMD_SUCCESS) {
sway_log(L_ERROR, "Command '%s' failed: %s", res->input, res->error);
}
@ -719,6 +725,14 @@ static bool handle_key(wlc_handle view, uint32_t time, const struct wlc_modifier
}
list_free(candidates);
swayc_t *focused = get_focused_container(&root_container);
if (focused->type == C_VIEW) {
pid_t pid = wlc_view_get_pid(focused->handle);
if (!(get_feature_policy(pid) & FEATURE_KEYBOARD)) {
return EVENT_HANDLED;
}
}
return EVENT_PASSTHROUGH;
}
@ -775,6 +789,15 @@ static bool handle_pointer_motion(wlc_handle handle, uint32_t time, const struct
}
pointer_position_set(&new_origin, false);
swayc_t *focused = get_focused_container(&root_container);
if (focused->type == C_VIEW) {
pid_t pid = wlc_view_get_pid(focused->handle);
if (!(get_feature_policy(pid) & FEATURE_MOUSE)) {
return EVENT_HANDLED;
}
}
return EVENT_PASSTHROUGH;
}
@ -842,6 +865,12 @@ static bool handle_pointer_button(wlc_handle view, uint32_t time, const struct w
// don't change focus or mode if fullscreen
if (swayc_is_fullscreen(focused)) {
if (focused->type == C_VIEW) {
pid_t pid = wlc_view_get_pid(focused->handle);
if (!(get_feature_policy(pid) & FEATURE_MOUSE)) {
return EVENT_HANDLED;
}
}
return EVENT_PASSTHROUGH;
}
@ -884,6 +913,13 @@ static bool handle_pointer_button(wlc_handle view, uint32_t time, const struct w
return EVENT_HANDLED;
}
if (focused->type == C_VIEW) {
pid_t pid = wlc_view_get_pid(focused->handle);
if (!(get_feature_policy(pid) & FEATURE_MOUSE)) {
return EVENT_HANDLED;
}
}
// Always send mouse release
if (state == WLC_BUTTON_STATE_RELEASED) {
return EVENT_PASSTHROUGH;
@ -900,18 +936,18 @@ bool handle_pointer_scroll(wlc_handle view, uint32_t time, const struct wlc_modi
int y_amount = (int)_amount[1];
if (x_amount > 0 && strcmp(config->floating_scroll_up_cmd, "")) {
handle_command(config->floating_scroll_up_cmd);
handle_command(config->floating_scroll_up_cmd, CONTEXT_BINDING);
return EVENT_HANDLED;
} else if (x_amount < 0 && strcmp(config->floating_scroll_down_cmd, "")) {
handle_command(config->floating_scroll_down_cmd);
handle_command(config->floating_scroll_down_cmd, CONTEXT_BINDING);
return EVENT_HANDLED;
}
if (y_amount > 0 && strcmp(config->floating_scroll_right_cmd, "")) {
handle_command(config->floating_scroll_right_cmd);
handle_command(config->floating_scroll_right_cmd, CONTEXT_BINDING);
return EVENT_HANDLED;
} else if (y_amount < 0 && strcmp(config->floating_scroll_left_cmd, "")) {
handle_command(config->floating_scroll_left_cmd);
handle_command(config->floating_scroll_left_cmd, CONTEXT_BINDING);
return EVENT_HANDLED;
}
}
@ -924,7 +960,7 @@ static void handle_wlc_ready(void) {
config->active = true;
while (config->cmd_queue->length) {
char *line = config->cmd_queue->items[0];
struct cmd_results *res = handle_command(line);
struct cmd_results *res = handle_command(line, CONTEXT_CONFIG);
if (res->status != CMD_SUCCESS) {
sway_log(L_ERROR, "Error on line '%s': %s", line, res->error);
}

View file

@ -15,6 +15,7 @@
#include <libinput.h>
#include "sway/ipc-json.h"
#include "sway/ipc-server.h"
#include "sway/security.h"
#include "sway/config.h"
#include "sway/commands.h"
#include "sway/input.h"
@ -55,8 +56,6 @@ bool ipc_send_reply(struct ipc_client *client, const char *payload, uint32_t pay
void ipc_get_workspaces_callback(swayc_t *workspace, void *data);
void ipc_get_outputs_callback(swayc_t *container, void *data);
#define event_mask(ev) (1 << (ev & 0x7F))
void ipc_init(void) {
ipc_socket = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
if (ipc_socket == -1) {
@ -126,6 +125,17 @@ struct sockaddr_un *ipc_user_sockaddr(void) {
return ipc_sockaddr;
}
static pid_t get_client_pid(int client_fd) {
struct ucred ucred;
socklen_t len = sizeof(struct ucred);
if (getsockopt(client_fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == -1) {
return -1;
}
return ucred.pid;
}
int ipc_handle_connection(int fd, uint32_t mask, void *data) {
(void) fd; (void) data;
sway_log(L_DEBUG, "Event on IPC listening socket");
@ -144,6 +154,15 @@ int ipc_handle_connection(int fd, uint32_t mask, void *data) {
return 0;
}
pid_t pid = get_client_pid(client_fd);
if (!(get_feature_policy(pid) & FEATURE_IPC)) {
sway_log(L_INFO, "Permission to connect to IPC socket denied to %d", pid);
const char *error = "{\"success\": false, \"message\": \"Permission denied\"}";
write(client_fd, &error, sizeof(error));
close(client_fd);
return 0;
}
struct ipc_client* client = malloc(sizeof(struct ipc_client));
client->payload_length = 0;
client->fd = client_fd;
@ -309,10 +328,15 @@ void ipc_client_handle_command(struct ipc_client *client) {
}
buf[client->payload_length] = '\0';
const char *error_denied = "{ \"success\": false, \"error\": \"Permission denied\" }";
switch (client->current_command) {
case IPC_COMMAND:
{
struct cmd_results *results = handle_command(buf);
if (!(config->ipc_policy & IPC_FEATURE_COMMAND)) {
goto exit_denied;
}
struct cmd_results *results = handle_command(buf, CONTEXT_IPC);
const char *json = cmd_results_to_json(results);
char reply[256];
int length = snprintf(reply, sizeof(reply), "%s", json);
@ -343,10 +367,8 @@ void ipc_client_handle_command(struct ipc_client *client) {
client->subscribed_events |= event_mask(IPC_EVENT_WINDOW);
} else if (strcmp(event_type, "modifier") == 0) {
client->subscribed_events |= event_mask(IPC_EVENT_MODIFIER);
#if SWAY_BINDING_EVENT
} else if (strcmp(event_type, "binding") == 0) {
client->subscribed_events |= event_mask(IPC_EVENT_BINDING);
#endif
} else {
ipc_send_reply(client, "{\"success\": false}", 18);
json_object_put(request);
@ -363,6 +385,9 @@ void ipc_client_handle_command(struct ipc_client *client) {
case IPC_GET_WORKSPACES:
{
if (!(config->ipc_policy & IPC_FEATURE_GET_WORKSPACES)) {
goto exit_denied;
}
json_object *workspaces = json_object_new_array();
container_map(&root_container, ipc_get_workspaces_callback, workspaces);
const char *json_string = json_object_to_json_string(workspaces);
@ -373,6 +398,9 @@ void ipc_client_handle_command(struct ipc_client *client) {
case IPC_GET_INPUTS:
{
if (!(config->ipc_policy & IPC_FEATURE_GET_INPUTS)) {
goto exit_denied;
}
json_object *inputs = json_object_new_array();
if (input_devices) {
for(int i=0; i<input_devices->length; i++) {
@ -392,6 +420,9 @@ void ipc_client_handle_command(struct ipc_client *client) {
case IPC_GET_OUTPUTS:
{
if (!(config->ipc_policy & IPC_FEATURE_GET_OUTPUTS)) {
goto exit_denied;
}
json_object *outputs = json_object_new_array();
container_map(&root_container, ipc_get_outputs_callback, outputs);
const char *json_string = json_object_to_json_string(outputs);
@ -402,6 +433,9 @@ void ipc_client_handle_command(struct ipc_client *client) {
case IPC_GET_TREE:
{
if (!(config->ipc_policy & IPC_FEATURE_GET_TREE)) {
goto exit_denied;
}
json_object *tree = ipc_json_describe_container_recursive(&root_container);
const char *json_string = json_object_to_json_string(tree);
ipc_send_reply(client, json_string, (uint32_t) strlen(json_string));
@ -462,6 +496,9 @@ void ipc_client_handle_command(struct ipc_client *client) {
case IPC_GET_BAR_CONFIG:
{
if (!(config->ipc_policy & IPC_FEATURE_GET_BAR_CONFIG)) {
goto exit_denied;
}
if (!buf[0]) {
// Send list of configured bar IDs
json_object *bars = json_object_new_array();
@ -502,6 +539,9 @@ void ipc_client_handle_command(struct ipc_client *client) {
goto exit_cleanup;
}
exit_denied:
ipc_send_reply(client, error_denied, (uint32_t)strlen(error_denied));
exit_cleanup:
client->payload_length = 0;
free(buf);
@ -566,6 +606,9 @@ void ipc_send_event(const char *json_string, enum ipc_command_type event) {
}
void ipc_event_workspace(swayc_t *old, swayc_t *new, const char *change) {
if (!(config->ipc_policy & IPC_FEATURE_EVENT_WORKSPACE)) {
return;
}
sway_log(L_DEBUG, "Sending workspace::%s event", change);
json_object *obj = json_object_new_object();
json_object_object_add(obj, "change", json_object_new_string(change));
@ -590,6 +633,9 @@ void ipc_event_workspace(swayc_t *old, swayc_t *new, const char *change) {
}
void ipc_event_window(swayc_t *window, const char *change) {
if (!(config->ipc_policy & IPC_FEATURE_EVENT_WINDOW)) {
return;
}
sway_log(L_DEBUG, "Sending window::%s event", change);
json_object *obj = json_object_new_object();
json_object_object_add(obj, "change", json_object_new_string(change));
@ -615,6 +661,9 @@ void ipc_event_barconfig_update(struct bar_config *bar) {
}
void ipc_event_mode(const char *mode) {
if (!(config->ipc_policy & IPC_FEATURE_EVENT_MODE)) {
return;
}
sway_log(L_DEBUG, "Sending mode::%s event", mode);
json_object *obj = json_object_new_object();
json_object_object_add(obj, "change", json_object_new_string(mode));
@ -639,8 +688,10 @@ void ipc_event_modifier(uint32_t modifier, const char *state) {
json_object_put(obj); // free
}
#if SWAY_BINDING_EVENT
static void ipc_event_binding(json_object *sb_obj) {
if (!(config->ipc_policy & IPC_FEATURE_EVENT_BINDING)) {
return;
}
sway_log(L_DEBUG, "Sending binding::run event");
json_object *obj = json_object_new_object();
json_object_object_add(obj, "change", json_object_new_string("run"));
@ -651,10 +702,8 @@ static void ipc_event_binding(json_object *sb_obj) {
json_object_put(obj); // free
}
#endif
void ipc_event_binding_keyboard(struct sway_binding *sb) {
#if SWAY_BINDING_EVENT
json_object *sb_obj = json_object_new_object();
json_object_object_add(sb_obj, "command", json_object_new_string(sb->command));
@ -705,5 +754,4 @@ void ipc_event_binding_keyboard(struct sway_binding *sb) {
json_object_object_add(sb_obj, "input_type", json_object_new_string("keyboard"));
ipc_event_binding(sb_obj);
#endif
}

View file

@ -4,13 +4,16 @@
#include <wlc/wlc.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <signal.h>
#include <unistd.h>
#include <getopt.h>
#include <sys/capability.h>
#include "sway/extensions.h"
#include "sway/layout.h"
#include "sway/config.h"
#include "sway/security.h"
#include "sway/handlers.h"
#include "sway/input.h"
#include "sway/ipc-server.h"
@ -142,6 +145,63 @@ static void log_kernel() {
fclose(f);
}
static void security_sanity_check() {
// TODO: Notify users visually if this has issues
struct stat s;
if (stat("/proc", &s)) {
sway_log(L_ERROR,
"!! DANGER !! /proc is not available - sway CANNOT enforce security rules!");
}
cap_flag_value_t v;
cap_t cap = cap_get_proc();
if (!cap || cap_get_flag(cap, CAP_SYS_PTRACE, CAP_PERMITTED, &v) != 0 || v != CAP_SET) {
sway_log(L_ERROR,
"!! DANGER !! Sway does not have CAP_SYS_PTRACE and cannot enforce security rules for processes running as other users.");
}
if (cap) {
cap_free(cap);
}
if (!stat(SYSCONFDIR "/sway", &s)) {
if (s.st_uid != 0 || s.st_gid != 0
|| (s.st_mode & S_IWGRP) || (s.st_mode & S_IWOTH)) {
sway_log(L_ERROR,
"!! DANGER !! " SYSCONFDIR "/sway is not secure! It should be owned by root and set to 0755 at the minimum");
}
}
struct {
char *command;
enum command_context context;
bool checked;
} expected[] = {
{ "reload", CONTEXT_BINDING, false },
{ "restart", CONTEXT_BINDING, false },
{ "permit", CONTEXT_CONFIG, false },
{ "reject", CONTEXT_CONFIG, false },
{ "ipc", CONTEXT_CONFIG, false },
};
int expected_len = 5;
for (int i = 0; i < config->command_policies->length; ++i) {
struct command_policy *policy = config->command_policies->items[i];
for (int j = 0; j < expected_len; ++j) {
if (strcmp(expected[j].command, policy->command) == 0) {
expected[j].checked = true;
if (expected[j].context != policy->context) {
sway_log(L_ERROR,
"!! DANGER !! Command security policy for %s should be set to %s",
expected[j].command, command_policy_str(expected[j].context));
}
}
}
}
for (int j = 0; j < expected_len; ++j) {
if (!expected[j].checked) {
sway_log(L_ERROR,
"!! DANGER !! Command security policy for %s should be set to %s",
expected[j].command, command_policy_str(expected[j].context));
}
}
}
int main(int argc, char **argv) {
static int verbose = 0, debug = 0, validate = 0;
@ -170,6 +230,10 @@ int main(int argc, char **argv) {
" --get-socketpath Gets the IPC socket path and prints it, then exits.\n"
"\n";
// Security:
unsetenv("LD_PRELOAD");
setenv("LD_LIBRARY_PATH", _LD_LIBRARY_PATH, 1);
int c;
while (1) {
int option_index = 0;
@ -298,6 +362,8 @@ int main(int argc, char **argv) {
free(config_path);
}
security_sanity_check();
if (!terminate_request) {
wlc_run();
}

94
sway/security.c Normal file
View file

@ -0,0 +1,94 @@
#include <unistd.h>
#include <stdio.h>
#include "sway/config.h"
#include "sway/security.h"
#include "log.h"
struct feature_policy *alloc_feature_policy(const char *program) {
uint32_t default_policy = 0;
for (int i = 0; i < config->feature_policies->length; ++i) {
struct feature_policy *policy = config->feature_policies->items[i];
if (strcmp(policy->program, "*") == 0) {
default_policy = policy->features;
break;
}
}
struct feature_policy *policy = malloc(sizeof(struct feature_policy));
policy->program = strdup(program);
policy->features = default_policy;
return policy;
}
struct command_policy *alloc_command_policy(const char *command) {
struct command_policy *policy = malloc(sizeof(struct command_policy));
policy->command = strdup(command);
policy->context = 0;
return policy;
}
enum secure_feature get_feature_policy(pid_t pid) {
const char *fmt = "/proc/%d/exe";
int pathlen = snprintf(NULL, 0, fmt, pid);
char *path = malloc(pathlen + 1);
snprintf(path, pathlen + 1, fmt, pid);
static char link[2048];
uint32_t default_policy = 0;
ssize_t len = readlink(path, link, sizeof(link));
if (len < 0) {
sway_log(L_INFO,
"WARNING: unable to read %s for security check. Using default policy.",
path);
strcpy(link, "*");
} else {
link[len] = '\0';
}
free(path);
for (int i = 0; i < config->feature_policies->length; ++i) {
struct feature_policy *policy = config->feature_policies->items[i];
if (strcmp(policy->program, "*") == 0) {
default_policy = policy->features;
}
if (strcmp(policy->program, link) == 0) {
return policy->features;
}
}
return default_policy;
}
enum command_context get_command_policy(const char *cmd) {
uint32_t default_policy = 0;
for (int i = 0; i < config->command_policies->length; ++i) {
struct command_policy *policy = config->command_policies->items[i];
if (strcmp(policy->command, "*") == 0) {
default_policy = policy->context;
}
if (strcmp(policy->command, cmd) == 0) {
return policy->context;
}
}
return default_policy;
}
const char *command_policy_str(enum command_context context) {
switch (context) {
case CONTEXT_ALL:
return "all";
case CONTEXT_CONFIG:
return "config";
case CONTEXT_BINDING:
return "binding";
case CONTEXT_IPC:
return "IPC";
case CONTEXT_CRITERIA:
return "criteria";
default:
return "unknown";
}
}

250
sway/sway-security.7.txt Normal file
View file

@ -0,0 +1,250 @@
/////
vim:set ts=4 sw=4 tw=82 noet:
/////
sway-security (7)
=================
Name
----
sway-security - Guidelines for securing your sway install
Security Overview
-----------------
**Sway is NOT secure**. We are working on it but do not trust that we have it all
figured out yet. The following man page is provisional.
Securing sway requires careful configuration of your environment, the sort that's
usually best suited to a distro maintainer who wants to ship a secure sway
environment in their distro. Sway provides a number of means of securing it but
you must make a few changes external to sway first.
Configuration security
----------------------
Many of Sway's security features are configurable. It's important that a possibly
untrusted program is not able to edit this. Security rules are kept in
_/etc/sway/config.d/security_ (usually), which should only be writable by root.
However, configuration of security rules is not limited to this file - any config
file that sway loads (including i.e. _~/.config/sway/config_) should not be editable
by the user you intend to run programs as. One simple strategy is to use
/etc/sway/config instead of a config file in your home directory, but that doesn't
work well for multi-user systems. A more robust strategy is to run untrusted
programs as another user, or in a sandbox. Configuring this is up to you.
Note that _/etc/sway/config.d/*_ must be included explicitly from your config file.
This is done by default in /etc/sway/config but you must check your own config if
you choose to place it in other locations.
Environment security
--------------------
LD_PRELOAD is a mechanism designed to ruin the security of your system. There are
a number of strategies for dealing with this but they all suck a little. In order
of most practical to least practical:
1. Only run important programs via exec. Sway's exec command will ensure that
LD_PRELOAD is unset when running programs.
2. Remove LD_PRELOAD support from your dynamic loader (requires patching libc).
This may break programs that rely on LD_PRELOAD for legitimate functionality,
but this is the most effective solution.
3. Use static linking for important programs. Of course statically linked programs
are unaffected by the dynamic linking security dumpster fire.
Note that should you choose method 1, you MUST ensure that sway itself isn't
compromised by LD_PRELOAD. It probably isn't, but you can be sure by setting
/usr/bin/sway to a+s (setuid), which will instruct the dynamic linker not to
permit LD_PRELOAD for it (and will also run it as root, which sway will shortly
drop). You could also statically link sway itself.
Note that LD_LIBRARY_PATH has all of the same problems, and all of the same
solutions.
Read your log
-------------
Sway does sanity checks and prints big red warnings to stderr if they fail. Read
them.
Feature policies
----------------
Certain sway features are security sensitive and may be configured with security
policies. These features are:
**background**::
Permission for a program to become the background.
**fullscreen**::
Permission to become fullscreen. Note that users can always make a window
fullscreen themselves with the fullscreen command.
**ipc**::
Permission to connect to sway's IPC socket.
**keyboard**::
Permission to receive keyboard events (only while they are focused).
**lock**::
Permission for a program to act as a screen locker. This involves becoming
fullscreen (on all outputs) and receiving _all_ keyboard and mouse input for
the duration of the process.
**mouse**::
Permission to receive mouse events (only while the mouse is over them).
**panel**::
Permission for a program to stick its windows to the sides of the screen.
**screenshot**::
Permission to take screenshots or record the screen.
By default, all programs are granted **fullscreen**, **keyboard**, **mouse**, and
**ipc** permissions. You can use the following config commands to control a
program's access:
**permit** <executable> <features...>::
Permits <executable> to use <features> (each feature seperated by a space).
<executable> may be * to affect the default policy, or the full path to the
executable file.
**reject** <executable> <features...>::
Disallows <executable> from using <features> (each feature seperated by a space).
<executable> may be * to affect the default policy, or the full path to the
executable file.
Note that policy enforcement requires procfs to be mounted at /proc and the sway
process to be able to access _/proc/[pid]/exe_ (see **procfs(5)** for details on
this access - setcap cap_sys_ptrace=eip /usr/bin/sway should do the trick). If
sway is unable to read _/proc/[pid]/exe_, it will apply the default policy.
To work correctly, sway's own programs require the following permissions:
- swaybg: background
- swaylock: lock, keyboard
- swaybar: panel, mouse, ipc
- swaygrab: screenshot, ipc
When you first declare a policy for an executable, it will inherit the default
policy. Further changes to the default policy will not retroactively affect which
permissions an earlier policy inherits. You must explicitly reject any features
from the default policy that you do not want an executable to receive permission
for.
Command policies
----------------
You can also control the context from which a command may execute. The different
contexts you can control are:
**config**::
Can be run from your config file.
**binding**::
Can be run from bindsym or bindcode commands.
**ipc**::
Can be run by IPC clients.
**criteria**::
Can be run when evaluating window criteria.
**all**::
Shorthand for granting permission in all contexts.
By default a command is allowed to execute in any context. To configure this, open
a commands block and fill it with policies:
commands {
<name> <contexts...>
...
}
For example, you could do this to limit the use of the focus command to just
binding and critiera:
commands {
focus binding criteria
}
Setting a command policy overwrites any previous policy that was in place.
IPC policies
------------
You may whitelist IPC access like so:
permit /usr/bin/swaybar ipc
permit /usr/bin/swaygrab ipc
# etc
Note that it's suggested you do not enable swaymsg to access IPC if you intend to
secure your IPC socket, because any program could just run swaymsg itself instead
of connecting to IPC directly.
You can also configure which features of IPC are available with an IPC block:
ipc {
...
}
The following commands are available within this block:
**bar-config** <enabled|disabled>::
Controls GET_BAR_CONFIG (required for swaybar to work at all).
**command** <enabled|disabled>::
Controls executing sway commands via IPC.
**inputs** <enabled|disabled>::
Controls GET_INPUTS (input device information).
**marks** <enabled|disabled>::
Controls GET_MARKS.
**outputs** <enabled|disabled>::
Controls GET_OUTPUTS.
**tree** <enabled|disabled>::
Controls GET_TREE.
**workspaces** <enabled|disabled>::
Controls GET_WORKSPACES.
You can also control which IPC events can be raised with an events block:
ipc {
events {
...
}
}
The following commands are vaild within an ipc events block:
**binding** <enabled|disabled>::
Controls keybinding notifications (disabled by default).
**input** <enabled|disabled>::
Controls input device hotplugging notifications.
**mode** <enabled|disabled>::
Controls output hotplugging notifications.
**output** <enabled|disabled>::
Controls output hotplugging notifications.
**window** <enabled|disabled>::
Controls window event notifications.
**workspace** <enabled|disabled>::
Controls workspace notifications.
Disabling some of these may cause swaybar to behave incorrectly.
Authors
-------
Maintained by Drew DeVault <sir@cmpwn.com>, who is assisted by other open
source contributors. For more information about sway development, see
<https://github.com/SirCmpwn/sway>.

View file

@ -42,7 +42,7 @@ install(
install(
FILES ${CMAKE_CURRENT_SOURCE_DIR}/pam/swaylock
DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/pam.d/
DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/pam.d/
COMPONENT data
)