From 5831f7ab68a7166a492812d6301868541fdc9ae3 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Thu, 1 Dec 2016 19:27:35 -0500 Subject: [PATCH 01/31] Write example security config, start on code --- config.d/security | 52 +++++++++++++++++++++++++++++++++++++++++++++++ sway/main.c | 23 +++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 config.d/security diff --git a/config.d/security b/config.d/security new file mode 100644 index 000000000..bff55f0f8 --- /dev/null +++ b/config.d/security @@ -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 $PREFIX/swaylock lock +permit $PREFIX/swaybar panel +permit $PREFIX/swaybg background +permit $PREFIX/swaygrab screenshot + +permit * fullscreen keyboard mouse + +# 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 + bar-config enabled + binding enabled + modifier enabled + input enabled + } +} + +# Limits the contexts from which certain commands are permitted +commands { + fullscreen bindsym criteria + bindsym config + exit bindsym + kill bindsym + + # You should not change these unless you know what you're doing - it could + # cripple your security + reload bindsym + restart bindsym + permit config + reject config + ipc config +} diff --git a/sway/main.c b/sway/main.c index a040cec92..4704f9005 100644 --- a/sway/main.c +++ b/sway/main.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -142,6 +143,27 @@ static void log_kernel() { fclose(f); } +static void security_sanity_check() { + // TODO: Notify users visually if this has issues + struct stat s = {0}; + if (stat("/proc", &s)) { + sway_log(L_ERROR, + "!! DANGER !! /proc is not available - sway CANNOT enforce security rules!"); + } + if (!stat(SYSCONFDIR "/sway", &s)) { + if (s.st_uid != 0 || s.st_gid != 0 || s.st_mode != 00755) { + sway_log(L_ERROR, + "!! DANGER !! " SYSCONFDIR "/sway is not secure! It should be owned by root and set to 0755"); + } + } + // TODO: check that these command policies are set + // reload bindsym + // restart bindsym + // permit config + // reject config + // ipc config +} + int main(int argc, char **argv) { static int verbose = 0, debug = 0, validate = 0; @@ -256,6 +278,7 @@ int main(int argc, char **argv) { } wlc_log_set_handler(wlc_log_handler); detect_proprietary(); + security_sanity_check(); input_devices = create_list(); From 44cc0ef125332f1fe3dad7d16ed0a78a25cd1974 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Thu, 1 Dec 2016 19:38:36 -0500 Subject: [PATCH 02/31] Add config related code and initial headers --- config.d/security | 10 +++++----- include/security.h | 9 +++++++++ include/sway/config.h | 39 ++++++++++++++++++++++++++++++++++++--- sway/config.c | 24 ++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 8 deletions(-) create mode 100644 include/security.h diff --git a/config.d/security b/config.d/security index bff55f0f8..fe75d8ea5 100644 --- a/config.d/security +++ b/config.d/security @@ -37,15 +37,15 @@ ipc { # Limits the contexts from which certain commands are permitted commands { - fullscreen bindsym criteria + fullscreen binding criteria bindsym config - exit bindsym - kill bindsym + exit binding + kill binding # You should not change these unless you know what you're doing - it could # cripple your security - reload bindsym - restart bindsym + reload binding + restart binding permit config reject config ipc config diff --git a/include/security.h b/include/security.h new file mode 100644 index 000000000..efc25ce66 --- /dev/null +++ b/include/security.h @@ -0,0 +1,9 @@ +#ifndef _SWAY_SECURITY_H +#define _SWAY_SECURITY_H +#include +#include "sway/config.h" + +const struct feature_permissions *get_permissions(pid_t pid); +enum command_context get_command_context(const char *cmd); + +#endif diff --git a/include/sway/config.h b/include/sway/config.h index 8d077ee74..3744386cf 100644 --- a/include/sway/config.h +++ b/include/sway/config.h @@ -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,35 @@ 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; + enum command_context 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, +}; + +struct feature_policy { + char *program; + bool permit; + enum secure_feature features; +}; + /** * The configuration struct. The result of loading a config file. */ @@ -252,8 +278,15 @@ 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; }; +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. diff --git a/sway/config.c b/sway/config.c index 7d5999d8d..a2f6a7282 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,10 @@ 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(); } static int compare_modifiers(const void *left, const void *right) { From 26752932003145c89a0cd8d39c9944d6f5917837 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Thu, 1 Dec 2016 19:58:11 -0500 Subject: [PATCH 03/31] Implement policy lookups --- include/security.h | 4 +-- include/sway/config.h | 1 - include/sway/security.h | 9 +++++++ sway/CMakeLists.txt | 1 + sway/security.c | 54 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 include/sway/security.h create mode 100644 sway/security.c diff --git a/include/security.h b/include/security.h index efc25ce66..3a5dbca07 100644 --- a/include/security.h +++ b/include/security.h @@ -3,7 +3,7 @@ #include #include "sway/config.h" -const struct feature_permissions *get_permissions(pid_t pid); -enum command_context get_command_context(const char *cmd); +enum secure_features get_feature_policy(pid_t pid); +enum command_context get_command_policy(const char *cmd); #endif diff --git a/include/sway/config.h b/include/sway/config.h index 3744386cf..14a86e499 100644 --- a/include/sway/config.h +++ b/include/sway/config.h @@ -206,7 +206,6 @@ enum secure_feature { struct feature_policy { char *program; - bool permit; enum secure_feature features; }; diff --git a/include/sway/security.h b/include/sway/security.h new file mode 100644 index 000000000..efc25ce66 --- /dev/null +++ b/include/sway/security.h @@ -0,0 +1,9 @@ +#ifndef _SWAY_SECURITY_H +#define _SWAY_SECURITY_H +#include +#include "sway/config.h" + +const struct feature_permissions *get_permissions(pid_t pid); +enum command_context get_command_context(const char *cmd); + +#endif diff --git a/sway/CMakeLists.txt b/sway/CMakeLists.txt index bb9ea81ff..9349c30d7 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( diff --git a/sway/security.c b/sway/security.c new file mode 100644 index 000000000..c72d54f63 --- /dev/null +++ b/sway/security.c @@ -0,0 +1,54 @@ +#include +#include +#include "sway/config.h" +#include "sway/security.h" +#include "log.h" + +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]; + + enum secure_feature default_policy = + FEATURE_FULLSCREEN | FEATURE_KEYBOARD | FEATURE_MOUSE; + + 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'; + } + + for (int i = 0; i < config->feature_policies->length; ++i) { + struct feature_policy *policy = config->feature_policies->items[i]; + if (strcmp(policy->program, "*")) { + 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) { + enum command_context default_policy = CONTEXT_ALL; + + for (int i = 0; i < config->command_policies->length; ++i) { + struct command_policy *policy = config->command_policies->items[i]; + if (strcmp(policy->command, "*")) { + default_policy = policy->context; + } + if (strcmp(policy->command, cmd) == 0) { + return policy->context; + } + } + + return default_policy; +} From 1a8a42f372e1bed146623e3357dbb12d8947e654 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Thu, 1 Dec 2016 20:39:35 -0500 Subject: [PATCH 04/31] Memory leak --- sway/security.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sway/security.c b/sway/security.c index c72d54f63..00e5e8d78 100644 --- a/sway/security.c +++ b/sway/security.c @@ -15,6 +15,7 @@ enum secure_feature get_feature_policy(pid_t pid) { FEATURE_FULLSCREEN | FEATURE_KEYBOARD | FEATURE_MOUSE; ssize_t len = readlink(path, link, sizeof(link)); + free(path); if (len < 0) { sway_log(L_INFO, "WARNING: unable to read %s for security check. Using default policy.", From 76cab04b4d7828f3c4f607c49e1e6ad78aa6e3da Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Thu, 1 Dec 2016 21:36:43 -0500 Subject: [PATCH 05/31] Implement permit and reject commands --- config.d/{security => security.in} | 8 +-- config => config.in | 8 +-- include/security.h | 9 --- include/sway/commands.h | 2 + include/sway/security.h | 6 +- sway/commands.c | 2 + sway/commands/permit.c | 95 ++++++++++++++++++++++++++++++ sway/security.c | 7 +++ 8 files changed, 115 insertions(+), 22 deletions(-) rename config.d/{security => security.in} (88%) rename config => config.in (98%) delete mode 100644 include/security.h create mode 100644 sway/commands/permit.c diff --git a/config.d/security b/config.d/security.in similarity index 88% rename from config.d/security rename to config.d/security.in index fe75d8ea5..f59b2980d 100644 --- a/config.d/security +++ b/config.d/security.in @@ -6,10 +6,10 @@ # installation. # Configures which programs are allowed to use which sway features -permit $PREFIX/swaylock lock -permit $PREFIX/swaybar panel -permit $PREFIX/swaybg background -permit $PREFIX/swaygrab screenshot +permit __PREFIX__/swaylock lock +permit __PREFIX__/swaybar panel +permit __PREFIX__/swaybg background +permit __PREFIX__/swaygrab screenshot permit * fullscreen keyboard mouse diff --git a/config b/config.in similarity index 98% rename from config rename to config.in index 47bf1e4fd..ddd0fec5c 100644 --- a/config +++ b/config.in @@ -195,10 +195,4 @@ bar { } } -# You may want this: -# -# include ~/.config/sway/conf.d/* -# -# Protip: -# -# include ~/.config/sway/`hostname`/* +include __SYSCONFDIR__/etc/sway/config.d/* diff --git a/include/security.h b/include/security.h deleted file mode 100644 index 3a5dbca07..000000000 --- a/include/security.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef _SWAY_SECURITY_H -#define _SWAY_SECURITY_H -#include -#include "sway/config.h" - -enum secure_features get_feature_policy(pid_t pid); -enum command_context get_command_policy(const char *cmd); - -#endif diff --git a/include/sway/commands.h b/include/sway/commands.h index db5e94d9b..1d5d56ac0 100644 --- a/include/sway/commands.h +++ b/include/sway/commands.h @@ -122,6 +122,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; diff --git a/include/sway/security.h b/include/sway/security.h index efc25ce66..ae2de0d80 100644 --- a/include/sway/security.h +++ b/include/sway/security.h @@ -3,7 +3,9 @@ #include #include "sway/config.h" -const struct feature_permissions *get_permissions(pid_t pid); -enum command_context get_command_context(const char *cmd); +enum secure_feature get_feature_policy(pid_t pid); +enum command_context get_command_policy(const char *cmd); + +struct feature_policy *alloc_feature_policy(const char *program); #endif diff --git a/sway/commands.c b/sway/commands.c index de29a7af5..e2bafcb2d 100644 --- a/sway/commands.c +++ b/sway/commands.c @@ -187,6 +187,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 }, diff --git a/sway/commands/permit.c b/sway/commands/permit.c new file mode 100644 index 000000000..8a7bb98cc --- /dev/null +++ b/sway/commands/permit.c @@ -0,0 +1,95 @@ +#include +#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 }, + }; + size_t names_len = sizeof(feature_names) / + (sizeof(char *) + sizeof(enum secure_feature)); + + for (int i = 1; i < argc; ++i) { + size_t j; + for (j = 0; j < names_len; ++j) { + if (strcmp(feature_names[j].name, argv[i]) == 0) { + break; + } + } + if (j == names_len) { + *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/security.c b/sway/security.c index 00e5e8d78..776bd5279 100644 --- a/sway/security.c +++ b/sway/security.c @@ -4,6 +4,13 @@ #include "sway/security.h" #include "log.h" +struct feature_policy *alloc_feature_policy(const char *program) { + struct feature_policy *policy = malloc(sizeof(struct feature_policy)); + policy->program = strdup(program); + policy->features = FEATURE_FULLSCREEN | FEATURE_KEYBOARD | FEATURE_MOUSE; + return policy; +} + enum secure_feature get_feature_policy(pid_t pid) { const char *fmt = "/proc/%d/exe"; int pathlen = snprintf(NULL, 0, fmt, pid); From 21e1b2bef3d3cda3d10d4dc2aafe5fcac583c2a5 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Thu, 1 Dec 2016 21:51:07 -0500 Subject: [PATCH 06/31] Add security checks for background, panel, lock --- sway/extensions.c | 25 +++++++++++++++++++++++++ sway/security.c | 4 ++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/sway/extensions.c b/sway/extensions.c index 60cd8d41c..96c7e60dd 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/security.c b/sway/security.c index 776bd5279..a4cecf16b 100644 --- a/sway/security.c +++ b/sway/security.c @@ -34,7 +34,7 @@ enum secure_feature get_feature_policy(pid_t pid) { for (int i = 0; i < config->feature_policies->length; ++i) { struct feature_policy *policy = config->feature_policies->items[i]; - if (strcmp(policy->program, "*")) { + if (strcmp(policy->program, "*") == 0) { default_policy = policy->features; } if (strcmp(policy->program, link) == 0) { @@ -50,7 +50,7 @@ enum command_context get_command_policy(const char *cmd) { for (int i = 0; i < config->command_policies->length; ++i) { struct command_policy *policy = config->command_policies->items[i]; - if (strcmp(policy->command, "*")) { + if (strcmp(policy->command, "*") == 0) { default_policy = policy->context; } if (strcmp(policy->command, cmd) == 0) { From dc4b57c868662e76dc4363eca6ddd0d73284eb72 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Thu, 1 Dec 2016 21:58:38 -0500 Subject: [PATCH 07/31] Shut Clang up --- sway/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sway/main.c b/sway/main.c index 4704f9005..a6721fbad 100644 --- a/sway/main.c +++ b/sway/main.c @@ -145,7 +145,7 @@ static void log_kernel() { static void security_sanity_check() { // TODO: Notify users visually if this has issues - struct stat s = {0}; + struct stat s; if (stat("/proc", &s)) { sway_log(L_ERROR, "!! DANGER !! /proc is not available - sway CANNOT enforce security rules!"); From ffdbb9d05077db2a44ab0f307b09d6bdbd1cab27 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Thu, 1 Dec 2016 22:03:36 -0500 Subject: [PATCH 08/31] Enforce fullscreen permissions --- sway/handlers.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sway/handlers.c b/sway/handlers.c index 2235bc8b7..24784330d 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" @@ -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) { From 8aeeacf178b234cefd046aa4ea8ec9c076d3f7b1 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Thu, 1 Dec 2016 22:09:33 -0500 Subject: [PATCH 09/31] Enforce keyboard permissions --- sway/handlers.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sway/handlers.c b/sway/handlers.c index 24784330d..3b85679db 100644 --- a/sway/handlers.c +++ b/sway/handlers.c @@ -725,6 +725,15 @@ 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)) { + sway_log(L_INFO, "Denying keypress to %d (%s)", pid, focused->name); + return EVENT_HANDLED; + } + } return EVENT_PASSTHROUGH; } From 0d395681feb13e41a1ba7f70a4e100e9094547f4 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Thu, 1 Dec 2016 22:11:48 -0500 Subject: [PATCH 10/31] Enforce mouse permissions --- sway/handlers.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/sway/handlers.c b/sway/handlers.c index 3b85679db..a329329df 100644 --- a/sway/handlers.c +++ b/sway/handlers.c @@ -730,7 +730,6 @@ static bool handle_key(wlc_handle view, uint32_t time, const struct wlc_modifier if (focused->type == C_VIEW) { pid_t pid = wlc_view_get_pid(focused->handle); if (!(get_feature_policy(pid) & FEATURE_KEYBOARD)) { - sway_log(L_INFO, "Denying keypress to %d (%s)", pid, focused->name); return EVENT_HANDLED; } } @@ -790,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; } @@ -857,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; } @@ -899,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; From f23880b1fdd70a21b04317c18208a1f3ce356839 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Fri, 2 Dec 2016 08:10:03 -0500 Subject: [PATCH 11/31] Add support for command policies in config file --- include/sway/commands.h | 10 ++++- include/sway/security.h | 1 + sway/commands.c | 82 +++++++++++++++++++++++++++++++++++++++- sway/commands/commands.c | 23 +++++++++++ sway/commands/permit.c | 3 +- sway/config.c | 21 +++++++++- sway/security.c | 10 +++-- 7 files changed, 142 insertions(+), 8 deletions(-) create mode 100644 sway/commands/commands.c diff --git a/include/sway/commands.h b/include/sway/commands.h index 1d5d56ac0..ccc3cf584 100644 --- a/include/sway/commands.h +++ b/include/sway/commands.h @@ -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, }; /** @@ -58,6 +61,10 @@ struct cmd_results *handle_command(char *command); * 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; diff --git a/include/sway/security.h b/include/sway/security.h index ae2de0d80..aa51fd815 100644 --- a/include/sway/security.h +++ b/include/sway/security.h @@ -7,5 +7,6 @@ enum secure_feature get_feature_policy(pid_t pid); enum command_context get_command_policy(const char *cmd); struct feature_policy *alloc_feature_policy(const char *program); +struct command_policy *alloc_command_policy(const char *command); #endif diff --git a/sway/commands.c b/sway/commands.c index e2bafcb2d..0bfe9d13f 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 }, @@ -460,7 +462,85 @@ 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) { + 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 }, + }; + size_t names_len = 5; + + for (int i = 1; i < argc; ++i) { + size_t j; + for (j = 0; j < names_len; ++j) { + if (strcmp(context_names[j].name, argv[i]) == 0) { + break; + } + } + if (j == names_len) { + 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 000000000..5d248e30d --- /dev/null +++ b/sway/commands/commands.c @@ -0,0 +1,23 @@ +#include +#include +#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/permit.c b/sway/commands/permit.c index 8a7bb98cc..258ea5b27 100644 --- a/sway/commands/permit.c +++ b/sway/commands/permit.c @@ -20,8 +20,7 @@ static enum secure_feature get_features(int argc, char **argv, { "keyboard", FEATURE_KEYBOARD }, { "mouse", FEATURE_MOUSE }, }; - size_t names_len = sizeof(feature_names) / - (sizeof(char *) + sizeof(enum secure_feature)); + size_t names_len = 7; for (int i = 1; i < argc; ++i) { size_t j; diff --git a/sway/config.c b/sway/config.c index a2f6a7282..e55c6dea2 100644 --- a/sway/config.c +++ b/sway/config.c @@ -580,7 +580,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: @@ -626,6 +632,14 @@ 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_END: switch(block) { case CMD_BLOCK_MODE: @@ -651,6 +665,11 @@ 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_END: sway_log(L_ERROR, "Unmatched }"); break; diff --git a/sway/security.c b/sway/security.c index a4cecf16b..670cae56e 100644 --- a/sway/security.c +++ b/sway/security.c @@ -11,6 +11,13 @@ struct feature_policy *alloc_feature_policy(const char *program) { 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 = CONTEXT_ALL; + return policy; +} + enum secure_feature get_feature_policy(pid_t pid) { const char *fmt = "/proc/%d/exe"; int pathlen = snprintf(NULL, 0, fmt, pid); @@ -50,9 +57,6 @@ enum command_context get_command_policy(const char *cmd) { 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; } From 39cf9a82f7c1f7e5d7b4952cabf215c8459a99e2 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Fri, 2 Dec 2016 08:17:45 -0500 Subject: [PATCH 12/31] Enforce command policies --- include/sway/commands.h | 2 +- include/sway/security.h | 2 ++ sway/commands.c | 12 +++++++++++- sway/handlers.c | 14 +++++++------- sway/ipc-server.c | 2 +- sway/security.c | 17 +++++++++++++++++ 6 files changed, 39 insertions(+), 10 deletions(-) diff --git a/include/sway/commands.h b/include/sway/commands.h index ccc3cf584..9e8d013ec 100644 --- a/include/sway/commands.h +++ b/include/sway/commands.h @@ -54,7 +54,7 @@ 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. * diff --git a/include/sway/security.h b/include/sway/security.h index aa51fd815..1cc85beec 100644 --- a/include/sway/security.h +++ b/include/sway/security.h @@ -6,6 +6,8 @@ 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); diff --git a/sway/commands.c b/sway/commands.c index 0bfe9d13f..5d5087b13 100644 --- a/sway/commands.c +++ b/sway/commands.c @@ -323,7 +323,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.) @@ -397,6 +397,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); diff --git a/sway/handlers.c b/sway/handlers.c index a329329df..ee52ba38f 100644 --- a/sway/handlers.c +++ b/sway/handlers.c @@ -386,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); } @@ -585,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); } @@ -936,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; } } @@ -960,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 ebb5ce583..e575081b2 100644 --- a/sway/ipc-server.c +++ b/sway/ipc-server.c @@ -312,7 +312,7 @@ void ipc_client_handle_command(struct ipc_client *client) { switch (client->current_command) { case IPC_COMMAND: { - struct cmd_results *results = handle_command(buf); + 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); diff --git a/sway/security.c b/sway/security.c index 670cae56e..2ccc30fda 100644 --- a/sway/security.c +++ b/sway/security.c @@ -64,3 +64,20 @@ enum command_context get_command_policy(const char *cmd) { 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"; + } +} From 04fc10feeb4bd3a736b071ef1fa89c5685118707 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Fri, 2 Dec 2016 08:42:26 -0500 Subject: [PATCH 13/31] Flesh out security_sanity_check --- sway/main.c | 47 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/sway/main.c b/sway/main.c index a6721fbad..d396089ca 100644 --- a/sway/main.c +++ b/sway/main.c @@ -12,6 +12,7 @@ #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" @@ -151,17 +152,44 @@ static void security_sanity_check() { "!! DANGER !! /proc is not available - sway CANNOT enforce security rules!"); } if (!stat(SYSCONFDIR "/sway", &s)) { - if (s.st_uid != 0 || s.st_gid != 0 || s.st_mode != 00755) { + 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"); + "!! 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)); } } - // TODO: check that these command policies are set - // reload bindsym - // restart bindsym - // permit config - // reject config - // ipc config } int main(int argc, char **argv) { @@ -278,7 +306,6 @@ int main(int argc, char **argv) { } wlc_log_set_handler(wlc_log_handler); detect_proprietary(); - security_sanity_check(); input_devices = create_list(); @@ -321,6 +348,8 @@ int main(int argc, char **argv) { free(config_path); } + security_sanity_check(); + if (!terminate_request) { wlc_run(); } From 10c21250402aa8127a6700bc0330f47c7439f5bb Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Fri, 2 Dec 2016 08:47:03 -0500 Subject: [PATCH 14/31] Unset LD_PRELOAD on startup (before dropping root) LD_PRELOAD enables keyloggers to easily be made. This solution isn't perfect - really a secure system wouldn't have LD_PRELOAD at all. It was a stupid idea in the first place. --- sway/main.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sway/main.c b/sway/main.c index d396089ca..1db88da2f 100644 --- a/sway/main.c +++ b/sway/main.c @@ -220,6 +220,8 @@ int main(int argc, char **argv) { " --get-socketpath Gets the IPC socket path and prints it, then exits.\n" "\n"; + unsetenv("LD_PRELOAD"); // Security + int c; while (1) { int option_index = 0; From 3dbeb9c35cd3cd71b318370b776bdaa00436a356 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Fri, 2 Dec 2016 10:05:43 -0500 Subject: [PATCH 15/31] Add sway-security(7) --- config.d/security.in | 3 +- sway/CMakeLists.txt | 1 + sway/sway-security.7.txt | 229 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 231 insertions(+), 2 deletions(-) create mode 100644 sway/sway-security.7.txt diff --git a/config.d/security.in b/config.d/security.in index f59b2980d..a1efb98fe 100644 --- a/config.d/security.in +++ b/config.d/security.in @@ -28,10 +28,9 @@ ipc { output enabled mode enabled window enabled - bar-config enabled - binding enabled modifier enabled input enabled + binding disabled } } diff --git a/sway/CMakeLists.txt b/sway/CMakeLists.txt index 9349c30d7..893882208 100644 --- a/sway/CMakeLists.txt +++ b/sway/CMakeLists.txt @@ -73,3 +73,4 @@ 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/sway-security.7.txt b/sway/sway-security.7.txt new file mode 100644 index 000000000..f3d4a2299 --- /dev/null +++ b/sway/sway-security.7.txt @@ -0,0 +1,229 @@ +///// +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 by GNU for the purpose of ruining the security +of your system. One of the many ways LD_PRELOAD kills security is by making +Wayland keyloggers possible. + +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 security dumpster fire that is dynamic linking. + +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. + +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. + +**keyboard**:: + Permission to receive keyboard events. + +**lock**:: + Permission for a program to act as a screen locker. This involves becoming + fullscreen (on all outputs) and accepting all keyboard and mouse input for the + duration of the process. + +**mouse**:: + Permission to receive mouse events. + +**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**, and **mouse** +permissions. You can use the following config commands to control a program's +access: + +**permit** :: + Permits to use (each feature seperated by a space). + may be * to affect the default policy. + +**reject** :: + Disallows from using (each feature seperated by a space). + may be * to affect the default policy. + +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. + +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. + +By default a command is allowed to execute in any context. To configure this, open +a commands block and fill it with policies: + + commands { + + ... + } + +For example, you could do this to limit the use of the focus command to just +binding and critiera: + + commands { + focus binding criteria + } + +IPC policies +------------ + +By default all programs can connect to IPC for backwards compatability with i3. +However, you can whitelist IPC access like so: + + reject * ipc + 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** :: + Controls GET_BAR_CONFIG (required for swaybar to work at all). + +**command** :: + Controls executing sway commands via IPC. + +**inputs** :: + Controls GET_INPUTS (input device information). + +**marks** :: + Controls GET_MARKS. + +**outputs** :: + Controls GET_OUTPUTS. + +**tree** :: + Controls GET_TREE. + +**workspaces** :: + 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** :: + Controls keybinding notifications (disabled by default). + +**input** :: + Controls input device hotplugging notifications. + +**mode** :: + Controls output hotplugging notifications. + +**output** :: + Controls output hotplugging notifications. + +**window** :: + Controls window event notifications. + +**workspace** :: + Controls workspace notifications. + +Disabling some of these may cause swaybar to behave incorrectly. + +Authors +------- +Maintained by Drew DeVault , who is assisted by other open +source contributors. For more information about sway development, see +. From 4d312f753c74beadfe26b4cdb4599e7e06fe55a8 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Fri, 2 Dec 2016 10:13:06 -0500 Subject: [PATCH 16/31] Add docs on what features sway programs require --- sway/sway-security.7.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sway/sway-security.7.txt b/sway/sway-security.7.txt index f3d4a2299..37c71260b 100644 --- a/sway/sway-security.7.txt +++ b/sway/sway-security.7.txt @@ -115,6 +115,13 @@ 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 +- swaygrab: screenshot + Command policies ---------------- From 1a143e601bdc5a199d1573cbf5213091404868d7 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Fri, 2 Dec 2016 10:17:53 -0500 Subject: [PATCH 17/31] Clarify when keyboard/mouse features work --- sway/sway-security.7.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sway/sway-security.7.txt b/sway/sway-security.7.txt index 37c71260b..451f7b885 100644 --- a/sway/sway-security.7.txt +++ b/sway/sway-security.7.txt @@ -82,7 +82,7 @@ policies. These features are: fullscreen themselves with the fullscreen command. **keyboard**:: - Permission to receive keyboard events. + Permission to receive keyboard events (only while they are focused). **lock**:: Permission for a program to act as a screen locker. This involves becoming @@ -90,7 +90,7 @@ policies. These features are: duration of the process. **mouse**:: - Permission to receive mouse events. + 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. From a4e92ad2723a9c33c029f90f8a2af054bf74e1ce Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Fri, 2 Dec 2016 10:23:30 -0500 Subject: [PATCH 18/31] Deal with LD_LIBRARY_PATH --- CMakeLists.txt | 3 +++ sway/main.c | 4 +++- sway/sway-security.7.txt | 3 +++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 83989ecd7..cd816e9bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,6 +48,9 @@ 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) diff --git a/sway/main.c b/sway/main.c index 1db88da2f..9746cfb24 100644 --- a/sway/main.c +++ b/sway/main.c @@ -220,7 +220,9 @@ int main(int argc, char **argv) { " --get-socketpath Gets the IPC socket path and prints it, then exits.\n" "\n"; - unsetenv("LD_PRELOAD"); // Security + // Security: + unsetenv("LD_PRELOAD"); + setenv("LD_LIBRARY_PATH", _LD_LIBRARY_PATH, 1); int c; while (1) { diff --git a/sway/sway-security.7.txt b/sway/sway-security.7.txt index 451f7b885..b6f18e806 100644 --- a/sway/sway-security.7.txt +++ b/sway/sway-security.7.txt @@ -62,6 +62,9 @@ compromised by LD_PRELOAD. It probably isn't, but you can be sure by setting 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 ------------- From c61746a15b78bcd22ca473345ff164ff2c9de973 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Fri, 2 Dec 2016 10:29:50 -0500 Subject: [PATCH 19/31] Soften up environment security So no one gets their feewings hurt --- sway/sway-security.7.txt | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/sway/sway-security.7.txt b/sway/sway-security.7.txt index b6f18e806..ec11f10bc 100644 --- a/sway/sway-security.7.txt +++ b/sway/sway-security.7.txt @@ -39,12 +39,9 @@ you choose to place it in other locations. Environment security -------------------- -LD_PRELOAD is a mechanism designed by GNU for the purpose of ruining the security -of your system. One of the many ways LD_PRELOAD kills security is by making -Wayland keyloggers possible. - -There are a number of strategies for dealing with this but they all suck a little. -In order of most practical to least practical: +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. @@ -54,7 +51,7 @@ In order of most practical to least practical: but this is the most effective solution. 3. Use static linking for important programs. Of course statically linked programs - are unaffected by the security dumpster fire that is dynamic linking. + 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 From 0c8dc0e6dfbd9272bc22b5476259cd68a1fab35c Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Fri, 2 Dec 2016 10:32:08 -0500 Subject: [PATCH 20/31] Clarify that executable has to be a full path --- sway/sway-security.7.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sway/sway-security.7.txt b/sway/sway-security.7.txt index ec11f10bc..a1ed9e321 100644 --- a/sway/sway-security.7.txt +++ b/sway/sway-security.7.txt @@ -104,11 +104,13 @@ access: **permit** :: Permits to use (each feature seperated by a space). - may be * to affect the default policy. + may be * to affect the default policy, or the full path to the + executable file. **reject** :: Disallows from using (each feature seperated by a space). - may be * to affect the default policy. + 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 From 751e6d2ab2ac4f4a3403b1406d5e8b206e9d650b Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Fri, 2 Dec 2016 10:34:17 -0500 Subject: [PATCH 21/31] Clarify lock permission consequences --- sway/sway-security.7.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sway/sway-security.7.txt b/sway/sway-security.7.txt index a1ed9e321..a4122c5cc 100644 --- a/sway/sway-security.7.txt +++ b/sway/sway-security.7.txt @@ -86,8 +86,8 @@ policies. These features are: **lock**:: Permission for a program to act as a screen locker. This involves becoming - fullscreen (on all outputs) and accepting all keyboard and mouse input for the - duration of the process. + 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). From 25a4a85a59802d3e437129370945d4b4a662c7b2 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Fri, 2 Dec 2016 15:56:36 -0500 Subject: [PATCH 22/31] Run config files through sed and install to /etc --- config.d/security.in | 8 ++++---- config.in | 2 +- sway/CMakeLists.txt | 30 +++++++++++++++++++++++++----- swaylock/CMakeLists.txt | 2 +- 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/config.d/security.in b/config.d/security.in index a1efb98fe..b5690dc79 100644 --- a/config.d/security.in +++ b/config.d/security.in @@ -6,10 +6,10 @@ # installation. # Configures which programs are allowed to use which sway features -permit __PREFIX__/swaylock lock -permit __PREFIX__/swaybar panel -permit __PREFIX__/swaybg background -permit __PREFIX__/swaygrab screenshot +permit __PREFIX__/bin/swaylock lock +permit __PREFIX__/bin/swaybar panel +permit __PREFIX__/bin/swaybg background +permit __PREFIX__/bin/swaygrab screenshot permit * fullscreen keyboard mouse diff --git a/config.in b/config.in index ddd0fec5c..3cb0525ea 100644 --- a/config.in +++ b/config.in @@ -195,4 +195,4 @@ bar { } } -include __SYSCONFDIR__/etc/sway/config.d/* +include __SYSCONFDIR__/sway/config.d/* diff --git a/sway/CMakeLists.txt b/sway/CMakeLists.txt index 893882208..15fa17201 100644 --- a/sway/CMakeLists.txt +++ b/sway/CMakeLists.txt @@ -63,11 +63,31 @@ 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) diff --git a/swaylock/CMakeLists.txt b/swaylock/CMakeLists.txt index febbd1af0..4aec6424b 100644 --- a/swaylock/CMakeLists.txt +++ b/swaylock/CMakeLists.txt @@ -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 ) From 0a1b211e09e9fc82885eaf12c0a3658d36c0bec0 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Fri, 2 Dec 2016 16:01:33 -0500 Subject: [PATCH 23/31] Drop -Denable-binding-event --- CMakeLists.txt | 4 ---- sway/ipc-server.c | 6 ------ 2 files changed, 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cd816e9bf..e314fd734 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,7 +45,6 @@ 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") @@ -86,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) diff --git a/sway/ipc-server.c b/sway/ipc-server.c index e575081b2..0442a2f95 100644 --- a/sway/ipc-server.c +++ b/sway/ipc-server.c @@ -343,10 +343,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); @@ -639,7 +637,6 @@ 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) { sway_log(L_DEBUG, "Sending binding::run event"); json_object *obj = json_object_new_object(); @@ -651,10 +648,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 +700,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 } From e9e1a6a409a276310e1015763184641547e7823c Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Fri, 2 Dec 2016 16:08:45 -0500 Subject: [PATCH 24/31] Add IPC policy to config Also reduces enum abuse, cc @minus7 --- include/ipc.h | 2 ++ include/sway/config.h | 9 +++++---- sway/config.c | 1 + sway/ipc-server.c | 2 -- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/include/ipc.h b/include/ipc.h index 496625ceb..983903359 100644 --- a/include/ipc.h +++ b/include/ipc.h @@ -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, diff --git a/include/sway/config.h b/include/sway/config.h index 14a86e499..1154b8713 100644 --- a/include/sway/config.h +++ b/include/sway/config.h @@ -135,7 +135,7 @@ struct bar_config { int height; // -1 not defined int tray_padding; bool workspace_buttons; - bool wrap_scroll; + bool wrap_scroll; char *separator_symbol; bool strip_workspace_numbers; bool binding_mode_indicator; @@ -191,7 +191,7 @@ enum command_context { struct command_policy { char *command; - enum command_context context; + uint32_t context; }; enum secure_feature { @@ -206,7 +206,7 @@ enum secure_feature { struct feature_policy { char *program; - enum secure_feature features; + uint32_t features; }; /** @@ -228,7 +228,7 @@ struct sway_config { uint32_t floating_mod; uint32_t dragging_key; uint32_t resizing_key; - char *floating_scroll_up_cmd; + char *floating_scroll_up_cmd; char *floating_scroll_down_cmd; char *floating_scroll_left_cmd; char *floating_scroll_right_cmd; @@ -281,6 +281,7 @@ struct sway_config { // Security list_t *command_policies; list_t *feature_policies; + uint32_t ipc_policy; }; void pid_workspace_add(struct pid_workspace *pw); diff --git a/sway/config.c b/sway/config.c index e55c6dea2..b1b0aac9b 100644 --- a/sway/config.c +++ b/sway/config.c @@ -345,6 +345,7 @@ static void config_defaults(struct sway_config *config) { // 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) { diff --git a/sway/ipc-server.c b/sway/ipc-server.c index 0442a2f95..ef741e3bc 100644 --- a/sway/ipc-server.c +++ b/sway/ipc-server.c @@ -55,8 +55,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) { From c8dc4925d1e0f5d5086a4c15415ee9fb0b7e6155 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Fri, 2 Dec 2016 17:34:26 -0500 Subject: [PATCH 25/31] Add IPC security policy command handlers --- include/sway/commands.h | 5 ++ sway/commands.c | 30 ++++++++- sway/commands/ipc.c | 140 ++++++++++++++++++++++++++++++++++++++++ sway/config.c | 26 ++++++++ 4 files changed, 200 insertions(+), 1 deletion(-) create mode 100644 sway/commands/ipc.c diff --git a/include/sway/commands.h b/include/sway/commands.h index 9e8d013ec..3ab8d5af7 100644 --- a/include/sway/commands.h +++ b/include/sway/commands.h @@ -120,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; @@ -192,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 diff --git a/sway/commands.c b/sway/commands.c index 5d5087b13..47f7533c6 100644 --- a/sway/commands.c +++ b/sway/commands.c @@ -180,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 }, @@ -292,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; @@ -311,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), diff --git a/sway/commands/ipc.c b/sway/commands/ipc.c new file mode 100644 index 000000000..e6ae27a43 --- /dev/null +++ b/sway/commands/ipc.c @@ -0,0 +1,140 @@ +#include +#include +#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_command_type type; + } types[] = { + { "command", IPC_COMMAND }, + { "workspaces", IPC_GET_WORKSPACES }, + { "outputs", IPC_GET_OUTPUTS }, + { "tree", IPC_GET_TREE }, + { "marks", IPC_GET_MARKS }, + { "bar-config", IPC_GET_BAR_CONFIG }, + { "inputs", IPC_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_command_type type; + } types[] = { + { "workspace", event_mask(IPC_EVENT_WORKSPACE) }, + { "output", event_mask(IPC_EVENT_OUTPUT) }, + { "mode", event_mask(IPC_EVENT_MODE) }, + { "window", event_mask(IPC_EVENT_WINDOW) }, + { "binding", event_mask(IPC_EVENT_BINDING) }, + { "input", event_mask(IPC_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/config.c b/sway/config.c index b1b0aac9b..e737f83c4 100644 --- a/sway/config.c +++ b/sway/config.c @@ -641,6 +641,22 @@ bool read_config(FILE *file, struct sway_config *config) { } 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: @@ -671,6 +687,16 @@ bool read_config(FILE *file, struct sway_config *config) { 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; From 62dad7148f7b7b314f0297e191861ae3f03e9e1f Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Fri, 2 Dec 2016 17:55:03 -0500 Subject: [PATCH 26/31] Enforce IPC security policy --- include/sway/config.h | 17 +++++++++++++++++ sway/commands/ipc.c | 26 +++++++++++++------------- sway/ipc-server.c | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 13 deletions(-) diff --git a/include/sway/config.h b/include/sway/config.h index 1154b8713..192e697c0 100644 --- a/include/sway/config.h +++ b/include/sway/config.h @@ -209,6 +209,23 @@ struct feature_policy { 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. */ diff --git a/sway/commands/ipc.c b/sway/commands/ipc.c index e6ae27a43..f96e9980c 100644 --- a/sway/commands/ipc.c +++ b/sway/commands/ipc.c @@ -62,13 +62,13 @@ struct cmd_results *cmd_ipc_cmd(int argc, char **argv) { char *name; enum ipc_command_type type; } types[] = { - { "command", IPC_COMMAND }, - { "workspaces", IPC_GET_WORKSPACES }, - { "outputs", IPC_GET_OUTPUTS }, - { "tree", IPC_GET_TREE }, - { "marks", IPC_GET_MARKS }, - { "bar-config", IPC_GET_BAR_CONFIG }, - { "inputs", IPC_GET_INPUTS }, + { "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; @@ -111,12 +111,12 @@ struct cmd_results *cmd_ipc_event_cmd(int argc, char **argv) { char *name; enum ipc_command_type type; } types[] = { - { "workspace", event_mask(IPC_EVENT_WORKSPACE) }, - { "output", event_mask(IPC_EVENT_OUTPUT) }, - { "mode", event_mask(IPC_EVENT_MODE) }, - { "window", event_mask(IPC_EVENT_WINDOW) }, - { "binding", event_mask(IPC_EVENT_BINDING) }, - { "input", event_mask(IPC_EVENT_INPUT) }, + { "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; diff --git a/sway/ipc-server.c b/sway/ipc-server.c index ef741e3bc..15791c5ed 100644 --- a/sway/ipc-server.c +++ b/sway/ipc-server.c @@ -307,9 +307,14 @@ 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: { + 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]; @@ -359,6 +364,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); @@ -369,6 +377,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; ilength; i++) { @@ -388,6 +399,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); @@ -398,6 +412,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)); @@ -458,6 +475,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(); @@ -498,6 +518,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); @@ -562,6 +585,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)); @@ -586,6 +612,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)); @@ -611,6 +640,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)); @@ -636,6 +668,9 @@ void ipc_event_modifier(uint32_t modifier, const char *state) { } 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")); From d353da248b4653d7bc027ff0dceca946cdd0b22f Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Fri, 2 Dec 2016 18:08:15 -0500 Subject: [PATCH 27/31] Add ipc connection feature policy controls --- include/sway/config.h | 1 + sway/commands.c | 5 ++--- sway/commands/permit.c | 6 +++--- sway/ipc-server.c | 21 +++++++++++++++++++++ sway/security.c | 2 +- sway/sway-security.7.txt | 9 ++++++--- 6 files changed, 34 insertions(+), 10 deletions(-) diff --git a/include/sway/config.h b/include/sway/config.h index 192e697c0..2c6b83e75 100644 --- a/include/sway/config.h +++ b/include/sway/config.h @@ -202,6 +202,7 @@ enum secure_feature { FEATURE_FULLSCREEN = 16, FEATURE_KEYBOARD = 32, FEATURE_MOUSE = 64, + FEATURE_IPC = 128, }; struct feature_policy { diff --git a/sway/commands.c b/sway/commands.c index 47f7533c6..3d8f8c5bb 100644 --- a/sway/commands.c +++ b/sway/commands.c @@ -542,16 +542,15 @@ struct cmd_results *config_commands_command(char *exec) { { "criteria", CONTEXT_CRITERIA }, { "all", CONTEXT_ALL }, }; - size_t names_len = 5; for (int i = 1; i < argc; ++i) { size_t j; - for (j = 0; j < names_len; ++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 == names_len) { + if (j == sizeof(context_names) / sizeof(context_names[0])) { results = cmd_results_new(CMD_INVALID, cmd, "Invalid command context %s", argv[i]); goto cleanup; diff --git a/sway/commands/permit.c b/sway/commands/permit.c index 258ea5b27..7a25e4ceb 100644 --- a/sway/commands/permit.c +++ b/sway/commands/permit.c @@ -19,17 +19,17 @@ static enum secure_feature get_features(int argc, char **argv, { "fullscreen", FEATURE_FULLSCREEN }, { "keyboard", FEATURE_KEYBOARD }, { "mouse", FEATURE_MOUSE }, + { "ipc", FEATURE_IPC }, }; - size_t names_len = 7; for (int i = 1; i < argc; ++i) { size_t j; - for (j = 0; j < names_len; ++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 == names_len) { + if (j == sizeof(feature_names) / sizeof(feature_names[0])) { *error = cmd_results_new(CMD_INVALID, "permit", "Invalid feature grant %s", argv[i]); return 0; diff --git a/sway/ipc-server.c b/sway/ipc-server.c index 15791c5ed..c04c465a2 100644 --- a/sway/ipc-server.c +++ b/sway/ipc-server.c @@ -15,6 +15,7 @@ #include #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" @@ -124,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"); @@ -142,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; diff --git a/sway/security.c b/sway/security.c index 2ccc30fda..0d5102532 100644 --- a/sway/security.c +++ b/sway/security.c @@ -7,7 +7,7 @@ struct feature_policy *alloc_feature_policy(const char *program) { struct feature_policy *policy = malloc(sizeof(struct feature_policy)); policy->program = strdup(program); - policy->features = FEATURE_FULLSCREEN | FEATURE_KEYBOARD | FEATURE_MOUSE; + policy->features = FEATURE_FULLSCREEN | FEATURE_KEYBOARD | FEATURE_MOUSE | FEATURE_IPC; return policy; } diff --git a/sway/sway-security.7.txt b/sway/sway-security.7.txt index a4122c5cc..53c7b8763 100644 --- a/sway/sway-security.7.txt +++ b/sway/sway-security.7.txt @@ -81,6 +81,9 @@ policies. These features are: 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). @@ -98,9 +101,9 @@ policies. These features are: **screenshot**:: Permission to take screenshots or record the screen. -By default, all programs are granted **fullscreen**, **keyboard**, and **mouse** -permissions. You can use the following config commands to control a program's -access: +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** :: Permits to use (each feature seperated by a space). From 8577095db77eef62af05fd2acbd9bd2c28b901f6 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Fri, 2 Dec 2016 18:37:01 -0500 Subject: [PATCH 28/31] Check for CAP_SYS_PTRACE --- sway/CMakeLists.txt | 1 + sway/main.c | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/sway/CMakeLists.txt b/sway/CMakeLists.txt index 15fa17201..d1afadb65 100644 --- a/sway/CMakeLists.txt +++ b/sway/CMakeLists.txt @@ -55,6 +55,7 @@ target_link_libraries(sway ${PANGO_LIBRARIES} ${JSONC_LIBRARIES} m + cap ) install( diff --git a/sway/main.c b/sway/main.c index 9746cfb24..73c4b5f2c 100644 --- a/sway/main.c +++ b/sway/main.c @@ -9,6 +9,7 @@ #include #include #include +#include #include "sway/extensions.h" #include "sway/layout.h" #include "sway/config.h" @@ -151,6 +152,15 @@ static void security_sanity_check() { 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)) { From d2d6fcd1ffb496fbd3dfb448865351447474580d Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Fri, 2 Dec 2016 18:38:31 -0500 Subject: [PATCH 29/31] Fix clang issues --- sway/commands/ipc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sway/commands/ipc.c b/sway/commands/ipc.c index f96e9980c..222be0dd1 100644 --- a/sway/commands/ipc.c +++ b/sway/commands/ipc.c @@ -60,7 +60,7 @@ struct cmd_results *cmd_ipc_cmd(int argc, char **argv) { struct { char *name; - enum ipc_command_type type; + enum ipc_feature type; } types[] = { { "command", IPC_FEATURE_COMMAND }, { "workspaces", IPC_FEATURE_GET_WORKSPACES }, @@ -109,7 +109,7 @@ struct cmd_results *cmd_ipc_event_cmd(int argc, char **argv) { struct { char *name; - enum ipc_command_type type; + enum ipc_feature type; } types[] = { { "workspace", IPC_FEATURE_EVENT_WORKSPACE }, { "output", IPC_FEATURE_EVENT_OUTPUT }, From 93d99f37126b93176677fb22cf7500d10f3db6e4 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Fri, 2 Dec 2016 18:57:10 -0500 Subject: [PATCH 30/31] Fix use-after-free --- sway/security.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sway/security.c b/sway/security.c index 0d5102532..1d236b1df 100644 --- a/sway/security.c +++ b/sway/security.c @@ -29,7 +29,6 @@ enum secure_feature get_feature_policy(pid_t pid) { FEATURE_FULLSCREEN | FEATURE_KEYBOARD | FEATURE_MOUSE; ssize_t len = readlink(path, link, sizeof(link)); - free(path); if (len < 0) { sway_log(L_INFO, "WARNING: unable to read %s for security check. Using default policy.", @@ -38,6 +37,7 @@ enum secure_feature get_feature_policy(pid_t pid) { } 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]; From e7a764fdf450a8259ddbc17446dd720fa1157b44 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Sat, 3 Dec 2016 12:38:42 -0500 Subject: [PATCH 31/31] Disallow everything by default And update config.d/security to configure sane defaults --- config.d/security.in | 5 +++-- sway/commands.c | 2 +- sway/security.c | 21 ++++++++++++++++----- sway/sway-security.7.txt | 19 ++++++++++++++----- 4 files changed, 34 insertions(+), 13 deletions(-) diff --git a/config.d/security.in b/config.d/security.in index b5690dc79..47592b05e 100644 --- a/config.d/security.in +++ b/config.d/security.in @@ -6,13 +6,12 @@ # 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 -permit * fullscreen keyboard mouse - # Configures which IPC features are enabled ipc { command enabled @@ -36,6 +35,8 @@ ipc { # Limits the contexts from which certain commands are permitted commands { + * all + fullscreen binding criteria bindsym config exit binding diff --git a/sway/commands.c b/sway/commands.c index 3d8f8c5bb..d87d00841 100644 --- a/sway/commands.c +++ b/sway/commands.c @@ -524,7 +524,7 @@ struct cmd_results *config_commands_command(char *exec) { } struct cmd_handler *handler = find_handler(cmd, CMD_BLOCK_END); - if (!handler) { + if (!handler && strcmp(cmd, "*") != 0) { char *input = cmd ? cmd : "(empty)"; results = cmd_results_new(CMD_INVALID, input, "Unknown/invalid command"); goto cleanup; diff --git a/sway/security.c b/sway/security.c index 1d236b1df..f16fdd1f3 100644 --- a/sway/security.c +++ b/sway/security.c @@ -5,16 +5,25 @@ #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 = FEATURE_FULLSCREEN | FEATURE_KEYBOARD | FEATURE_MOUSE | FEATURE_IPC; + 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 = CONTEXT_ALL; + policy->context = 0; return policy; } @@ -25,8 +34,7 @@ enum secure_feature get_feature_policy(pid_t pid) { snprintf(path, pathlen + 1, fmt, pid); static char link[2048]; - enum secure_feature default_policy = - FEATURE_FULLSCREEN | FEATURE_KEYBOARD | FEATURE_MOUSE; + uint32_t default_policy = 0; ssize_t len = readlink(path, link, sizeof(link)); if (len < 0) { @@ -53,10 +61,13 @@ enum secure_feature get_feature_policy(pid_t pid) { } enum command_context get_command_policy(const char *cmd) { - enum command_context default_policy = CONTEXT_ALL; + 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; } diff --git a/sway/sway-security.7.txt b/sway/sway-security.7.txt index 53c7b8763..9a2581b11 100644 --- a/sway/sway-security.7.txt +++ b/sway/sway-security.7.txt @@ -124,8 +124,14 @@ To work correctly, sway's own programs require the following permissions: - swaybg: background - swaylock: lock, keyboard -- swaybar: panel, mouse -- swaygrab: screenshot +- 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 ---------------- @@ -145,6 +151,9 @@ contexts you can control are: **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: @@ -160,13 +169,13 @@ binding and critiera: focus binding criteria } +Setting a command policy overwrites any previous policy that was in place. + IPC policies ------------ -By default all programs can connect to IPC for backwards compatability with i3. -However, you can whitelist IPC access like so: +You may whitelist IPC access like so: - reject * ipc permit /usr/bin/swaybar ipc permit /usr/bin/swaygrab ipc # etc