aboutsummaryrefslogtreecommitdiff
path: root/sway
diff options
context:
space:
mode:
authorDrew DeVault <sir@cmpwn.com>2016-12-04 08:30:40 -0500
committerGitHub <noreply@github.com>2016-12-04 08:30:40 -0500
commit5778c59a2f302071fd781683db57a97b51396c87 (patch)
treee0ec272832e88e6c8d92719efa70c6749452daff /sway
parentcd5694fdb5bc9beb575902ea57d037833ad8e85c (diff)
parente7a764fdf450a8259ddbc17446dd720fa1157b44 (diff)
Merge pull request #981 from SirCmpwn/security
Security features
Diffstat (limited to 'sway')
-rw-r--r--sway/CMakeLists.txt33
-rw-r--r--sway/commands.c125
-rw-r--r--sway/commands/commands.c23
-rw-r--r--sway/commands/ipc.c140
-rw-r--r--sway/commands/permit.c94
-rw-r--r--sway/config.c72
-rw-r--r--sway/extensions.c25
-rw-r--r--sway/handlers.c50
-rw-r--r--sway/ipc-server.c66
-rw-r--r--sway/main.c66
-rw-r--r--sway/security.c94
-rw-r--r--sway/sway-security.7.txt250
12 files changed, 1013 insertions, 25 deletions
diff --git a/sway/CMakeLists.txt b/sway/CMakeLists.txt
index bb9ea81f..d1afadb6 100644
--- a/sway/CMakeLists.txt
+++ b/sway/CMakeLists.txt
@@ -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
)
-install(
- FILES ${PROJECT_SOURCE_DIR}/config
- DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/sway/
- COMPONENT configuration
-)
+
+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 ${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)
diff --git a/sway/commands.c b/sway/commands.c
index de29a7af..d87d0084 100644
--- a/sway/commands.c
+++ b/sway/commands.c
@@ -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,7 +500,84 @@ 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:
+
+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;
}
diff --git a/sway/commands/commands.c b/sway/commands/commands.c
new file mode 100644
index 00000000..5d248e30
--- /dev/null
+++ b/sway/commands/commands.c
@@ -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);
+}
diff --git a/sway/commands/ipc.c b/sway/commands/ipc.c
new file mode 100644
index 00000000..222be0dd
--- /dev/null
+++ b/sway/commands/ipc.c
@@ -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);
+}
diff --git a/sway/commands/permit.c b/sway/commands/permit.c
new file mode 100644
index 00000000..7a25e4ce
--- /dev/null
+++ b/sway/commands/permit.c
@@ -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);
+}
diff --git a/sway/config.c b/sway/config.c
index 7d5999d8..e737f83c 100644
--- a/sway/config.c
+++ b/sway/config.c
@@ -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;
diff --git a/sway/extensions.c b/sway/extensions.c
index 60cd8d41..96c7e60d 100644
--- a/sway/extensions.c
+++ b/sway/extensions.c
@@ -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;
diff --git a/sway/handlers.c b/sway/handlers.c
index 2235bc8b..ee52ba38 100644
--- a/sway/handlers.c
+++ b/sway/handlers.c
@@ -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);
}
diff --git a/sway/ipc-server.c b/sway/ipc-server.c
index ebb5ce58..c04c465a 100644
--- a/sway/ipc-server.c
+++ b/sway/ipc-server.c
@@ -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
}
diff --git a/sway/main.c b/sway/main.c
index a040cec9..73c4b5f2 100644
--- a/sway/main.c
+++ b/sway/main.c
@@ -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();
}
diff --git a/sway/security.c b/sway/security.c
new file mode 100644
index 00000000..f16fdd1f
--- /dev/null
+++ b/sway/security.c
@@ -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";
+ }
+}
diff --git a/sway/sway-security.7.txt b/sway/sway-security.7.txt
new file mode 100644
index 00000000..9a2581b1
--- /dev/null
+++ b/sway/sway-security.7.txt
@@ -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>.