aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/sway/config.h17
-rw-r--r--include/sway/security.h6
-rw-r--r--security.d/00-defaults.in (renamed from security.in)38
-rw-r--r--sway/CMakeLists.txt2
-rw-r--r--sway/commands.c2
-rw-r--r--sway/commands/commands.c8
-rw-r--r--sway/commands/ipc.c28
-rw-r--r--sway/commands/permit.c21
-rw-r--r--sway/config.c54
-rw-r--r--sway/ipc-server.c72
-rw-r--r--sway/main.c7
-rw-r--r--sway/security.c54
-rw-r--r--sway/sway-security.7.txt34
13 files changed, 228 insertions, 115 deletions
diff --git a/include/sway/config.h b/include/sway/config.h
index febde63d..d77fbd51 100644
--- a/include/sway/config.h
+++ b/include/sway/config.h
@@ -203,7 +203,6 @@ enum secure_feature {
203 FEATURE_FULLSCREEN = 16, 203 FEATURE_FULLSCREEN = 16,
204 FEATURE_KEYBOARD = 32, 204 FEATURE_KEYBOARD = 32,
205 FEATURE_MOUSE = 64, 205 FEATURE_MOUSE = 64,
206 FEATURE_IPC = 128,
207}; 206};
208 207
209struct feature_policy { 208struct feature_policy {
@@ -225,7 +224,17 @@ enum ipc_feature {
225 IPC_FEATURE_EVENT_MODE = 1024, 224 IPC_FEATURE_EVENT_MODE = 1024,
226 IPC_FEATURE_EVENT_WINDOW = 2048, 225 IPC_FEATURE_EVENT_WINDOW = 2048,
227 IPC_FEATURE_EVENT_BINDING = 4096, 226 IPC_FEATURE_EVENT_BINDING = 4096,
228 IPC_FEATURE_EVENT_INPUT = 8192 227 IPC_FEATURE_EVENT_INPUT = 8192,
228
229 IPC_FEATURE_ALL_COMMANDS = 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
230 IPC_FEATURE_ALL_EVENTS = 256 | 512 | 1024 | 2048 | 4096 | 8192,
231
232 IPC_FEATURE_ALL = IPC_FEATURE_ALL_COMMANDS | IPC_FEATURE_ALL_EVENTS,
233};
234
235struct ipc_policy {
236 char *program;
237 uint32_t features;
229}; 238};
230 239
231/** 240/**
@@ -300,7 +309,7 @@ struct sway_config {
300 // Security 309 // Security
301 list_t *command_policies; 310 list_t *command_policies;
302 list_t *feature_policies; 311 list_t *feature_policies;
303 uint32_t ipc_policy; 312 list_t *ipc_policies;
304}; 313};
305 314
306void pid_workspace_add(struct pid_workspace *pw); 315void pid_workspace_add(struct pid_workspace *pw);
@@ -331,6 +340,8 @@ void free_config(struct sway_config *config);
331 */ 340 */
332char *do_var_replacement(char *str); 341char *do_var_replacement(char *str);
333 342
343struct cmd_results *check_security_config();
344
334int input_identifier_cmp(const void *item, const void *data); 345int input_identifier_cmp(const void *item, const void *data);
335void merge_input_config(struct input_config *dst, struct input_config *src); 346void merge_input_config(struct input_config *dst, struct input_config *src);
336void apply_input_config(struct input_config *ic, struct libinput_device *dev); 347void apply_input_config(struct input_config *ic, struct libinput_device *dev);
diff --git a/include/sway/security.h b/include/sway/security.h
index 1cc85bee..c3a5cfd4 100644
--- a/include/sway/security.h
+++ b/include/sway/security.h
@@ -3,12 +3,14 @@
3#include <unistd.h> 3#include <unistd.h>
4#include "sway/config.h" 4#include "sway/config.h"
5 5
6enum secure_feature get_feature_policy(pid_t pid); 6uint32_t get_feature_policy(pid_t pid);
7enum command_context get_command_policy(const char *cmd); 7uint32_t get_ipc_policy(pid_t pid);
8uint32_t get_command_policy(const char *cmd);
8 9
9const char *command_policy_str(enum command_context context); 10const char *command_policy_str(enum command_context context);
10 11
11struct feature_policy *alloc_feature_policy(const char *program); 12struct feature_policy *alloc_feature_policy(const char *program);
13struct ipc_policy *alloc_ipc_policy(const char *program);
12struct command_policy *alloc_command_policy(const char *command); 14struct command_policy *alloc_command_policy(const char *command);
13 15
14#endif 16#endif
diff --git a/security.in b/security.d/00-defaults.in
index 16897ade..34831c65 100644
--- a/security.in
+++ b/security.d/00-defaults.in
@@ -5,36 +5,42 @@
5# You MUST read this man page if you intend to attempt to secure your sway 5# You MUST read this man page if you intend to attempt to secure your sway
6# installation. 6# installation.
7# 7#
8# This file should live at __SYSCONFDIR__/sway/security and will be 8# DO NOT CHANGE THIS FILE. Override these defaults by writing new files in
9# automatically read by sway. 9# __SYSCONFDIR__/sway/security.d/*
10 10
11# Configures which programs are allowed to use which sway features 11# Configures enabled compositor features for specific programs
12permit * fullscreen keyboard mouse ipc 12permit * fullscreen keyboard mouse
13permit __PREFIX__/bin/swaylock lock 13permit __PREFIX__/bin/swaylock lock
14permit __PREFIX__/bin/swaybar panel
15permit __PREFIX__/bin/swaybg background 14permit __PREFIX__/bin/swaybg background
16permit __PREFIX__/bin/swaygrab screenshot 15permit __PREFIX__/bin/swaygrab screenshot
16permit __PREFIX__/bin/swaybar panel
17 17
18# Configures which IPC features are enabled 18# Configures enabled IPC features for specific programs
19ipc { 19ipc __PREFIX__/bin/swaymsg {
20 command enabled 20 * enabled
21
22 events {
23 * disabled
24 }
25}
26
27ipc __PREFIX__/bin/swaybar {
28 bar-config enabled
21 outputs enabled 29 outputs enabled
22 workspaces enabled 30 workspaces enabled
23 tree enabled 31 command enabled
24 marks enabled
25 bar-config enabled
26 inputs enabled
27 32
28 events { 33 events {
29 workspace enabled 34 workspace enabled
30 output enabled
31 mode enabled 35 mode enabled
32 window enabled
33 input enabled
34 binding disabled
35 } 36 }
36} 37}
37 38
39ipc __PREFIX__/bin/swaygrab {
40 outputs enabled
41 tree enabled
42}
43
38# Limits the contexts from which certain commands are permitted 44# Limits the contexts from which certain commands are permitted
39commands { 45commands {
40 * all 46 * all
diff --git a/sway/CMakeLists.txt b/sway/CMakeLists.txt
index d5453003..981f8a07 100644
--- a/sway/CMakeLists.txt
+++ b/sway/CMakeLists.txt
@@ -91,7 +91,7 @@ function(add_config name source destination)
91endfunction() 91endfunction()
92 92
93add_config(config config sway) 93add_config(config config sway)
94add_config(security security sway) 94add_config(00-defaults security.d/00-defaults sway/security.d)
95 95
96add_manpage(sway 1) 96add_manpage(sway 1)
97add_manpage(sway 5) 97add_manpage(sway 5)
diff --git a/sway/commands.c b/sway/commands.c
index c15cb00a..068e8866 100644
--- a/sway/commands.c
+++ b/sway/commands.c
@@ -297,6 +297,7 @@ static struct cmd_handler bar_colors_handlers[] = {
297}; 297};
298 298
299static struct cmd_handler ipc_handlers[] = { 299static struct cmd_handler ipc_handlers[] = {
300 { "*", cmd_ipc_cmd },
300 { "bar-config", cmd_ipc_cmd }, 301 { "bar-config", cmd_ipc_cmd },
301 { "command", cmd_ipc_cmd }, 302 { "command", cmd_ipc_cmd },
302 { "events", cmd_ipc_events }, 303 { "events", cmd_ipc_events },
@@ -308,6 +309,7 @@ static struct cmd_handler ipc_handlers[] = {
308}; 309};
309 310
310static struct cmd_handler ipc_event_handlers[] = { 311static struct cmd_handler ipc_event_handlers[] = {
312 { "*", cmd_ipc_event_cmd },
311 { "binding", cmd_ipc_event_cmd }, 313 { "binding", cmd_ipc_event_cmd },
312 { "input", cmd_ipc_event_cmd }, 314 { "input", cmd_ipc_event_cmd },
313 { "mode", cmd_ipc_event_cmd }, 315 { "mode", cmd_ipc_event_cmd },
diff --git a/sway/commands/commands.c b/sway/commands/commands.c
index 8c7ed487..0c64970c 100644
--- a/sway/commands/commands.c
+++ b/sway/commands/commands.c
@@ -10,6 +10,9 @@ struct cmd_results *cmd_commands(int argc, char **argv) {
10 if ((error = checkarg(argc, "commands", EXPECTED_EQUAL_TO, 1))) { 10 if ((error = checkarg(argc, "commands", EXPECTED_EQUAL_TO, 1))) {
11 return error; 11 return error;
12 } 12 }
13 if ((error = check_security_config())) {
14 return error;
15 }
13 16
14 if (strcmp(argv[0], "{") != 0) { 17 if (strcmp(argv[0], "{") != 0) {
15 return cmd_results_new(CMD_FAILURE, "commands", "Expected block declaration"); 18 return cmd_results_new(CMD_FAILURE, "commands", "Expected block declaration");
@@ -19,10 +22,5 @@ struct cmd_results *cmd_commands(int argc, char **argv) {
19 return cmd_results_new(CMD_FAILURE, "commands", "Can only be used in config file."); 22 return cmd_results_new(CMD_FAILURE, "commands", "Can only be used in config file.");
20 } 23 }
21 24
22 if (!current_config_path || strcmp(SYSCONFDIR "/sway/security", current_config_path) != 0) {
23 return cmd_results_new(CMD_INVALID, "permit",
24 "This command is only permitted to run from " SYSCONFDIR "/sway/security");
25 }
26
27 return cmd_results_new(CMD_BLOCK_COMMANDS, NULL, NULL); 25 return cmd_results_new(CMD_BLOCK_COMMANDS, NULL, NULL);
28} 26}
diff --git a/sway/commands/ipc.c b/sway/commands/ipc.c
index 113a975b..8a7b849f 100644
--- a/sway/commands/ipc.c
+++ b/sway/commands/ipc.c
@@ -1,18 +1,26 @@
1#include <stdio.h> 1#include <stdio.h>
2#include <string.h> 2#include <string.h>
3#include "sway/security.h"
3#include "sway/commands.h" 4#include "sway/commands.h"
4#include "sway/config.h" 5#include "sway/config.h"
5#include "ipc.h" 6#include "ipc.h"
6#include "log.h" 7#include "log.h"
7#include "util.h" 8#include "util.h"
8 9
10static struct ipc_policy *current_policy = NULL;
11
9struct cmd_results *cmd_ipc(int argc, char **argv) { 12struct cmd_results *cmd_ipc(int argc, char **argv) {
10 struct cmd_results *error = NULL; 13 struct cmd_results *error = NULL;
11 if ((error = checkarg(argc, "ipc", EXPECTED_EQUAL_TO, 1))) { 14 if ((error = checkarg(argc, "ipc", EXPECTED_EQUAL_TO, 2))) {
15 return error;
16 }
17 if ((error = check_security_config())) {
12 return error; 18 return error;
13 } 19 }
14 20
15 if (config->reading && strcmp("{", argv[0]) != 0) { 21 const char *program = argv[0];
22
23 if (config->reading && strcmp("{", argv[1]) != 0) {
16 return cmd_results_new(CMD_INVALID, "ipc", 24 return cmd_results_new(CMD_INVALID, "ipc",
17 "Expected '{' at start of IPC config definition."); 25 "Expected '{' at start of IPC config definition.");
18 } 26 }
@@ -21,10 +29,8 @@ struct cmd_results *cmd_ipc(int argc, char **argv) {
21 return cmd_results_new(CMD_FAILURE, "ipc", "Can only be used in config file."); 29 return cmd_results_new(CMD_FAILURE, "ipc", "Can only be used in config file.");
22 } 30 }
23 31
24 if (!current_config_path || strcmp(SYSCONFDIR "/sway/security", current_config_path) != 0) { 32 current_policy = alloc_ipc_policy(program);
25 return cmd_results_new(CMD_INVALID, "permit", 33 list_add(config->ipc_policies, current_policy);
26 "This command is only permitted to run from " SYSCONFDIR "/sway/security");
27 }
28 34
29 return cmd_results_new(CMD_BLOCK_IPC, NULL, NULL); 35 return cmd_results_new(CMD_BLOCK_IPC, NULL, NULL);
30} 36}
@@ -67,6 +73,7 @@ struct cmd_results *cmd_ipc_cmd(int argc, char **argv) {
67 char *name; 73 char *name;
68 enum ipc_feature type; 74 enum ipc_feature type;
69 } types[] = { 75 } types[] = {
76 { "*", IPC_FEATURE_ALL_COMMANDS },
70 { "command", IPC_FEATURE_COMMAND }, 77 { "command", IPC_FEATURE_COMMAND },
71 { "workspaces", IPC_FEATURE_GET_WORKSPACES }, 78 { "workspaces", IPC_FEATURE_GET_WORKSPACES },
72 { "outputs", IPC_FEATURE_GET_OUTPUTS }, 79 { "outputs", IPC_FEATURE_GET_OUTPUTS },
@@ -86,10 +93,10 @@ struct cmd_results *cmd_ipc_cmd(int argc, char **argv) {
86 } 93 }
87 94
88 if (enabled) { 95 if (enabled) {
89 config->ipc_policy |= type; 96 current_policy->features |= type;
90 sway_log(L_DEBUG, "Enabled IPC %s feature", argv[-1]); 97 sway_log(L_DEBUG, "Enabled IPC %s feature", argv[-1]);
91 } else { 98 } else {
92 config->ipc_policy &= ~type; 99 current_policy->features &= ~type;
93 sway_log(L_DEBUG, "Disabled IPC %s feature", argv[-1]); 100 sway_log(L_DEBUG, "Disabled IPC %s feature", argv[-1]);
94 } 101 }
95 102
@@ -116,6 +123,7 @@ struct cmd_results *cmd_ipc_event_cmd(int argc, char **argv) {
116 char *name; 123 char *name;
117 enum ipc_feature type; 124 enum ipc_feature type;
118 } types[] = { 125 } types[] = {
126 { "*", IPC_FEATURE_ALL_EVENTS },
119 { "workspace", IPC_FEATURE_EVENT_WORKSPACE }, 127 { "workspace", IPC_FEATURE_EVENT_WORKSPACE },
120 { "output", IPC_FEATURE_EVENT_OUTPUT }, 128 { "output", IPC_FEATURE_EVENT_OUTPUT },
121 { "mode", IPC_FEATURE_EVENT_MODE }, 129 { "mode", IPC_FEATURE_EVENT_MODE },
@@ -134,10 +142,10 @@ struct cmd_results *cmd_ipc_event_cmd(int argc, char **argv) {
134 } 142 }
135 143
136 if (enabled) { 144 if (enabled) {
137 config->ipc_policy |= type; 145 current_policy->features |= type;
138 sway_log(L_DEBUG, "Enabled IPC %s event", argv[-1]); 146 sway_log(L_DEBUG, "Enabled IPC %s event", argv[-1]);
139 } else { 147 } else {
140 config->ipc_policy &= ~type; 148 current_policy->features &= ~type;
141 sway_log(L_DEBUG, "Disabled IPC %s event", argv[-1]); 149 sway_log(L_DEBUG, "Disabled IPC %s event", argv[-1]);
142 } 150 }
143 151
diff --git a/sway/commands/permit.c b/sway/commands/permit.c
index 1b2a30bf..e2bec2e2 100644
--- a/sway/commands/permit.c
+++ b/sway/commands/permit.c
@@ -19,7 +19,6 @@ static enum secure_feature get_features(int argc, char **argv,
19 { "fullscreen", FEATURE_FULLSCREEN }, 19 { "fullscreen", FEATURE_FULLSCREEN },
20 { "keyboard", FEATURE_KEYBOARD }, 20 { "keyboard", FEATURE_KEYBOARD },
21 { "mouse", FEATURE_MOUSE }, 21 { "mouse", FEATURE_MOUSE },
22 { "ipc", FEATURE_IPC },
23 }; 22 };
24 23
25 for (int i = 1; i < argc; ++i) { 24 for (int i = 1; i < argc; ++i) {
@@ -63,19 +62,13 @@ struct cmd_results *cmd_permit(int argc, char **argv) {
63 if ((error = checkarg(argc, "permit", EXPECTED_MORE_THAN, 1))) { 62 if ((error = checkarg(argc, "permit", EXPECTED_MORE_THAN, 1))) {
64 return error; 63 return error;
65 } 64 }
66 65 if ((error = check_security_config())) {
67 if (!current_config_path || strcmp(SYSCONFDIR "/sway/security", current_config_path) != 0) { 66 return error;
68 return cmd_results_new(CMD_INVALID, "permit",
69 "This command is only permitted to run from " SYSCONFDIR "/sway/security");
70 } 67 }
71 68
72 struct feature_policy *policy = get_policy(argv[0]); 69 struct feature_policy *policy = get_policy(argv[0]);
73 policy->features |= get_features(argc, argv, &error); 70 policy->features |= get_features(argc, argv, &error);
74 71
75 if (error) {
76 return error;
77 }
78
79 sway_log(L_DEBUG, "Permissions granted to %s for features %d", 72 sway_log(L_DEBUG, "Permissions granted to %s for features %d",
80 policy->program, policy->features); 73 policy->program, policy->features);
81 74
@@ -87,19 +80,13 @@ struct cmd_results *cmd_reject(int argc, char **argv) {
87 if ((error = checkarg(argc, "reject", EXPECTED_MORE_THAN, 1))) { 80 if ((error = checkarg(argc, "reject", EXPECTED_MORE_THAN, 1))) {
88 return error; 81 return error;
89 } 82 }
90 83 if ((error = check_security_config())) {
91 if (!current_config_path || strcmp(SYSCONFDIR "/sway/security", current_config_path) != 0) { 84 return error;
92 return cmd_results_new(CMD_INVALID, "permit",
93 "This command is only permitted to run from " SYSCONFDIR "/sway/security");
94 } 85 }
95 86
96 struct feature_policy *policy = get_policy(argv[0]); 87 struct feature_policy *policy = get_policy(argv[0]);
97 policy->features &= ~get_features(argc, argv, &error); 88 policy->features &= ~get_features(argc, argv, &error);
98 89
99 if (error) {
100 return error;
101 }
102
103 sway_log(L_DEBUG, "Permissions granted to %s for features %d", 90 sway_log(L_DEBUG, "Permissions granted to %s for features %d",
104 policy->program, policy->features); 91 policy->program, policy->features);
105 92
diff --git a/sway/config.c b/sway/config.c
index 9e758c90..88e6fad1 100644
--- a/sway/config.c
+++ b/sway/config.c
@@ -11,6 +11,7 @@
11#include <libinput.h> 11#include <libinput.h>
12#include <limits.h> 12#include <limits.h>
13#include <float.h> 13#include <float.h>
14#include <dirent.h>
14#include "wayland-desktop-shell-server-protocol.h" 15#include "wayland-desktop-shell-server-protocol.h"
15#include "sway/commands.h" 16#include "sway/commands.h"
16#include "sway/config.h" 17#include "sway/config.h"
@@ -379,7 +380,7 @@ static void config_defaults(struct sway_config *config) {
379 // Security 380 // Security
380 if (!(config->command_policies = create_list())) goto cleanup; 381 if (!(config->command_policies = create_list())) goto cleanup;
381 if (!(config->feature_policies = create_list())) goto cleanup; 382 if (!(config->feature_policies = create_list())) goto cleanup;
382 config->ipc_policy = UINT32_MAX; 383 if (!(config->ipc_policies = create_list())) goto cleanup;
383 384
384 return; 385 return;
385cleanup: 386cleanup:
@@ -485,6 +486,10 @@ static bool load_config(const char *path, struct sway_config *config) {
485 return true; 486 return true;
486} 487}
487 488
489static int qstrcmp(const void* a, const void* b) {
490 return strcmp(*((char**) a), *((char**) b));
491}
492
488bool load_main_config(const char *file, bool is_active) { 493bool load_main_config(const char *file, bool is_active) {
489 input_init(); 494 input_init();
490 495
@@ -512,7 +517,43 @@ bool load_main_config(const char *file, bool is_active) {
512 list_add(config->config_chain, path); 517 list_add(config->config_chain, path);
513 518
514 config->reading = true; 519 config->reading = true;
515 bool success = load_config(SYSCONFDIR "/sway/security", config); 520
521 // Read security configs
522 bool success = true;
523 DIR *dir = opendir(SYSCONFDIR "/sway/security.d");
524 if (!dir) {
525 sway_log(L_ERROR, "%s does not exist, sway will have no security configuration"
526 " and will probably be broken", SYSCONFDIR "/sway/security.d");
527 } else {
528 list_t *secconfigs = create_list();
529 char *base = SYSCONFDIR "/sway/security.d/";
530 struct dirent *ent = readdir(dir);
531 while (ent != NULL) {
532 if (ent->d_type == DT_REG) {
533 char *_path = malloc(strlen(ent->d_name) + strlen(base) + 1);
534 strcpy(_path, base);
535 strcat(_path, ent->d_name);
536 list_add(secconfigs, _path);
537 }
538 ent = readdir(dir);
539 }
540 closedir(dir);
541
542 list_qsort(secconfigs, qstrcmp);
543 for (int i = 0; i < secconfigs->length; ++i) {
544 char *_path = secconfigs->items[i];
545 struct stat s;
546 if (stat(_path, &s) || s.st_uid != 0 || s.st_gid != 0 || (s.st_mode & 0777) != 0644) {
547 sway_log(L_ERROR, "Refusing to load %s - it must be owned by root and mode 644", _path);
548 success = false;
549 } else {
550 success = success && load_config(_path, config);
551 }
552 }
553
554 free_flat_list(secconfigs);
555 }
556
516 success = success && load_config(path, config); 557 success = success && load_config(path, config);
517 558
518 if (is_active) { 559 if (is_active) {
@@ -620,6 +661,15 @@ bool load_include_configs(const char *path, struct sway_config *config) {
620 return true; 661 return true;
621} 662}
622 663
664struct cmd_results *check_security_config() {
665 if (!current_config_path || strncmp(SYSCONFDIR "/sway/security.d/", current_config_path,
666 strlen(SYSCONFDIR "/sway/security.d/")) != 0) {
667 return cmd_results_new(CMD_INVALID, "permit",
668 "This command is only permitted to run from " SYSCONFDIR "/sway/security.d/*");
669 }
670 return NULL;
671}
672
623bool read_config(FILE *file, struct sway_config *config) { 673bool read_config(FILE *file, struct sway_config *config) {
624 bool success = true; 674 bool success = true;
625 enum cmd_status block = CMD_BLOCK_END; 675 enum cmd_status block = CMD_BLOCK_END;
diff --git a/sway/ipc-server.c b/sway/ipc-server.c
index be6e411a..eddae461 100644
--- a/sway/ipc-server.c
+++ b/sway/ipc-server.c
@@ -35,6 +35,7 @@ struct ipc_client {
35 struct wlc_event_source *event_source; 35 struct wlc_event_source *event_source;
36 int fd; 36 int fd;
37 uint32_t payload_length; 37 uint32_t payload_length;
38 uint32_t security_policy;
38 enum ipc_command_type current_command; 39 enum ipc_command_type current_command;
39 enum ipc_command_type subscribed_events; 40 enum ipc_command_type subscribed_events;
40}; 41};
@@ -159,17 +160,6 @@ int ipc_handle_connection(int fd, uint32_t mask, void *data) {
159 return 0; 160 return 0;
160 } 161 }
161 162
162 pid_t pid = get_client_pid(client_fd);
163 if (!(get_feature_policy(pid) & FEATURE_IPC)) {
164 sway_log(L_INFO, "Permission to connect to IPC socket denied to %d", pid);
165 const char *error = "{\"success\": false, \"message\": \"Permission denied\"}";
166 if (write(client_fd, &error, sizeof(error)) < (int)sizeof(error)) {
167 sway_log(L_DEBUG, "Failed to write entire error");
168 }
169 close(client_fd);
170 return 0;
171 }
172
173 struct ipc_client* client = malloc(sizeof(struct ipc_client)); 163 struct ipc_client* client = malloc(sizeof(struct ipc_client));
174 if (!client) { 164 if (!client) {
175 sway_log(L_ERROR, "Unable to allocate ipc client"); 165 sway_log(L_ERROR, "Unable to allocate ipc client");
@@ -181,6 +171,9 @@ int ipc_handle_connection(int fd, uint32_t mask, void *data) {
181 client->subscribed_events = 0; 171 client->subscribed_events = 0;
182 client->event_source = wlc_event_loop_add_fd(client_fd, WLC_EVENT_READABLE, ipc_client_handle_readable, client); 172 client->event_source = wlc_event_loop_add_fd(client_fd, WLC_EVENT_READABLE, ipc_client_handle_readable, client);
183 173
174 pid_t pid = get_client_pid(client->fd);
175 client->security_policy = get_ipc_policy(pid);
176
184 list_add(ipc_client_list, client); 177 list_add(ipc_client_list, client);
185 178
186 return 0; 179 return 0;
@@ -248,8 +241,7 @@ int ipc_client_handle_readable(int client_fd, uint32_t mask, void *data) {
248 return 0; 241 return 0;
249} 242}
250 243
251void ipc_client_disconnect(struct ipc_client *client) 244void ipc_client_disconnect(struct ipc_client *client) {
252{
253 if (!sway_assert(client != NULL, "client != NULL")) { 245 if (!sway_assert(client != NULL, "client != NULL")) {
254 return; 246 return;
255 } 247 }
@@ -333,8 +325,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
333 ipc_client_disconnect(client); 325 ipc_client_disconnect(client);
334 return; 326 return;
335 } 327 }
336 if (client->payload_length > 0) 328 if (client->payload_length > 0) {
337 {
338 ssize_t received = recv(client->fd, buf, client->payload_length, 0); 329 ssize_t received = recv(client->fd, buf, client->payload_length, 0);
339 if (received == -1) 330 if (received == -1)
340 { 331 {
@@ -351,7 +342,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
351 switch (client->current_command) { 342 switch (client->current_command) {
352 case IPC_COMMAND: 343 case IPC_COMMAND:
353 { 344 {
354 if (!(config->ipc_policy & IPC_FEATURE_COMMAND)) { 345 if (!(client->security_policy & IPC_FEATURE_COMMAND)) {
355 goto exit_denied; 346 goto exit_denied;
356 } 347 }
357 struct cmd_results *results = handle_command(buf, CONTEXT_IPC); 348 struct cmd_results *results = handle_command(buf, CONTEXT_IPC);
@@ -365,6 +356,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
365 356
366 case IPC_SUBSCRIBE: 357 case IPC_SUBSCRIBE:
367 { 358 {
359 // TODO: Check if they're permitted to use these events
368 struct json_object *request = json_tokener_parse(buf); 360 struct json_object *request = json_tokener_parse(buf);
369 if (request == NULL) { 361 if (request == NULL) {
370 ipc_send_reply(client, "{\"success\": false}", 18); 362 ipc_send_reply(client, "{\"success\": false}", 18);
@@ -403,7 +395,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
403 395
404 case IPC_GET_WORKSPACES: 396 case IPC_GET_WORKSPACES:
405 { 397 {
406 if (!(config->ipc_policy & IPC_FEATURE_GET_WORKSPACES)) { 398 if (!(client->security_policy & IPC_FEATURE_GET_WORKSPACES)) {
407 goto exit_denied; 399 goto exit_denied;
408 } 400 }
409 json_object *workspaces = json_object_new_array(); 401 json_object *workspaces = json_object_new_array();
@@ -416,7 +408,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
416 408
417 case IPC_GET_INPUTS: 409 case IPC_GET_INPUTS:
418 { 410 {
419 if (!(config->ipc_policy & IPC_FEATURE_GET_INPUTS)) { 411 if (!(client->security_policy & IPC_FEATURE_GET_INPUTS)) {
420 goto exit_denied; 412 goto exit_denied;
421 } 413 }
422 json_object *inputs = json_object_new_array(); 414 json_object *inputs = json_object_new_array();
@@ -442,7 +434,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
442 434
443 case IPC_GET_OUTPUTS: 435 case IPC_GET_OUTPUTS:
444 { 436 {
445 if (!(config->ipc_policy & IPC_FEATURE_GET_OUTPUTS)) { 437 if (!(client->security_policy & IPC_FEATURE_GET_OUTPUTS)) {
446 goto exit_denied; 438 goto exit_denied;
447 } 439 }
448 json_object *outputs = json_object_new_array(); 440 json_object *outputs = json_object_new_array();
@@ -455,7 +447,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
455 447
456 case IPC_GET_TREE: 448 case IPC_GET_TREE:
457 { 449 {
458 if (!(config->ipc_policy & IPC_FEATURE_GET_TREE)) { 450 if (!(client->security_policy & IPC_FEATURE_GET_TREE)) {
459 goto exit_denied; 451 goto exit_denied;
460 } 452 }
461 json_object *tree = ipc_json_describe_container_recursive(&root_container); 453 json_object *tree = ipc_json_describe_container_recursive(&root_container);
@@ -522,7 +514,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
522 514
523 case IPC_GET_BAR_CONFIG: 515 case IPC_GET_BAR_CONFIG:
524 { 516 {
525 if (!(config->ipc_policy & IPC_FEATURE_GET_BAR_CONFIG)) { 517 if (!(client->security_policy & IPC_FEATURE_GET_BAR_CONFIG)) {
526 goto exit_denied; 518 goto exit_denied;
527 } 519 }
528 if (!buf[0]) { 520 if (!buf[0]) {
@@ -567,6 +559,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
567 559
568exit_denied: 560exit_denied:
569 ipc_send_reply(client, error_denied, (uint32_t)strlen(error_denied)); 561 ipc_send_reply(client, error_denied, (uint32_t)strlen(error_denied));
562 sway_log(L_DEBUG, "Denied IPC client access to %i", client->current_command);
570 563
571exit_cleanup: 564exit_cleanup:
572 client->payload_length = 0; 565 client->payload_length = 0;
@@ -594,6 +587,8 @@ bool ipc_send_reply(struct ipc_client *client, const char *payload, uint32_t pay
594 return false; 587 return false;
595 } 588 }
596 589
590 sway_log(L_DEBUG, "Send IPC reply: %s", payload);
591
597 return true; 592 return true;
598} 593}
599 594
@@ -616,10 +611,33 @@ void ipc_get_outputs_callback(swayc_t *container, void *data) {
616} 611}
617 612
618void ipc_send_event(const char *json_string, enum ipc_command_type event) { 613void ipc_send_event(const char *json_string, enum ipc_command_type event) {
614 static struct {
615 enum ipc_command_type event;
616 enum ipc_feature feature;
617 } security_mappings[] = {
618 { IPC_EVENT_WORKSPACE, IPC_FEATURE_EVENT_WORKSPACE },
619 { IPC_EVENT_OUTPUT, IPC_FEATURE_EVENT_OUTPUT },
620 { IPC_EVENT_MODE, IPC_FEATURE_EVENT_MODE },
621 { IPC_EVENT_WINDOW, IPC_FEATURE_EVENT_WINDOW },
622 { IPC_EVENT_BINDING, IPC_FEATURE_EVENT_BINDING },
623 { IPC_EVENT_INPUT, IPC_FEATURE_EVENT_INPUT }
624 };
625
626 uint32_t security_mask = 0;
627 for (size_t i = 0; i < sizeof(security_mappings) / sizeof(security_mappings[0]); ++i) {
628 if (security_mappings[i].event == event) {
629 security_mask = security_mappings[i].feature;
630 break;
631 }
632 }
633
619 int i; 634 int i;
620 struct ipc_client *client; 635 struct ipc_client *client;
621 for (i = 0; i < ipc_client_list->length; i++) { 636 for (i = 0; i < ipc_client_list->length; i++) {
622 client = ipc_client_list->items[i]; 637 client = ipc_client_list->items[i];
638 if (!(client->security_policy & security_mask)) {
639 continue;
640 }
623 if ((client->subscribed_events & event_mask(event)) == 0) { 641 if ((client->subscribed_events & event_mask(event)) == 0) {
624 continue; 642 continue;
625 } 643 }
@@ -632,9 +650,6 @@ void ipc_send_event(const char *json_string, enum ipc_command_type event) {
632} 650}
633 651
634void ipc_event_workspace(swayc_t *old, swayc_t *new, const char *change) { 652void ipc_event_workspace(swayc_t *old, swayc_t *new, const char *change) {
635 if (!(config->ipc_policy & IPC_FEATURE_EVENT_WORKSPACE)) {
636 return;
637 }
638 sway_log(L_DEBUG, "Sending workspace::%s event", change); 653 sway_log(L_DEBUG, "Sending workspace::%s event", change);
639 json_object *obj = json_object_new_object(); 654 json_object *obj = json_object_new_object();
640 json_object_object_add(obj, "change", json_object_new_string(change)); 655 json_object_object_add(obj, "change", json_object_new_string(change));
@@ -659,9 +674,6 @@ void ipc_event_workspace(swayc_t *old, swayc_t *new, const char *change) {
659} 674}
660 675
661void ipc_event_window(swayc_t *window, const char *change) { 676void ipc_event_window(swayc_t *window, const char *change) {
662 if (!(config->ipc_policy & IPC_FEATURE_EVENT_WINDOW)) {
663 return;
664 }
665 sway_log(L_DEBUG, "Sending window::%s event", change); 677 sway_log(L_DEBUG, "Sending window::%s event", change);
666 json_object *obj = json_object_new_object(); 678 json_object *obj = json_object_new_object();
667 json_object_object_add(obj, "change", json_object_new_string(change)); 679 json_object_object_add(obj, "change", json_object_new_string(change));
@@ -687,9 +699,6 @@ void ipc_event_barconfig_update(struct bar_config *bar) {
687} 699}
688 700
689void ipc_event_mode(const char *mode) { 701void ipc_event_mode(const char *mode) {
690 if (!(config->ipc_policy & IPC_FEATURE_EVENT_MODE)) {
691 return;
692 }
693 sway_log(L_DEBUG, "Sending mode::%s event", mode); 702 sway_log(L_DEBUG, "Sending mode::%s event", mode);
694 json_object *obj = json_object_new_object(); 703 json_object *obj = json_object_new_object();
695 json_object_object_add(obj, "change", json_object_new_string(mode)); 704 json_object_object_add(obj, "change", json_object_new_string(mode));
@@ -715,9 +724,6 @@ void ipc_event_modifier(uint32_t modifier, const char *state) {
715} 724}
716 725
717static void ipc_event_binding(json_object *sb_obj) { 726static void ipc_event_binding(json_object *sb_obj) {
718 if (!(config->ipc_policy & IPC_FEATURE_EVENT_BINDING)) {
719 return;
720 }
721 sway_log(L_DEBUG, "Sending binding::run event"); 727 sway_log(L_DEBUG, "Sending binding::run event");
722 json_object *obj = json_object_new_object(); 728 json_object *obj = json_object_new_object();
723 json_object_object_add(obj, "change", json_object_new_string("run")); 729 json_object_object_add(obj, "change", json_object_new_string("run"));
diff --git a/sway/main.c b/sway/main.c
index 1c4c56c0..0151e078 100644
--- a/sway/main.c
+++ b/sway/main.c
@@ -175,13 +175,6 @@ static void security_sanity_check() {
175 cap_free(cap); 175 cap_free(cap);
176 } 176 }
177#endif 177#endif
178 if (!stat(SYSCONFDIR "/sway", &s)) {
179 if (s.st_uid != 0 || s.st_gid != 0
180 || (s.st_mode & S_IWGRP) || (s.st_mode & S_IWOTH)) {
181 sway_log(L_ERROR,
182 "!! DANGER !! " SYSCONFDIR "/sway is not secure! It should be owned by root and set to 0755 at the minimum");
183 }
184 }
185} 178}
186 179
187int main(int argc, char **argv) { 180int main(int argc, char **argv) {
diff --git a/sway/security.c b/sway/security.c
index 41a3b94b..9dfc7d2d 100644
--- a/sway/security.c
+++ b/sway/security.c
@@ -27,6 +27,29 @@ struct feature_policy *alloc_feature_policy(const char *program) {
27 return policy; 27 return policy;
28} 28}
29 29
30struct ipc_policy *alloc_ipc_policy(const char *program) {
31 uint32_t default_policy = 0;
32 for (int i = 0; i < config->ipc_policies->length; ++i) {
33 struct ipc_policy *policy = config->ipc_policies->items[i];
34 if (strcmp(policy->program, "*") == 0) {
35 default_policy = policy->features;
36 break;
37 }
38 }
39
40 struct ipc_policy *policy = malloc(sizeof(struct ipc_policy));
41 if (!policy) {
42 return NULL;
43 }
44 policy->program = strdup(program);
45 if (!policy->program) {
46 free(policy);
47 return NULL;
48 }
49 policy->features = default_policy;
50 return policy;
51}
52
30struct command_policy *alloc_command_policy(const char *command) { 53struct command_policy *alloc_command_policy(const char *command) {
31 struct command_policy *policy = malloc(sizeof(struct command_policy)); 54 struct command_policy *policy = malloc(sizeof(struct command_policy));
32 if (!policy) { 55 if (!policy) {
@@ -41,7 +64,7 @@ struct command_policy *alloc_command_policy(const char *command) {
41 return policy; 64 return policy;
42} 65}
43 66
44enum secure_feature get_feature_policy(pid_t pid) { 67static const char *get_pid_exe(pid_t pid) {
45#ifdef __FreeBSD__ 68#ifdef __FreeBSD__
46 const char *fmt = "/proc/%d/file"; 69 const char *fmt = "/proc/%d/file";
47#else 70#else
@@ -52,9 +75,8 @@ enum secure_feature get_feature_policy(pid_t pid) {
52 if (path) { 75 if (path) {
53 snprintf(path, pathlen + 1, fmt, pid); 76 snprintf(path, pathlen + 1, fmt, pid);
54 } 77 }
55 static char link[2048];
56 78
57 uint32_t default_policy = 0; 79 static char link[2048];
58 80
59 ssize_t len = !path ? -1 : readlink(path, link, sizeof(link)); 81 ssize_t len = !path ? -1 : readlink(path, link, sizeof(link));
60 if (len < 0) { 82 if (len < 0) {
@@ -67,6 +89,13 @@ enum secure_feature get_feature_policy(pid_t pid) {
67 } 89 }
68 free(path); 90 free(path);
69 91
92 return link;
93}
94
95uint32_t get_feature_policy(pid_t pid) {
96 uint32_t default_policy = 0;
97 const char *link = get_pid_exe(pid);
98
70 for (int i = 0; i < config->feature_policies->length; ++i) { 99 for (int i = 0; i < config->feature_policies->length; ++i) {
71 struct feature_policy *policy = config->feature_policies->items[i]; 100 struct feature_policy *policy = config->feature_policies->items[i];
72 if (strcmp(policy->program, "*") == 0) { 101 if (strcmp(policy->program, "*") == 0) {
@@ -80,7 +109,24 @@ enum secure_feature get_feature_policy(pid_t pid) {
80 return default_policy; 109 return default_policy;
81} 110}
82 111
83enum command_context get_command_policy(const char *cmd) { 112uint32_t get_ipc_policy(pid_t pid) {
113 uint32_t default_policy = 0;
114 const char *link = get_pid_exe(pid);
115
116 for (int i = 0; i < config->ipc_policies->length; ++i) {
117 struct ipc_policy *policy = config->ipc_policies->items[i];
118 if (strcmp(policy->program, "*") == 0) {
119 default_policy = policy->features;
120 }
121 if (strcmp(policy->program, link) == 0) {
122 return policy->features;
123 }
124 }
125
126 return default_policy;
127}
128
129uint32_t get_command_policy(const char *cmd) {
84 uint32_t default_policy = 0; 130 uint32_t default_policy = 0;
85 131
86 for (int i = 0; i < config->command_policies->length; ++i) { 132 for (int i = 0; i < config->command_policies->length; ++i) {
diff --git a/sway/sway-security.7.txt b/sway/sway-security.7.txt
index 7d8aa4ad..fb47ffcf 100644
--- a/sway/sway-security.7.txt
+++ b/sway/sway-security.7.txt
@@ -19,8 +19,13 @@ usually best suited to a distro maintainer who wants to ship a secure sway
19environment in their distro. Sway provides a number of means of securing it but 19environment in their distro. Sway provides a number of means of securing it but
20you must make a few changes external to sway first. 20you must make a few changes external to sway first.
21 21
22Security-related configuration is only valid in /etc/sway/config (or whatever path 22Configuration of security features is limited to files in the security directory
23is appropriate for your system). 23(this is likely /etc/sway/security.d/*, but depends on your installation prefix).
24Files in this directory must be owned by root:root and chmod 644. The default
25security configuration is installed to /etc/sway/security.d/00-defaults, and
26should not be modified - it will be updated with the latest recommended security
27defaults between releases. To override the defaults, you should add more files to
28this directory.
24 29
25Environment security 30Environment security
26-------------------- 31--------------------
@@ -160,22 +165,20 @@ Setting a command policy overwrites any previous policy that was in place.
160IPC policies 165IPC policies
161------------ 166------------
162 167
163You may whitelist IPC access like so: 168Disabling IPC access via swaymsg is encouraged if you intend to secure the IPC
169socket, because any program that can execute swaymsg could circumvent its own
170security policy by simply invoking swaymsg.
164 171
165 permit /usr/bin/swaybar ipc 172You can configure which features of IPC are available for particular clients:
166 permit /usr/bin/swaygrab ipc
167 # etc
168 173
169Note that it's suggested you do not enable swaymsg to access IPC if you intend to 174 ipc <executable> {
170secure your IPC socket, because any program could just run swaymsg itself instead
171of connecting to IPC directly.
172
173You can also configure which features of IPC are available with an IPC block:
174
175 ipc {
176 ... 175 ...
177 } 176 }
178 177
178You may use * for <executable> to configure the default policy for all clients.
179Configuring IPC policies for specific executables is not supported on FreeBSD, and
180the default policy will be applied to all IPC connections.
181
179The following commands are available within this block: 182The following commands are available within this block:
180 183
181**bar-config** <enabled|disabled>:: 184**bar-config** <enabled|disabled>::
@@ -201,7 +204,7 @@ The following commands are available within this block:
201 204
202You can also control which IPC events can be raised with an events block: 205You can also control which IPC events can be raised with an events block:
203 206
204 ipc { 207 ipc <executable> {
205 events { 208 events {
206 ... 209 ...
207 } 210 }
@@ -227,7 +230,8 @@ The following commands are vaild within an ipc events block:
227**workspace** <enabled|disabled>:: 230**workspace** <enabled|disabled>::
228 Controls workspace notifications. 231 Controls workspace notifications.
229 232
230Disabling some of these may cause swaybar to behave incorrectly. 233In each of these blocks, you may use * (as in "* enabled" or "* disabled") to
234control access to every feature at once.
231 235
232Authors 236Authors
233------- 237-------