summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Drew DeVault <sir@cmpwn.com>2016-12-04 08:30:40 -0500
committerLibravatar GitHub <noreply@github.com>2016-12-04 08:30:40 -0500
commit5778c59a2f302071fd781683db57a97b51396c87 (patch)
treee0ec272832e88e6c8d92719efa70c6749452daff
parentFix memory leaks in swaybar (diff)
parentDisallow everything by default (diff)
downloadsway-5778c59a2f302071fd781683db57a97b51396c87.tar.gz
sway-5778c59a2f302071fd781683db57a97b51396c87.tar.zst
sway-5778c59a2f302071fd781683db57a97b51396c87.zip
Merge pull request #981 from SirCmpwn/security
Security features
-rw-r--r--CMakeLists.txt7
-rw-r--r--config.d/security.in52
-rw-r--r--config.in (renamed from config)8
-rw-r--r--include/ipc.h2
-rw-r--r--include/sway/commands.h19
-rw-r--r--include/sway/config.h61
-rw-r--r--include/sway/security.h14
-rw-r--r--sway/CMakeLists.txt33
-rw-r--r--sway/commands.c125
-rw-r--r--sway/commands/commands.c23
-rw-r--r--sway/commands/ipc.c140
-rw-r--r--sway/commands/permit.c94
-rw-r--r--sway/config.c72
-rw-r--r--sway/extensions.c25
-rw-r--r--sway/handlers.c50
-rw-r--r--sway/ipc-server.c66
-rw-r--r--sway/main.c66
-rw-r--r--sway/security.c94
-rw-r--r--sway/sway-security.7.txt250
-rw-r--r--swaylock/CMakeLists.txt2
20 files changed, 1159 insertions, 44 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 83989ecd..e314fd73 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -45,9 +45,11 @@ option(enable-swaybar "Enables the swaybar utility" YES)
45option(enable-swaygrab "Enables the swaygrab utility" YES) 45option(enable-swaygrab "Enables the swaygrab utility" YES)
46option(enable-swaymsg "Enables the swaymsg utility" YES) 46option(enable-swaymsg "Enables the swaymsg utility" YES)
47option(enable-gdk-pixbuf "Use Pixbuf to support more image formats" YES) 47option(enable-gdk-pixbuf "Use Pixbuf to support more image formats" YES)
48option(enable-binding-event "Enables binding event subscription" YES)
49option(zsh-completions "Zsh shell completions" NO) 48option(zsh-completions "Zsh shell completions" NO)
50option(default-wallpaper "Installs the default wallpaper" YES) 49option(default-wallpaper "Installs the default wallpaper" YES)
50option(ld-library-path "Configures sway's default LD_LIBRARY_PATH" "/usr/lib")
51
52add_definitions(-D_LD_LIBRARY_PATH="${ld-library-path}")
51 53
52find_package(JsonC REQUIRED) 54find_package(JsonC REQUIRED)
53find_package(PCRE REQUIRED) 55find_package(PCRE REQUIRED)
@@ -83,9 +85,6 @@ if (enable-gdk-pixbuf)
83else() 85else()
84 message(STATUS "Building without gdk-pixbuf, only png images supported.") 86 message(STATUS "Building without gdk-pixbuf, only png images supported.")
85endif() 87endif()
86if(enable-binding-event)
87 add_definitions(-DSWAY_BINDING_EVENT=1)
88endif()
89 88
90include_directories(include) 89include_directories(include)
91 90
diff --git a/config.d/security.in b/config.d/security.in
new file mode 100644
index 00000000..47592b05
--- /dev/null
+++ b/config.d/security.in
@@ -0,0 +1,52 @@
1# sway security rules
2#
3# Read sway-security(7) for details on how to secure your sway install.
4#
5# You MUST read this man page if you intend to attempt to secure your sway
6# installation.
7
8# Configures which programs are allowed to use which sway features
9permit * fullscreen keyboard mouse ipc
10permit __PREFIX__/bin/swaylock lock
11permit __PREFIX__/bin/swaybar panel
12permit __PREFIX__/bin/swaybg background
13permit __PREFIX__/bin/swaygrab screenshot
14
15# Configures which IPC features are enabled
16ipc {
17 command enabled
18 outputs enabled
19 workspaces enabled
20 tree enabled
21 marks enabled
22 bar-config enabled
23 inputs enabled
24
25 events {
26 workspace enabled
27 output enabled
28 mode enabled
29 window enabled
30 modifier enabled
31 input enabled
32 binding disabled
33 }
34}
35
36# Limits the contexts from which certain commands are permitted
37commands {
38 * all
39
40 fullscreen binding criteria
41 bindsym config
42 exit binding
43 kill binding
44
45 # You should not change these unless you know what you're doing - it could
46 # cripple your security
47 reload binding
48 restart binding
49 permit config
50 reject config
51 ipc config
52}
diff --git a/config b/config.in
index 47bf1e4f..3cb0525e 100644
--- a/config
+++ b/config.in
@@ -195,10 +195,4 @@ bar {
195 } 195 }
196} 196}
197 197
198# You may want this: 198include __SYSCONFDIR__/sway/config.d/*
199#
200# include ~/.config/sway/conf.d/*
201#
202# Protip:
203#
204# include ~/.config/sway/`hostname`/*
diff --git a/include/ipc.h b/include/ipc.h
index 496625ce..98390335 100644
--- a/include/ipc.h
+++ b/include/ipc.h
@@ -1,6 +1,8 @@
1#ifndef _SWAY_IPC_H 1#ifndef _SWAY_IPC_H
2#define _SWAY_IPC_H 2#define _SWAY_IPC_H
3 3
4#define event_mask(ev) (1 << (ev & 0x7F))
5
4enum ipc_command_type { 6enum ipc_command_type {
5 IPC_COMMAND = 0, 7 IPC_COMMAND = 0,
6 IPC_GET_WORKSPACES = 1, 8 IPC_GET_WORKSPACES = 1,
diff --git a/include/sway/commands.h b/include/sway/commands.h
index db5e94d9..3ab8d5af 100644
--- a/include/sway/commands.h
+++ b/include/sway/commands.h
@@ -18,7 +18,10 @@ enum cmd_status {
18 CMD_BLOCK_MODE, 18 CMD_BLOCK_MODE,
19 CMD_BLOCK_BAR, 19 CMD_BLOCK_BAR,
20 CMD_BLOCK_BAR_COLORS, 20 CMD_BLOCK_BAR_COLORS,
21 CMD_BLOCK_INPUT 21 CMD_BLOCK_INPUT,
22 CMD_BLOCK_COMMANDS,
23 CMD_BLOCK_IPC,
24 CMD_BLOCK_IPC_EVENTS,
22}; 25};
23 26
24/** 27/**
@@ -51,13 +54,17 @@ int sp_index;
51/** 54/**
52 * Parse and handles a command. 55 * Parse and handles a command.
53 */ 56 */
54struct cmd_results *handle_command(char *command); 57struct cmd_results *handle_command(char *command, enum command_context context);
55/** 58/**
56 * Parse and handles a command during config file loading. 59 * Parse and handles a command during config file loading.
57 * 60 *
58 * Do not use this under normal conditions. 61 * Do not use this under normal conditions.
59 */ 62 */
60struct cmd_results *config_command(char *command, enum cmd_status block); 63struct cmd_results *config_command(char *command, enum cmd_status block);
64/*
65 * Parses a command policy rule.
66 */
67struct cmd_results *config_commands_command(char *exec);
61 68
62/** 69/**
63 * Allocates a cmd_results object. 70 * Allocates a cmd_results object.
@@ -93,6 +100,7 @@ sway_cmd cmd_client_unfocused;
93sway_cmd cmd_client_urgent; 100sway_cmd cmd_client_urgent;
94sway_cmd cmd_client_placeholder; 101sway_cmd cmd_client_placeholder;
95sway_cmd cmd_client_background; 102sway_cmd cmd_client_background;
103sway_cmd cmd_commands;
96sway_cmd cmd_debuglog; 104sway_cmd cmd_debuglog;
97sway_cmd cmd_exec; 105sway_cmd cmd_exec;
98sway_cmd cmd_exec_always; 106sway_cmd cmd_exec_always;
@@ -112,6 +120,7 @@ sway_cmd cmd_gaps;
112sway_cmd cmd_hide_edge_borders; 120sway_cmd cmd_hide_edge_borders;
113sway_cmd cmd_include; 121sway_cmd cmd_include;
114sway_cmd cmd_input; 122sway_cmd cmd_input;
123sway_cmd cmd_ipc;
115sway_cmd cmd_kill; 124sway_cmd cmd_kill;
116sway_cmd cmd_layout; 125sway_cmd cmd_layout;
117sway_cmd cmd_log_colors; 126sway_cmd cmd_log_colors;
@@ -122,6 +131,8 @@ sway_cmd cmd_new_float;
122sway_cmd cmd_new_window; 131sway_cmd cmd_new_window;
123sway_cmd cmd_orientation; 132sway_cmd cmd_orientation;
124sway_cmd cmd_output; 133sway_cmd cmd_output;
134sway_cmd cmd_permit;
135sway_cmd cmd_reject;
125sway_cmd cmd_reload; 136sway_cmd cmd_reload;
126sway_cmd cmd_resize; 137sway_cmd cmd_resize;
127sway_cmd cmd_scratchpad; 138sway_cmd cmd_scratchpad;
@@ -182,4 +193,8 @@ sway_cmd input_cmd_pointer_accel;
182sway_cmd input_cmd_scroll_method; 193sway_cmd input_cmd_scroll_method;
183sway_cmd input_cmd_tap; 194sway_cmd input_cmd_tap;
184 195
196sway_cmd cmd_ipc_cmd;
197sway_cmd cmd_ipc_events;
198sway_cmd cmd_ipc_event_cmd;
199
185#endif 200#endif
diff --git a/include/sway/config.h b/include/sway/config.h
index 8d077ee7..2c6b83e7 100644
--- a/include/sway/config.h
+++ b/include/sway/config.h
@@ -103,9 +103,6 @@ struct pid_workspace {
103 time_t *time_added; 103 time_t *time_added;
104}; 104};
105 105
106void pid_workspace_add(struct pid_workspace *pw);
107void free_pid_workspace(struct pid_workspace *pw);
108
109struct bar_config { 106struct bar_config {
110 /** 107 /**
111 * One of "dock", "hide", "invisible" 108 * One of "dock", "hide", "invisible"
@@ -138,7 +135,7 @@ struct bar_config {
138 int height; // -1 not defined 135 int height; // -1 not defined
139 int tray_padding; 136 int tray_padding;
140 bool workspace_buttons; 137 bool workspace_buttons;
141 bool wrap_scroll; 138 bool wrap_scroll;
142 char *separator_symbol; 139 char *separator_symbol;
143 bool strip_workspace_numbers; 140 bool strip_workspace_numbers;
144 bool binding_mode_indicator; 141 bool binding_mode_indicator;
@@ -184,6 +181,52 @@ enum edge_border_types {
184 E_BOTH /**< hide vertical and horizontal edge borders */ 181 E_BOTH /**< hide vertical and horizontal edge borders */
185}; 182};
186 183
184enum command_context {
185 CONTEXT_CONFIG = 1,
186 CONTEXT_BINDING = 2,
187 CONTEXT_IPC = 4,
188 CONTEXT_CRITERIA = 8,
189 CONTEXT_ALL = 0xFFFFFFFF,
190};
191
192struct command_policy {
193 char *command;
194 uint32_t context;
195};
196
197enum secure_feature {
198 FEATURE_LOCK = 1,
199 FEATURE_PANEL = 2,
200 FEATURE_BACKGROUND = 4,
201 FEATURE_SCREENSHOT = 8,
202 FEATURE_FULLSCREEN = 16,
203 FEATURE_KEYBOARD = 32,
204 FEATURE_MOUSE = 64,
205 FEATURE_IPC = 128,
206};
207
208struct feature_policy {
209 char *program;
210 uint32_t features;
211};
212
213enum ipc_feature {
214 IPC_FEATURE_COMMAND = 1,
215 IPC_FEATURE_GET_WORKSPACES = 2,
216 IPC_FEATURE_GET_OUTPUTS = 4,
217 IPC_FEATURE_GET_TREE = 8,
218 IPC_FEATURE_GET_MARKS = 16,
219 IPC_FEATURE_GET_BAR_CONFIG = 32,
220 IPC_FEATURE_GET_VERSION = 64,
221 IPC_FEATURE_GET_INPUTS = 128,
222 IPC_FEATURE_EVENT_WORKSPACE = 256,
223 IPC_FEATURE_EVENT_OUTPUT = 512,
224 IPC_FEATURE_EVENT_MODE = 1024,
225 IPC_FEATURE_EVENT_WINDOW = 2048,
226 IPC_FEATURE_EVENT_BINDING = 4096,
227 IPC_FEATURE_EVENT_INPUT = 8192
228};
229
187/** 230/**
188 * The configuration struct. The result of loading a config file. 231 * The configuration struct. The result of loading a config file.
189 */ 232 */
@@ -203,7 +246,7 @@ struct sway_config {
203 uint32_t floating_mod; 246 uint32_t floating_mod;
204 uint32_t dragging_key; 247 uint32_t dragging_key;
205 uint32_t resizing_key; 248 uint32_t resizing_key;
206 char *floating_scroll_up_cmd; 249 char *floating_scroll_up_cmd;
207 char *floating_scroll_down_cmd; 250 char *floating_scroll_down_cmd;
208 char *floating_scroll_left_cmd; 251 char *floating_scroll_left_cmd;
209 char *floating_scroll_right_cmd; 252 char *floating_scroll_right_cmd;
@@ -252,8 +295,16 @@ struct sway_config {
252 int32_t floating_maximum_height; 295 int32_t floating_maximum_height;
253 int32_t floating_minimum_width; 296 int32_t floating_minimum_width;
254 int32_t floating_minimum_height; 297 int32_t floating_minimum_height;
298
299 // Security
300 list_t *command_policies;
301 list_t *feature_policies;
302 uint32_t ipc_policy;
255}; 303};
256 304
305void pid_workspace_add(struct pid_workspace *pw);
306void free_pid_workspace(struct pid_workspace *pw);
307
257/** 308/**
258 * Loads the main config from the given path. is_active should be true when 309 * Loads the main config from the given path. is_active should be true when
259 * reloading the config. 310 * reloading the config.
diff --git a/include/sway/security.h b/include/sway/security.h
new file mode 100644
index 00000000..1cc85bee
--- /dev/null
+++ b/include/sway/security.h
@@ -0,0 +1,14 @@
1#ifndef _SWAY_SECURITY_H
2#define _SWAY_SECURITY_H
3#include <unistd.h>
4#include "sway/config.h"
5
6enum secure_feature get_feature_policy(pid_t pid);
7enum command_context get_command_policy(const char *cmd);
8
9const char *command_policy_str(enum command_context context);
10
11struct feature_policy *alloc_feature_policy(const char *program);
12struct command_policy *alloc_command_policy(const char *command);
13
14#endif
diff --git a/sway/CMakeLists.txt b/sway/CMakeLists.txt
index bb9ea81f..d1afadb6 100644
--- a/sway/CMakeLists.txt
+++ b/sway/CMakeLists.txt
@@ -35,6 +35,7 @@ add_executable(sway
35 output.c 35 output.c
36 workspace.c 36 workspace.c
37 border.c 37 border.c
38 security.c
38) 39)
39 40
40add_definitions( 41add_definitions(
@@ -54,6 +55,7 @@ target_link_libraries(sway
54 ${PANGO_LIBRARIES} 55 ${PANGO_LIBRARIES}
55 ${JSONC_LIBRARIES} 56 ${JSONC_LIBRARIES}
56 m 57 m
58 cap
57) 59)
58 60
59install( 61install(
@@ -62,13 +64,34 @@ install(
62 DESTINATION bin 64 DESTINATION bin
63 COMPONENT runtime 65 COMPONENT runtime
64) 66)
65install( 67
66 FILES ${PROJECT_SOURCE_DIR}/config 68add_custom_target(configs ALL)
67 DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/sway/ 69
68 COMPONENT configuration 70function(add_config name source destination)
69) 71 add_custom_command(
72 OUTPUT ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${name}
73 COMMAND sed -r
74 's?__PREFIX__?${CMAKE_INSTALL_PREFIX}?g\; s?__SYSCONFDIR__?${CMAKE_INSTALL_FULL_SYSCONFDIR}?g'
75 ${PROJECT_SOURCE_DIR}/${source}.in > ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${name}
76 DEPENDS ${PROJECT_SOURCE_DIR}/${source}.in
77 COMMENT "Generating config file ${source}"
78 )
79
80 install(
81 FILES ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${name}
82 DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/${destination}
83 COMPONENT configuration
84 )
85
86 add_custom_target(config-${name} DEPENDS ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${name})
87 add_dependencies(configs config-${name})
88endfunction()
89
90add_config(config config sway)
91add_config(security config.d/security sway/config.d)
70 92
71add_manpage(sway 1) 93add_manpage(sway 1)
72add_manpage(sway 5) 94add_manpage(sway 5)
73add_manpage(sway-input 5) 95add_manpage(sway-input 5)
74add_manpage(sway-bar 5) 96add_manpage(sway-bar 5)
97add_manpage(sway-security 7)
diff --git a/sway/commands.c b/sway/commands.c
index de29a7af..d87d0084 100644
--- a/sway/commands.c
+++ b/sway/commands.c
@@ -26,6 +26,7 @@
26#include "sway/input_state.h" 26#include "sway/input_state.h"
27#include "sway/criteria.h" 27#include "sway/criteria.h"
28#include "sway/ipc-server.h" 28#include "sway/ipc-server.h"
29#include "sway/security.h"
29#include "sway/input.h" 30#include "sway/input.h"
30#include "sway/border.h" 31#include "sway/border.h"
31#include "stringop.h" 32#include "stringop.h"
@@ -158,6 +159,7 @@ static struct cmd_handler handlers[] = {
158 { "client.placeholder", cmd_client_placeholder }, 159 { "client.placeholder", cmd_client_placeholder },
159 { "client.unfocused", cmd_client_unfocused }, 160 { "client.unfocused", cmd_client_unfocused },
160 { "client.urgent", cmd_client_urgent }, 161 { "client.urgent", cmd_client_urgent },
162 { "commands", cmd_commands },
161 { "debuglog", cmd_debuglog }, 163 { "debuglog", cmd_debuglog },
162 { "default_orientation", cmd_orientation }, 164 { "default_orientation", cmd_orientation },
163 { "exec", cmd_exec }, 165 { "exec", cmd_exec },
@@ -178,6 +180,7 @@ static struct cmd_handler handlers[] = {
178 { "hide_edge_borders", cmd_hide_edge_borders }, 180 { "hide_edge_borders", cmd_hide_edge_borders },
179 { "include", cmd_include }, 181 { "include", cmd_include },
180 { "input", cmd_input }, 182 { "input", cmd_input },
183 { "ipc", cmd_ipc },
181 { "kill", cmd_kill }, 184 { "kill", cmd_kill },
182 { "layout", cmd_layout }, 185 { "layout", cmd_layout },
183 { "log_colors", cmd_log_colors }, 186 { "log_colors", cmd_log_colors },
@@ -187,6 +190,8 @@ static struct cmd_handler handlers[] = {
187 { "new_float", cmd_new_float }, 190 { "new_float", cmd_new_float },
188 { "new_window", cmd_new_window }, 191 { "new_window", cmd_new_window },
189 { "output", cmd_output }, 192 { "output", cmd_output },
193 { "permit", cmd_permit },
194 { "reject", cmd_reject },
190 { "reload", cmd_reload }, 195 { "reload", cmd_reload },
191 { "resize", cmd_resize }, 196 { "resize", cmd_resize },
192 { "scratchpad", cmd_scratchpad }, 197 { "scratchpad", cmd_scratchpad },
@@ -288,6 +293,26 @@ static struct cmd_handler bar_colors_handlers[] = {
288 { "urgent_workspace", bar_colors_cmd_urgent_workspace }, 293 { "urgent_workspace", bar_colors_cmd_urgent_workspace },
289}; 294};
290 295
296static struct cmd_handler ipc_handlers[] = {
297 { "bar-config", cmd_ipc_cmd },
298 { "command", cmd_ipc_cmd },
299 { "events", cmd_ipc_events },
300 { "inputs", cmd_ipc_cmd },
301 { "marks", cmd_ipc_cmd },
302 { "outputs", cmd_ipc_cmd },
303 { "tree", cmd_ipc_cmd },
304 { "workspaces", cmd_ipc_cmd },
305};
306
307static struct cmd_handler ipc_event_handlers[] = {
308 { "binding", cmd_ipc_event_cmd },
309 { "input", cmd_ipc_event_cmd },
310 { "mode", cmd_ipc_event_cmd },
311 { "output", cmd_ipc_event_cmd },
312 { "window", cmd_ipc_event_cmd },
313 { "workspace", cmd_ipc_event_cmd },
314};
315
291static int handler_compare(const void *_a, const void *_b) { 316static int handler_compare(const void *_a, const void *_b) {
292 const struct cmd_handler *a = _a; 317 const struct cmd_handler *a = _a;
293 const struct cmd_handler *b = _b; 318 const struct cmd_handler *b = _b;
@@ -307,10 +332,17 @@ static struct cmd_handler *find_handler(char *line, enum cmd_status block) {
307 sizeof(bar_colors_handlers) / sizeof(struct cmd_handler), 332 sizeof(bar_colors_handlers) / sizeof(struct cmd_handler),
308 sizeof(struct cmd_handler), handler_compare); 333 sizeof(struct cmd_handler), handler_compare);
309 } else if (block == CMD_BLOCK_INPUT) { 334 } else if (block == CMD_BLOCK_INPUT) {
310 sway_log(L_DEBUG, "looking at input handlers");
311 res = bsearch(&d, input_handlers, 335 res = bsearch(&d, input_handlers,
312 sizeof(input_handlers) / sizeof(struct cmd_handler), 336 sizeof(input_handlers) / sizeof(struct cmd_handler),
313 sizeof(struct cmd_handler), handler_compare); 337 sizeof(struct cmd_handler), handler_compare);
338 } else if (block == CMD_BLOCK_IPC) {
339 res = bsearch(&d, ipc_handlers,
340 sizeof(ipc_handlers) / sizeof(struct cmd_handler),
341 sizeof(struct cmd_handler), handler_compare);
342 } else if (block == CMD_BLOCK_IPC_EVENTS) {
343 res = bsearch(&d, ipc_event_handlers,
344 sizeof(ipc_event_handlers) / sizeof(struct cmd_handler),
345 sizeof(struct cmd_handler), handler_compare);
314 } else { 346 } else {
315 res = bsearch(&d, handlers, 347 res = bsearch(&d, handlers,
316 sizeof(handlers) / sizeof(struct cmd_handler), 348 sizeof(handlers) / sizeof(struct cmd_handler),
@@ -319,7 +351,7 @@ static struct cmd_handler *find_handler(char *line, enum cmd_status block) {
319 return res; 351 return res;
320} 352}
321 353
322struct cmd_results *handle_command(char *_exec) { 354struct cmd_results *handle_command(char *_exec, enum command_context context) {
323 // Even though this function will process multiple commands we will only 355 // Even though this function will process multiple commands we will only
324 // return the last error, if any (for now). (Since we have access to an 356 // return the last error, if any (for now). (Since we have access to an
325 // error string we could e.g. concatonate all errors there.) 357 // error string we could e.g. concatonate all errors there.)
@@ -393,6 +425,16 @@ struct cmd_results *handle_command(char *_exec) {
393 free_argv(argc, argv); 425 free_argv(argc, argv);
394 goto cleanup; 426 goto cleanup;
395 } 427 }
428 if (!(get_command_policy(argv[0]) & context)) {
429 if (results) {
430 free_cmd_results(results);
431 }
432 results = cmd_results_new(CMD_INVALID, cmd,
433 "Permission denied for %s via %s", cmd,
434 command_policy_str(context));
435 free_argv(argc, argv);
436 goto cleanup;
437 }
396 struct cmd_results *res = handler->handle(argc-1, argv+1); 438 struct cmd_results *res = handler->handle(argc-1, argv+1);
397 if (res->status != CMD_SUCCESS) { 439 if (res->status != CMD_SUCCESS) {
398 free_argv(argc, argv); 440 free_argv(argc, argv);
@@ -458,7 +500,84 @@ struct cmd_results *config_command(char *exec, enum cmd_status block) {
458 } else { 500 } else {
459 results = cmd_results_new(CMD_INVALID, argv[0], "This command is shimmed, but unimplemented"); 501 results = cmd_results_new(CMD_INVALID, argv[0], "This command is shimmed, but unimplemented");
460 } 502 }
461 cleanup: 503
504cleanup:
505 free_argv(argc, argv);
506 return results;
507}
508
509struct cmd_results *config_commands_command(char *exec) {
510 struct cmd_results *results = NULL;
511 int argc;
512 char **argv = split_args(exec, &argc);
513 if (!argc) {
514 results = cmd_results_new(CMD_SUCCESS, NULL, NULL);
515 goto cleanup;
516 }
517
518 // Find handler for the command this is setting a policy for
519 char *cmd = argv[0];
520
521 if (strcmp(cmd, "}") == 0) {
522 results = cmd_results_new(CMD_BLOCK_END, NULL, NULL);
523 goto cleanup;
524 }
525
526 struct cmd_handler *handler = find_handler(cmd, CMD_BLOCK_END);
527 if (!handler && strcmp(cmd, "*") != 0) {
528 char *input = cmd ? cmd : "(empty)";
529 results = cmd_results_new(CMD_INVALID, input, "Unknown/invalid command");
530 goto cleanup;
531 }
532
533 enum command_context context = 0;
534
535 struct {
536 char *name;
537 enum command_context context;
538 } context_names[] = {
539 { "config", CONTEXT_CONFIG },
540 { "binding", CONTEXT_BINDING },
541 { "ipc", CONTEXT_IPC },
542 { "criteria", CONTEXT_CRITERIA },
543 { "all", CONTEXT_ALL },
544 };
545
546 for (int i = 1; i < argc; ++i) {
547 size_t j;
548 for (j = 0; j < sizeof(context_names) / sizeof(context_names[0]); ++j) {
549 if (strcmp(context_names[j].name, argv[i]) == 0) {
550 break;
551 }
552 }
553 if (j == sizeof(context_names) / sizeof(context_names[0])) {
554 results = cmd_results_new(CMD_INVALID, cmd,
555 "Invalid command context %s", argv[i]);
556 goto cleanup;
557 }
558 context |= context_names[j].context;
559 }
560
561 struct command_policy *policy = NULL;
562 for (int i = 0; i < config->command_policies->length; ++i) {
563 struct command_policy *p = config->command_policies->items[i];
564 if (strcmp(p->command, cmd) == 0) {
565 policy = p;
566 break;
567 }
568 }
569 if (!policy) {
570 policy = alloc_command_policy(cmd);
571 list_add(config->command_policies, policy);
572 }
573 policy->context = context;
574
575 sway_log(L_INFO, "Set command policy for %s to %d",
576 policy->command, policy->context);
577
578 results = cmd_results_new(CMD_SUCCESS, NULL, NULL);
579
580cleanup:
462 free_argv(argc, argv); 581 free_argv(argc, argv);
463 return results; 582 return results;
464} 583}
diff --git a/sway/commands/commands.c b/sway/commands/commands.c
new file mode 100644
index 00000000..5d248e30
--- /dev/null
+++ b/sway/commands/commands.c
@@ -0,0 +1,23 @@
1#include <stdbool.h>
2#include <string.h>
3#include "sway/commands.h"
4#include "sway/config.h"
5#include "list.h"
6#include "log.h"
7
8struct cmd_results *cmd_commands(int argc, char **argv) {
9 struct cmd_results *error = NULL;
10 if ((error = checkarg(argc, "commands", EXPECTED_EQUAL_TO, 1))) {
11 return error;
12 }
13
14 if (strcmp(argv[0], "{") != 0) {
15 return cmd_results_new(CMD_FAILURE, "commands", "Expected block declaration");
16 }
17
18 if (!config->reading) {
19 return cmd_results_new(CMD_FAILURE, "commands", "Can only be used in config file.");
20 }
21
22 return cmd_results_new(CMD_BLOCK_COMMANDS, NULL, NULL);
23}
diff --git a/sway/commands/ipc.c b/sway/commands/ipc.c
new file mode 100644
index 00000000..222be0dd
--- /dev/null
+++ b/sway/commands/ipc.c
@@ -0,0 +1,140 @@
1#include <stdio.h>
2#include <string.h>
3#include "sway/commands.h"
4#include "sway/config.h"
5#include "ipc.h"
6#include "log.h"
7#include "util.h"
8
9struct cmd_results *cmd_ipc(int argc, char **argv) {
10 struct cmd_results *error = NULL;
11 if ((error = checkarg(argc, "ipc", EXPECTED_EQUAL_TO, 1))) {
12 return error;
13 }
14
15 if (config->reading && strcmp("{", argv[0]) != 0) {
16 return cmd_results_new(CMD_INVALID, "ipc",
17 "Expected '{' at start of IPC config definition.");
18 }
19
20 if (!config->reading) {
21 return cmd_results_new(CMD_FAILURE, "ipc", "Can only be used in config file.");
22 }
23
24 return cmd_results_new(CMD_BLOCK_IPC, NULL, NULL);
25}
26
27struct cmd_results *cmd_ipc_events(int argc, char **argv) {
28 struct cmd_results *error = NULL;
29 if ((error = checkarg(argc, "events", EXPECTED_EQUAL_TO, 1))) {
30 return error;
31 }
32
33 if (config->reading && strcmp("{", argv[0]) != 0) {
34 return cmd_results_new(CMD_INVALID, "events",
35 "Expected '{' at start of IPC event config definition.");
36 }
37
38 if (!config->reading) {
39 return cmd_results_new(CMD_FAILURE, "events", "Can only be used in config file.");
40 }
41
42 return cmd_results_new(CMD_BLOCK_IPC_EVENTS, NULL, NULL);
43}
44
45struct cmd_results *cmd_ipc_cmd(int argc, char **argv) {
46 struct cmd_results *error = NULL;
47 if ((error = checkarg(argc, "ipc", EXPECTED_EQUAL_TO, 1))) {
48 return error;
49 }
50
51 bool enabled;
52 if (strcmp(argv[0], "enabled") == 0) {
53 enabled = true;
54 } else if (strcmp(argv[0], "disabled") == 0) {
55 enabled = false;
56 } else {
57 return cmd_results_new(CMD_INVALID, argv[-1],
58 "Argument must be one of 'enabled' or 'disabled'");
59 }
60
61 struct {
62 char *name;
63 enum ipc_feature type;
64 } types[] = {
65 { "command", IPC_FEATURE_COMMAND },
66 { "workspaces", IPC_FEATURE_GET_WORKSPACES },
67 { "outputs", IPC_FEATURE_GET_OUTPUTS },
68 { "tree", IPC_FEATURE_GET_TREE },
69 { "marks", IPC_FEATURE_GET_MARKS },
70 { "bar-config", IPC_FEATURE_GET_BAR_CONFIG },
71 { "inputs", IPC_FEATURE_GET_INPUTS },
72 };
73
74 uint32_t type = 0;
75
76 for (size_t i = 0; i < sizeof(types) / sizeof(types[0]); ++i) {
77 if (strcmp(types[i].name, argv[-1]) == 0) {
78 type = types[i].type;
79 break;
80 }
81 }
82
83 if (enabled) {
84 config->ipc_policy |= type;
85 sway_log(L_DEBUG, "Enabled IPC %s feature", argv[-1]);
86 } else {
87 config->ipc_policy &= ~type;
88 sway_log(L_DEBUG, "Disabled IPC %s feature", argv[-1]);
89 }
90
91 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
92}
93
94struct cmd_results *cmd_ipc_event_cmd(int argc, char **argv) {
95 struct cmd_results *error = NULL;
96 if ((error = checkarg(argc, "ipc", EXPECTED_EQUAL_TO, 1))) {
97 return error;
98 }
99
100 bool enabled;
101 if (strcmp(argv[0], "enabled") == 0) {
102 enabled = true;
103 } else if (strcmp(argv[0], "disabled") == 0) {
104 enabled = false;
105 } else {
106 return cmd_results_new(CMD_INVALID, argv[-1],
107 "Argument must be one of 'enabled' or 'disabled'");
108 }
109
110 struct {
111 char *name;
112 enum ipc_feature type;
113 } types[] = {
114 { "workspace", IPC_FEATURE_EVENT_WORKSPACE },
115 { "output", IPC_FEATURE_EVENT_OUTPUT },
116 { "mode", IPC_FEATURE_EVENT_MODE },
117 { "window", IPC_FEATURE_EVENT_WINDOW },
118 { "binding", IPC_FEATURE_EVENT_BINDING },
119 { "input", IPC_FEATURE_EVENT_INPUT },
120 };
121
122 uint32_t type = 0;
123
124 for (size_t i = 0; i < sizeof(types) / sizeof(types[0]); ++i) {
125 if (strcmp(types[i].name, argv[-1]) == 0) {
126 type = types[i].type;
127 break;
128 }
129 }
130
131 if (enabled) {
132 config->ipc_policy |= type;
133 sway_log(L_DEBUG, "Enabled IPC %s event", argv[-1]);
134 } else {
135 config->ipc_policy &= ~type;
136 sway_log(L_DEBUG, "Disabled IPC %s event", argv[-1]);
137 }
138
139 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
140}
diff --git a/sway/commands/permit.c b/sway/commands/permit.c
new file mode 100644
index 00000000..7a25e4ce
--- /dev/null
+++ b/sway/commands/permit.c
@@ -0,0 +1,94 @@
1#include <string.h>
2#include "sway/commands.h"
3#include "sway/config.h"
4#include "sway/security.h"
5#include "log.h"
6
7static enum secure_feature get_features(int argc, char **argv,
8 struct cmd_results **error) {
9 enum secure_feature features = 0;
10
11 struct {
12 char *name;
13 enum secure_feature feature;
14 } feature_names[] = {
15 { "lock", FEATURE_LOCK },
16 { "panel", FEATURE_PANEL },
17 { "background", FEATURE_BACKGROUND },
18 { "screenshot", FEATURE_SCREENSHOT },
19 { "fullscreen", FEATURE_FULLSCREEN },
20 { "keyboard", FEATURE_KEYBOARD },
21 { "mouse", FEATURE_MOUSE },
22 { "ipc", FEATURE_IPC },
23 };
24
25 for (int i = 1; i < argc; ++i) {
26 size_t j;
27 for (j = 0; j < sizeof(feature_names) / sizeof(feature_names[0]); ++j) {
28 if (strcmp(feature_names[j].name, argv[i]) == 0) {
29 break;
30 }
31 }
32 if (j == sizeof(feature_names) / sizeof(feature_names[0])) {
33 *error = cmd_results_new(CMD_INVALID,
34 "permit", "Invalid feature grant %s", argv[i]);
35 return 0;
36 }
37 features |= feature_names[j].feature;
38 }
39 return features;
40}
41
42static struct feature_policy *get_policy(const char *name) {
43 struct feature_policy *policy = NULL;
44 for (int i = 0; i < config->feature_policies->length; ++i) {
45 struct feature_policy *p = config->feature_policies->items[i];
46 if (strcmp(p->program, name) == 0) {
47 policy = p;
48 break;
49 }
50 }
51 if (!policy) {
52 policy = alloc_feature_policy(name);
53 list_add(config->feature_policies, policy);
54 }
55 return policy;
56}
57
58struct cmd_results *cmd_permit(int argc, char **argv) {
59 struct cmd_results *error = NULL;
60 if ((error = checkarg(argc, "permit", EXPECTED_MORE_THAN, 1))) {
61 return error;
62 }
63
64 struct feature_policy *policy = get_policy(argv[0]);
65 policy->features |= get_features(argc, argv, &error);
66
67 if (error) {
68 return error;
69 }
70
71 sway_log(L_DEBUG, "Permissions granted to %s for features %d",
72 policy->program, policy->features);
73
74 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
75}
76
77struct cmd_results *cmd_reject(int argc, char **argv) {
78 struct cmd_results *error = NULL;
79 if ((error = checkarg(argc, "reject", EXPECTED_MORE_THAN, 1))) {
80 return error;
81 }
82
83 struct feature_policy *policy = get_policy(argv[0]);
84 policy->features &= ~get_features(argc, argv, &error);
85
86 if (error) {
87 return error;
88 }
89
90 sway_log(L_DEBUG, "Permissions granted to %s for features %d",
91 policy->program, policy->features);
92
93 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
94}
diff --git a/sway/config.c b/sway/config.c
index 7d5999d8..e737f83c 100644
--- a/sway/config.c
+++ b/sway/config.c
@@ -167,6 +167,16 @@ void free_pid_workspace(struct pid_workspace *pw) {
167 free(pw); 167 free(pw);
168} 168}
169 169
170void free_command_policy(struct command_policy *policy) {
171 free(policy->command);
172 free(policy);
173}
174
175void free_feature_policy(struct feature_policy *policy) {
176 free(policy->program);
177 free(policy);
178}
179
170void free_config(struct sway_config *config) { 180void free_config(struct sway_config *config) {
171 int i; 181 int i;
172 for (i = 0; i < config->symbols->length; ++i) { 182 for (i = 0; i < config->symbols->length; ++i) {
@@ -211,6 +221,16 @@ void free_config(struct sway_config *config) {
211 } 221 }
212 list_free(config->output_configs); 222 list_free(config->output_configs);
213 223
224 for (i = 0; i < config->command_policies->length; ++i) {
225 free_command_policy(config->command_policies->items[i]);
226 }
227 list_free(config->command_policies);
228
229 for (i = 0; i < config->feature_policies->length; ++i) {
230 free_feature_policy(config->feature_policies->items[i]);
231 }
232 list_free(config->feature_policies);
233
214 list_free(config->active_bar_modifiers); 234 list_free(config->active_bar_modifiers);
215 free_flat_list(config->config_chain); 235 free_flat_list(config->config_chain);
216 free(config->font); 236 free(config->font);
@@ -321,6 +341,11 @@ static void config_defaults(struct sway_config *config) {
321 config->border_colors.placeholder.child_border = 0x0C0C0CFF; 341 config->border_colors.placeholder.child_border = 0x0C0C0CFF;
322 342
323 config->border_colors.background = 0xFFFFFFFF; 343 config->border_colors.background = 0xFFFFFFFF;
344
345 // Security
346 config->command_policies = create_list();
347 config->feature_policies = create_list();
348 config->ipc_policy = UINT32_MAX;
324} 349}
325 350
326static int compare_modifiers(const void *left, const void *right) { 351static int compare_modifiers(const void *left, const void *right) {
@@ -556,7 +581,13 @@ bool read_config(FILE *file, struct sway_config *config) {
556 free(line); 581 free(line);
557 continue; 582 continue;
558 } 583 }
559 struct cmd_results *res = config_command(line, block); 584 struct cmd_results *res;
585 if (block == CMD_BLOCK_COMMANDS) {
586 // Special case
587 res = config_commands_command(line);
588 } else {
589 res = config_command(line, block);
590 }
560 switch(res->status) { 591 switch(res->status) {
561 case CMD_FAILURE: 592 case CMD_FAILURE:
562 case CMD_INVALID: 593 case CMD_INVALID:
@@ -602,6 +633,30 @@ bool read_config(FILE *file, struct sway_config *config) {
602 } 633 }
603 break; 634 break;
604 635
636 case CMD_BLOCK_COMMANDS:
637 if (block == CMD_BLOCK_END) {
638 block = CMD_BLOCK_COMMANDS;
639 } else {
640 sway_log(L_ERROR, "Invalid block '%s'", line);
641 }
642 break;
643
644 case CMD_BLOCK_IPC:
645 if (block == CMD_BLOCK_END) {
646 block = CMD_BLOCK_IPC;
647 } else {
648 sway_log(L_ERROR, "Invalid block '%s'", line);
649 }
650 break;
651
652 case CMD_BLOCK_IPC_EVENTS:
653 if (block == CMD_BLOCK_IPC) {
654 block = CMD_BLOCK_IPC_EVENTS;
655 } else {
656 sway_log(L_ERROR, "Invalid block '%s'", line);
657 }
658 break;
659
605 case CMD_BLOCK_END: 660 case CMD_BLOCK_END:
606 switch(block) { 661 switch(block) {
607 case CMD_BLOCK_MODE: 662 case CMD_BLOCK_MODE:
@@ -627,6 +682,21 @@ bool read_config(FILE *file, struct sway_config *config) {
627 block = CMD_BLOCK_BAR; 682 block = CMD_BLOCK_BAR;
628 break; 683 break;
629 684
685 case CMD_BLOCK_COMMANDS:
686 sway_log(L_DEBUG, "End of commands block");
687 block = CMD_BLOCK_END;
688 break;
689
690 case CMD_BLOCK_IPC:
691 sway_log(L_DEBUG, "End of IPC block");
692 block = CMD_BLOCK_END;
693 break;
694
695 case CMD_BLOCK_IPC_EVENTS:
696 sway_log(L_DEBUG, "End of IPC events block");
697 block = CMD_BLOCK_IPC;
698 break;
699
630 case CMD_BLOCK_END: 700 case CMD_BLOCK_END:
631 sway_log(L_ERROR, "Unmatched }"); 701 sway_log(L_ERROR, "Unmatched }");
632 break; 702 break;
diff --git a/sway/extensions.c b/sway/extensions.c
index 60cd8d41..96c7e60d 100644
--- a/sway/extensions.c
+++ b/sway/extensions.c
@@ -7,6 +7,7 @@
7#include "sway/layout.h" 7#include "sway/layout.h"
8#include "sway/input_state.h" 8#include "sway/input_state.h"
9#include "sway/extensions.h" 9#include "sway/extensions.h"
10#include "sway/security.h"
10#include "sway/ipc-server.h" 11#include "sway/ipc-server.h"
11#include "log.h" 12#include "log.h"
12 13
@@ -68,6 +69,12 @@ void lock_surface_destructor(struct wl_resource *resource) {
68 69
69static void set_background(struct wl_client *client, struct wl_resource *resource, 70static void set_background(struct wl_client *client, struct wl_resource *resource,
70 struct wl_resource *_output, struct wl_resource *surface) { 71 struct wl_resource *_output, struct wl_resource *surface) {
72 pid_t pid;
73 wl_client_get_credentials(client, &pid, NULL, NULL);
74 if (!(get_feature_policy(pid) & FEATURE_BACKGROUND)) {
75 sway_log(L_INFO, "Denying background feature to %d", pid);
76 return;
77 }
71 wlc_handle output = wlc_handle_from_wl_output_resource(_output); 78 wlc_handle output = wlc_handle_from_wl_output_resource(_output);
72 if (!output) { 79 if (!output) {
73 return; 80 return;
@@ -86,6 +93,12 @@ static void set_background(struct wl_client *client, struct wl_resource *resourc
86 93
87static void set_panel(struct wl_client *client, struct wl_resource *resource, 94static void set_panel(struct wl_client *client, struct wl_resource *resource,
88 struct wl_resource *_output, struct wl_resource *surface) { 95 struct wl_resource *_output, struct wl_resource *surface) {
96 pid_t pid;
97 wl_client_get_credentials(client, &pid, NULL, NULL);
98 if (!(get_feature_policy(pid) & FEATURE_PANEL)) {
99 sway_log(L_INFO, "Denying panel feature to %d", pid);
100 return;
101 }
89 wlc_handle output = wlc_handle_from_wl_output_resource(_output); 102 wlc_handle output = wlc_handle_from_wl_output_resource(_output);
90 if (!output) { 103 if (!output) {
91 return; 104 return;
@@ -111,6 +124,12 @@ static void desktop_unlock(struct wl_client *client, struct wl_resource *resourc
111 124
112static void set_lock_surface(struct wl_client *client, struct wl_resource *resource, 125static void set_lock_surface(struct wl_client *client, struct wl_resource *resource,
113 struct wl_resource *_output, struct wl_resource *surface) { 126 struct wl_resource *_output, struct wl_resource *surface) {
127 pid_t pid;
128 wl_client_get_credentials(client, &pid, NULL, NULL);
129 if (!(get_feature_policy(pid) & FEATURE_LOCK)) {
130 sway_log(L_INFO, "Denying lock feature to %d", pid);
131 return;
132 }
114 swayc_t *output = swayc_by_handle(wlc_handle_from_wl_output_resource(_output)); 133 swayc_t *output = swayc_by_handle(wlc_handle_from_wl_output_resource(_output));
115 swayc_t *view = swayc_by_handle(wlc_handle_from_wl_surface_resource(surface)); 134 swayc_t *view = swayc_by_handle(wlc_handle_from_wl_surface_resource(surface));
116 sway_log(L_DEBUG, "Setting lock surface to %p", view); 135 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
155} 174}
156 175
157static void set_panel_position(struct wl_client *client, struct wl_resource *resource, uint32_t position) { 176static void set_panel_position(struct wl_client *client, struct wl_resource *resource, uint32_t position) {
177 pid_t pid;
178 wl_client_get_credentials(client, &pid, NULL, NULL);
179 if (!(get_feature_policy(pid) & FEATURE_PANEL)) {
180 sway_log(L_INFO, "Denying panel feature to %d", pid);
181 return;
182 }
158 struct panel_config *config = find_or_create_panel_config(resource); 183 struct panel_config *config = find_or_create_panel_config(resource);
159 sway_log(L_DEBUG, "Panel position for wl_resource %p changed %d => %d", resource, config->panel_position, position); 184 sway_log(L_DEBUG, "Panel position for wl_resource %p changed %d => %d", resource, config->panel_position, position);
160 config->panel_position = position; 185 config->panel_position = position;
diff --git a/sway/handlers.c b/sway/handlers.c
index 2235bc8b..ee52ba38 100644
--- a/sway/handlers.c
+++ b/sway/handlers.c
@@ -21,6 +21,7 @@
21#include "sway/criteria.h" 21#include "sway/criteria.h"
22#include "sway/ipc-server.h" 22#include "sway/ipc-server.h"
23#include "sway/input.h" 23#include "sway/input.h"
24#include "sway/security.h"
24#include "list.h" 25#include "list.h"
25#include "stringop.h" 26#include "stringop.h"
26#include "log.h" 27#include "log.h"
@@ -385,7 +386,7 @@ static bool handle_view_created(wlc_handle handle) {
385 struct criteria *crit = criteria->items[i]; 386 struct criteria *crit = criteria->items[i];
386 sway_log(L_DEBUG, "for_window '%s' matches new view %p, cmd: '%s'", 387 sway_log(L_DEBUG, "for_window '%s' matches new view %p, cmd: '%s'",
387 crit->crit_raw, newview, crit->cmdlist); 388 crit->crit_raw, newview, crit->cmdlist);
388 struct cmd_results *res = handle_command(crit->cmdlist); 389 struct cmd_results *res = handle_command(crit->cmdlist, CONTEXT_CRITERIA);
389 if (res->status != CMD_SUCCESS) { 390 if (res->status != CMD_SUCCESS) {
390 sway_log(L_ERROR, "Command '%s' failed: %s", res->input, res->error); 391 sway_log(L_ERROR, "Command '%s' failed: %s", res->input, res->error);
391 } 392 }
@@ -516,8 +517,13 @@ static void handle_view_geometry_request(wlc_handle handle, const struct wlc_geo
516 517
517static void handle_view_state_request(wlc_handle view, enum wlc_view_state_bit state, bool toggle) { 518static void handle_view_state_request(wlc_handle view, enum wlc_view_state_bit state, bool toggle) {
518 swayc_t *c = swayc_by_handle(view); 519 swayc_t *c = swayc_by_handle(view);
520 pid_t pid = wlc_view_get_pid(view);
519 switch (state) { 521 switch (state) {
520 case WLC_BIT_FULLSCREEN: 522 case WLC_BIT_FULLSCREEN:
523 if (!(get_feature_policy(pid) & FEATURE_FULLSCREEN)) {
524 sway_log(L_INFO, "Denying fullscreen to %d (%s)", pid, c->name);
525 break;
526 }
521 // i3 just lets it become fullscreen 527 // i3 just lets it become fullscreen
522 wlc_view_set_state(view, state, toggle); 528 wlc_view_set_state(view, state, toggle);
523 if (c) { 529 if (c) {
@@ -579,7 +585,7 @@ static void handle_binding_command(struct sway_binding *binding) {
579 reload = true; 585 reload = true;
580 } 586 }
581 587
582 struct cmd_results *res = handle_command(binding->command); 588 struct cmd_results *res = handle_command(binding->command, CONTEXT_BINDING);
583 if (res->status != CMD_SUCCESS) { 589 if (res->status != CMD_SUCCESS) {
584 sway_log(L_ERROR, "Command '%s' failed: %s", res->input, res->error); 590 sway_log(L_ERROR, "Command '%s' failed: %s", res->input, res->error);
585 } 591 }
@@ -719,6 +725,14 @@ static bool handle_key(wlc_handle view, uint32_t time, const struct wlc_modifier
719 } 725 }
720 726
721 list_free(candidates); 727 list_free(candidates);
728
729 swayc_t *focused = get_focused_container(&root_container);
730 if (focused->type == C_VIEW) {
731 pid_t pid = wlc_view_get_pid(focused->handle);
732 if (!(get_feature_policy(pid) & FEATURE_KEYBOARD)) {
733 return EVENT_HANDLED;
734 }
735 }
722 return EVENT_PASSTHROUGH; 736 return EVENT_PASSTHROUGH;
723} 737}
724 738
@@ -775,6 +789,15 @@ static bool handle_pointer_motion(wlc_handle handle, uint32_t time, const struct
775 } 789 }
776 790
777 pointer_position_set(&new_origin, false); 791 pointer_position_set(&new_origin, false);
792
793 swayc_t *focused = get_focused_container(&root_container);
794 if (focused->type == C_VIEW) {
795 pid_t pid = wlc_view_get_pid(focused->handle);
796 if (!(get_feature_policy(pid) & FEATURE_MOUSE)) {
797 return EVENT_HANDLED;
798 }
799 }
800
778 return EVENT_PASSTHROUGH; 801 return EVENT_PASSTHROUGH;
779} 802}
780 803
@@ -842,6 +865,12 @@ static bool handle_pointer_button(wlc_handle view, uint32_t time, const struct w
842 865
843 // don't change focus or mode if fullscreen 866 // don't change focus or mode if fullscreen
844 if (swayc_is_fullscreen(focused)) { 867 if (swayc_is_fullscreen(focused)) {
868 if (focused->type == C_VIEW) {
869 pid_t pid = wlc_view_get_pid(focused->handle);
870 if (!(get_feature_policy(pid) & FEATURE_MOUSE)) {
871 return EVENT_HANDLED;
872 }
873 }
845 return EVENT_PASSTHROUGH; 874 return EVENT_PASSTHROUGH;
846 } 875 }
847 876
@@ -884,6 +913,13 @@ static bool handle_pointer_button(wlc_handle view, uint32_t time, const struct w
884 return EVENT_HANDLED; 913 return EVENT_HANDLED;
885 } 914 }
886 915
916 if (focused->type == C_VIEW) {
917 pid_t pid = wlc_view_get_pid(focused->handle);
918 if (!(get_feature_policy(pid) & FEATURE_MOUSE)) {
919 return EVENT_HANDLED;
920 }
921 }
922
887 // Always send mouse release 923 // Always send mouse release
888 if (state == WLC_BUTTON_STATE_RELEASED) { 924 if (state == WLC_BUTTON_STATE_RELEASED) {
889 return EVENT_PASSTHROUGH; 925 return EVENT_PASSTHROUGH;
@@ -900,18 +936,18 @@ bool handle_pointer_scroll(wlc_handle view, uint32_t time, const struct wlc_modi
900 int y_amount = (int)_amount[1]; 936 int y_amount = (int)_amount[1];
901 937
902 if (x_amount > 0 && strcmp(config->floating_scroll_up_cmd, "")) { 938 if (x_amount > 0 && strcmp(config->floating_scroll_up_cmd, "")) {
903 handle_command(config->floating_scroll_up_cmd); 939 handle_command(config->floating_scroll_up_cmd, CONTEXT_BINDING);
904 return EVENT_HANDLED; 940 return EVENT_HANDLED;
905 } else if (x_amount < 0 && strcmp(config->floating_scroll_down_cmd, "")) { 941 } else if (x_amount < 0 && strcmp(config->floating_scroll_down_cmd, "")) {
906 handle_command(config->floating_scroll_down_cmd); 942 handle_command(config->floating_scroll_down_cmd, CONTEXT_BINDING);
907 return EVENT_HANDLED; 943 return EVENT_HANDLED;
908 } 944 }
909 945
910 if (y_amount > 0 && strcmp(config->floating_scroll_right_cmd, "")) { 946 if (y_amount > 0 && strcmp(config->floating_scroll_right_cmd, "")) {
911 handle_command(config->floating_scroll_right_cmd); 947 handle_command(config->floating_scroll_right_cmd, CONTEXT_BINDING);
912 return EVENT_HANDLED; 948 return EVENT_HANDLED;
913 } else if (y_amount < 0 && strcmp(config->floating_scroll_left_cmd, "")) { 949 } else if (y_amount < 0 && strcmp(config->floating_scroll_left_cmd, "")) {
914 handle_command(config->floating_scroll_left_cmd); 950 handle_command(config->floating_scroll_left_cmd, CONTEXT_BINDING);
915 return EVENT_HANDLED; 951 return EVENT_HANDLED;
916 } 952 }
917 } 953 }
@@ -924,7 +960,7 @@ static void handle_wlc_ready(void) {
924 config->active = true; 960 config->active = true;
925 while (config->cmd_queue->length) { 961 while (config->cmd_queue->length) {
926 char *line = config->cmd_queue->items[0]; 962 char *line = config->cmd_queue->items[0];
927 struct cmd_results *res = handle_command(line); 963 struct cmd_results *res = handle_command(line, CONTEXT_CONFIG);
928 if (res->status != CMD_SUCCESS) { 964 if (res->status != CMD_SUCCESS) {
929 sway_log(L_ERROR, "Error on line '%s': %s", line, res->error); 965 sway_log(L_ERROR, "Error on line '%s': %s", line, res->error);
930 } 966 }
diff --git a/sway/ipc-server.c b/sway/ipc-server.c
index ebb5ce58..c04c465a 100644
--- a/sway/ipc-server.c
+++ b/sway/ipc-server.c
@@ -15,6 +15,7 @@
15#include <libinput.h> 15#include <libinput.h>
16#include "sway/ipc-json.h" 16#include "sway/ipc-json.h"
17#include "sway/ipc-server.h" 17#include "sway/ipc-server.h"
18#include "sway/security.h"
18#include "sway/config.h" 19#include "sway/config.h"
19#include "sway/commands.h" 20#include "sway/commands.h"
20#include "sway/input.h" 21#include "sway/input.h"
@@ -55,8 +56,6 @@ bool ipc_send_reply(struct ipc_client *client, const char *payload, uint32_t pay
55void ipc_get_workspaces_callback(swayc_t *workspace, void *data); 56void ipc_get_workspaces_callback(swayc_t *workspace, void *data);
56void ipc_get_outputs_callback(swayc_t *container, void *data); 57void ipc_get_outputs_callback(swayc_t *container, void *data);
57 58
58#define event_mask(ev) (1 << (ev & 0x7F))
59
60void ipc_init(void) { 59void ipc_init(void) {
61 ipc_socket = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); 60 ipc_socket = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
62 if (ipc_socket == -1) { 61 if (ipc_socket == -1) {
@@ -126,6 +125,17 @@ struct sockaddr_un *ipc_user_sockaddr(void) {
126 return ipc_sockaddr; 125 return ipc_sockaddr;
127} 126}
128 127
128static pid_t get_client_pid(int client_fd) {
129 struct ucred ucred;
130 socklen_t len = sizeof(struct ucred);
131
132 if (getsockopt(client_fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == -1) {
133 return -1;
134 }
135
136 return ucred.pid;
137}
138
129int ipc_handle_connection(int fd, uint32_t mask, void *data) { 139int ipc_handle_connection(int fd, uint32_t mask, void *data) {
130 (void) fd; (void) data; 140 (void) fd; (void) data;
131 sway_log(L_DEBUG, "Event on IPC listening socket"); 141 sway_log(L_DEBUG, "Event on IPC listening socket");
@@ -144,6 +154,15 @@ int ipc_handle_connection(int fd, uint32_t mask, void *data) {
144 return 0; 154 return 0;
145 } 155 }
146 156
157 pid_t pid = get_client_pid(client_fd);
158 if (!(get_feature_policy(pid) & FEATURE_IPC)) {
159 sway_log(L_INFO, "Permission to connect to IPC socket denied to %d", pid);
160 const char *error = "{\"success\": false, \"message\": \"Permission denied\"}";
161 write(client_fd, &error, sizeof(error));
162 close(client_fd);
163 return 0;
164 }
165
147 struct ipc_client* client = malloc(sizeof(struct ipc_client)); 166 struct ipc_client* client = malloc(sizeof(struct ipc_client));
148 client->payload_length = 0; 167 client->payload_length = 0;
149 client->fd = client_fd; 168 client->fd = client_fd;
@@ -309,10 +328,15 @@ void ipc_client_handle_command(struct ipc_client *client) {
309 } 328 }
310 buf[client->payload_length] = '\0'; 329 buf[client->payload_length] = '\0';
311 330
331 const char *error_denied = "{ \"success\": false, \"error\": \"Permission denied\" }";
332
312 switch (client->current_command) { 333 switch (client->current_command) {
313 case IPC_COMMAND: 334 case IPC_COMMAND:
314 { 335 {
315 struct cmd_results *results = handle_command(buf); 336 if (!(config->ipc_policy & IPC_FEATURE_COMMAND)) {
337 goto exit_denied;
338 }
339 struct cmd_results *results = handle_command(buf, CONTEXT_IPC);
316 const char *json = cmd_results_to_json(results); 340 const char *json = cmd_results_to_json(results);
317 char reply[256]; 341 char reply[256];
318 int length = snprintf(reply, sizeof(reply), "%s", json); 342 int length = snprintf(reply, sizeof(reply), "%s", json);
@@ -343,10 +367,8 @@ void ipc_client_handle_command(struct ipc_client *client) {
343 client->subscribed_events |= event_mask(IPC_EVENT_WINDOW); 367 client->subscribed_events |= event_mask(IPC_EVENT_WINDOW);
344 } else if (strcmp(event_type, "modifier") == 0) { 368 } else if (strcmp(event_type, "modifier") == 0) {
345 client->subscribed_events |= event_mask(IPC_EVENT_MODIFIER); 369 client->subscribed_events |= event_mask(IPC_EVENT_MODIFIER);
346#if SWAY_BINDING_EVENT
347 } else if (strcmp(event_type, "binding") == 0) { 370 } else if (strcmp(event_type, "binding") == 0) {
348 client->subscribed_events |= event_mask(IPC_EVENT_BINDING); 371 client->subscribed_events |= event_mask(IPC_EVENT_BINDING);
349#endif
350 } else { 372 } else {
351 ipc_send_reply(client, "{\"success\": false}", 18); 373 ipc_send_reply(client, "{\"success\": false}", 18);
352 json_object_put(request); 374 json_object_put(request);
@@ -363,6 +385,9 @@ void ipc_client_handle_command(struct ipc_client *client) {
363 385
364 case IPC_GET_WORKSPACES: 386 case IPC_GET_WORKSPACES:
365 { 387 {
388 if (!(config->ipc_policy & IPC_FEATURE_GET_WORKSPACES)) {
389 goto exit_denied;
390 }
366 json_object *workspaces = json_object_new_array(); 391 json_object *workspaces = json_object_new_array();
367 container_map(&root_container, ipc_get_workspaces_callback, workspaces); 392 container_map(&root_container, ipc_get_workspaces_callback, workspaces);
368 const char *json_string = json_object_to_json_string(workspaces); 393 const char *json_string = json_object_to_json_string(workspaces);
@@ -373,6 +398,9 @@ void ipc_client_handle_command(struct ipc_client *client) {
373 398
374 case IPC_GET_INPUTS: 399 case IPC_GET_INPUTS:
375 { 400 {
401 if (!(config->ipc_policy & IPC_FEATURE_GET_INPUTS)) {
402 goto exit_denied;
403 }
376 json_object *inputs = json_object_new_array(); 404 json_object *inputs = json_object_new_array();
377 if (input_devices) { 405 if (input_devices) {
378 for(int i=0; i<input_devices->length; i++) { 406 for(int i=0; i<input_devices->length; i++) {
@@ -392,6 +420,9 @@ void ipc_client_handle_command(struct ipc_client *client) {
392 420
393 case IPC_GET_OUTPUTS: 421 case IPC_GET_OUTPUTS:
394 { 422 {
423 if (!(config->ipc_policy & IPC_FEATURE_GET_OUTPUTS)) {
424 goto exit_denied;
425 }
395 json_object *outputs = json_object_new_array(); 426 json_object *outputs = json_object_new_array();
396 container_map(&root_container, ipc_get_outputs_callback, outputs); 427 container_map(&root_container, ipc_get_outputs_callback, outputs);
397 const char *json_string = json_object_to_json_string(outputs); 428 const char *json_string = json_object_to_json_string(outputs);
@@ -402,6 +433,9 @@ void ipc_client_handle_command(struct ipc_client *client) {
402 433
403 case IPC_GET_TREE: 434 case IPC_GET_TREE:
404 { 435 {
436 if (!(config->ipc_policy & IPC_FEATURE_GET_TREE)) {
437 goto exit_denied;
438 }
405 json_object *tree = ipc_json_describe_container_recursive(&root_container); 439 json_object *tree = ipc_json_describe_container_recursive(&root_container);
406 const char *json_string = json_object_to_json_string(tree); 440 const char *json_string = json_object_to_json_string(tree);
407 ipc_send_reply(client, json_string, (uint32_t) strlen(json_string)); 441 ipc_send_reply(client, json_string, (uint32_t) strlen(json_string));
@@ -462,6 +496,9 @@ void ipc_client_handle_command(struct ipc_client *client) {
462 496
463 case IPC_GET_BAR_CONFIG: 497 case IPC_GET_BAR_CONFIG:
464 { 498 {
499 if (!(config->ipc_policy & IPC_FEATURE_GET_BAR_CONFIG)) {
500 goto exit_denied;
501 }
465 if (!buf[0]) { 502 if (!buf[0]) {
466 // Send list of configured bar IDs 503 // Send list of configured bar IDs
467 json_object *bars = json_object_new_array(); 504 json_object *bars = json_object_new_array();
@@ -502,6 +539,9 @@ void ipc_client_handle_command(struct ipc_client *client) {
502 goto exit_cleanup; 539 goto exit_cleanup;
503 } 540 }
504 541
542exit_denied:
543 ipc_send_reply(client, error_denied, (uint32_t)strlen(error_denied));
544
505exit_cleanup: 545exit_cleanup:
506 client->payload_length = 0; 546 client->payload_length = 0;
507 free(buf); 547 free(buf);
@@ -566,6 +606,9 @@ void ipc_send_event(const char *json_string, enum ipc_command_type event) {
566} 606}
567 607
568void ipc_event_workspace(swayc_t *old, swayc_t *new, const char *change) { 608void ipc_event_workspace(swayc_t *old, swayc_t *new, const char *change) {
609 if (!(config->ipc_policy & IPC_FEATURE_EVENT_WORKSPACE)) {
610 return;
611 }
569 sway_log(L_DEBUG, "Sending workspace::%s event", change); 612 sway_log(L_DEBUG, "Sending workspace::%s event", change);
570 json_object *obj = json_object_new_object(); 613 json_object *obj = json_object_new_object();
571 json_object_object_add(obj, "change", json_object_new_string(change)); 614 json_object_object_add(obj, "change", json_object_new_string(change));
@@ -590,6 +633,9 @@ void ipc_event_workspace(swayc_t *old, swayc_t *new, const char *change) {
590} 633}
591 634
592void ipc_event_window(swayc_t *window, const char *change) { 635void ipc_event_window(swayc_t *window, const char *change) {
636 if (!(config->ipc_policy & IPC_FEATURE_EVENT_WINDOW)) {
637 return;
638 }
593 sway_log(L_DEBUG, "Sending window::%s event", change); 639 sway_log(L_DEBUG, "Sending window::%s event", change);
594 json_object *obj = json_object_new_object(); 640 json_object *obj = json_object_new_object();
595 json_object_object_add(obj, "change", json_object_new_string(change)); 641 json_object_object_add(obj, "change", json_object_new_string(change));
@@ -615,6 +661,9 @@ void ipc_event_barconfig_update(struct bar_config *bar) {
615} 661}
616 662
617void ipc_event_mode(const char *mode) { 663void ipc_event_mode(const char *mode) {
664 if (!(config->ipc_policy & IPC_FEATURE_EVENT_MODE)) {
665 return;
666 }
618 sway_log(L_DEBUG, "Sending mode::%s event", mode); 667 sway_log(L_DEBUG, "Sending mode::%s event", mode);
619 json_object *obj = json_object_new_object(); 668 json_object *obj = json_object_new_object();
620 json_object_object_add(obj, "change", json_object_new_string(mode)); 669 json_object_object_add(obj, "change", json_object_new_string(mode));
@@ -639,8 +688,10 @@ void ipc_event_modifier(uint32_t modifier, const char *state) {
639 json_object_put(obj); // free 688 json_object_put(obj); // free
640} 689}
641 690
642#if SWAY_BINDING_EVENT
643static void ipc_event_binding(json_object *sb_obj) { 691static void ipc_event_binding(json_object *sb_obj) {
692 if (!(config->ipc_policy & IPC_FEATURE_EVENT_BINDING)) {
693 return;
694 }
644 sway_log(L_DEBUG, "Sending binding::run event"); 695 sway_log(L_DEBUG, "Sending binding::run event");
645 json_object *obj = json_object_new_object(); 696 json_object *obj = json_object_new_object();
646 json_object_object_add(obj, "change", json_object_new_string("run")); 697 json_object_object_add(obj, "change", json_object_new_string("run"));
@@ -651,10 +702,8 @@ static void ipc_event_binding(json_object *sb_obj) {
651 702
652 json_object_put(obj); // free 703 json_object_put(obj); // free
653} 704}
654#endif
655 705
656void ipc_event_binding_keyboard(struct sway_binding *sb) { 706void ipc_event_binding_keyboard(struct sway_binding *sb) {
657#if SWAY_BINDING_EVENT
658 json_object *sb_obj = json_object_new_object(); 707 json_object *sb_obj = json_object_new_object();
659 json_object_object_add(sb_obj, "command", json_object_new_string(sb->command)); 708 json_object_object_add(sb_obj, "command", json_object_new_string(sb->command));
660 709
@@ -705,5 +754,4 @@ void ipc_event_binding_keyboard(struct sway_binding *sb) {
705 json_object_object_add(sb_obj, "input_type", json_object_new_string("keyboard")); 754 json_object_object_add(sb_obj, "input_type", json_object_new_string("keyboard"));
706 755
707 ipc_event_binding(sb_obj); 756 ipc_event_binding(sb_obj);
708#endif
709} 757}
diff --git a/sway/main.c b/sway/main.c
index a040cec9..73c4b5f2 100644
--- a/sway/main.c
+++ b/sway/main.c
@@ -4,13 +4,16 @@
4#include <wlc/wlc.h> 4#include <wlc/wlc.h>
5#include <sys/wait.h> 5#include <sys/wait.h>
6#include <sys/types.h> 6#include <sys/types.h>
7#include <sys/stat.h>
7#include <sys/un.h> 8#include <sys/un.h>
8#include <signal.h> 9#include <signal.h>
9#include <unistd.h> 10#include <unistd.h>
10#include <getopt.h> 11#include <getopt.h>
12#include <sys/capability.h>
11#include "sway/extensions.h" 13#include "sway/extensions.h"
12#include "sway/layout.h" 14#include "sway/layout.h"
13#include "sway/config.h" 15#include "sway/config.h"
16#include "sway/security.h"
14#include "sway/handlers.h" 17#include "sway/handlers.h"
15#include "sway/input.h" 18#include "sway/input.h"
16#include "sway/ipc-server.h" 19#include "sway/ipc-server.h"
@@ -142,6 +145,63 @@ static void log_kernel() {
142 fclose(f); 145 fclose(f);
143} 146}
144 147
148static void security_sanity_check() {
149 // TODO: Notify users visually if this has issues
150 struct stat s;
151 if (stat("/proc", &s)) {
152 sway_log(L_ERROR,
153 "!! DANGER !! /proc is not available - sway CANNOT enforce security rules!");
154 }
155 cap_flag_value_t v;
156 cap_t cap = cap_get_proc();
157 if (!cap || cap_get_flag(cap, CAP_SYS_PTRACE, CAP_PERMITTED, &v) != 0 || v != CAP_SET) {
158 sway_log(L_ERROR,
159 "!! DANGER !! Sway does not have CAP_SYS_PTRACE and cannot enforce security rules for processes running as other users.");
160 }
161 if (cap) {
162 cap_free(cap);
163 }
164 if (!stat(SYSCONFDIR "/sway", &s)) {
165 if (s.st_uid != 0 || s.st_gid != 0
166 || (s.st_mode & S_IWGRP) || (s.st_mode & S_IWOTH)) {
167 sway_log(L_ERROR,
168 "!! DANGER !! " SYSCONFDIR "/sway is not secure! It should be owned by root and set to 0755 at the minimum");
169 }
170 }
171 struct {
172 char *command;
173 enum command_context context;
174 bool checked;
175 } expected[] = {
176 { "reload", CONTEXT_BINDING, false },
177 { "restart", CONTEXT_BINDING, false },
178 { "permit", CONTEXT_CONFIG, false },
179 { "reject", CONTEXT_CONFIG, false },
180 { "ipc", CONTEXT_CONFIG, false },
181 };
182 int expected_len = 5;
183 for (int i = 0; i < config->command_policies->length; ++i) {
184 struct command_policy *policy = config->command_policies->items[i];
185 for (int j = 0; j < expected_len; ++j) {
186 if (strcmp(expected[j].command, policy->command) == 0) {
187 expected[j].checked = true;
188 if (expected[j].context != policy->context) {
189 sway_log(L_ERROR,
190 "!! DANGER !! Command security policy for %s should be set to %s",
191 expected[j].command, command_policy_str(expected[j].context));
192 }
193 }
194 }
195 }
196 for (int j = 0; j < expected_len; ++j) {
197 if (!expected[j].checked) {
198 sway_log(L_ERROR,
199 "!! DANGER !! Command security policy for %s should be set to %s",
200 expected[j].command, command_policy_str(expected[j].context));
201 }
202 }
203}
204
145int main(int argc, char **argv) { 205int main(int argc, char **argv) {
146 static int verbose = 0, debug = 0, validate = 0; 206 static int verbose = 0, debug = 0, validate = 0;
147 207
@@ -170,6 +230,10 @@ int main(int argc, char **argv) {
170 " --get-socketpath Gets the IPC socket path and prints it, then exits.\n" 230 " --get-socketpath Gets the IPC socket path and prints it, then exits.\n"
171 "\n"; 231 "\n";
172 232
233 // Security:
234 unsetenv("LD_PRELOAD");
235 setenv("LD_LIBRARY_PATH", _LD_LIBRARY_PATH, 1);
236
173 int c; 237 int c;
174 while (1) { 238 while (1) {
175 int option_index = 0; 239 int option_index = 0;
@@ -298,6 +362,8 @@ int main(int argc, char **argv) {
298 free(config_path); 362 free(config_path);
299 } 363 }
300 364
365 security_sanity_check();
366
301 if (!terminate_request) { 367 if (!terminate_request) {
302 wlc_run(); 368 wlc_run();
303 } 369 }
diff --git a/sway/security.c b/sway/security.c
new file mode 100644
index 00000000..f16fdd1f
--- /dev/null
+++ b/sway/security.c
@@ -0,0 +1,94 @@
1#include <unistd.h>
2#include <stdio.h>
3#include "sway/config.h"
4#include "sway/security.h"
5#include "log.h"
6
7struct feature_policy *alloc_feature_policy(const char *program) {
8 uint32_t default_policy = 0;
9 for (int i = 0; i < config->feature_policies->length; ++i) {
10 struct feature_policy *policy = config->feature_policies->items[i];
11 if (strcmp(policy->program, "*") == 0) {
12 default_policy = policy->features;
13 break;
14 }
15 }
16
17 struct feature_policy *policy = malloc(sizeof(struct feature_policy));
18 policy->program = strdup(program);
19 policy->features = default_policy;
20 return policy;
21}
22
23struct command_policy *alloc_command_policy(const char *command) {
24 struct command_policy *policy = malloc(sizeof(struct command_policy));
25 policy->command = strdup(command);
26 policy->context = 0;
27 return policy;
28}
29
30enum secure_feature get_feature_policy(pid_t pid) {
31 const char *fmt = "/proc/%d/exe";
32 int pathlen = snprintf(NULL, 0, fmt, pid);
33 char *path = malloc(pathlen + 1);
34 snprintf(path, pathlen + 1, fmt, pid);
35 static char link[2048];
36
37 uint32_t default_policy = 0;
38
39 ssize_t len = readlink(path, link, sizeof(link));
40 if (len < 0) {
41 sway_log(L_INFO,
42 "WARNING: unable to read %s for security check. Using default policy.",
43 path);
44 strcpy(link, "*");
45 } else {
46 link[len] = '\0';
47 }
48 free(path);
49
50 for (int i = 0; i < config->feature_policies->length; ++i) {
51 struct feature_policy *policy = config->feature_policies->items[i];
52 if (strcmp(policy->program, "*") == 0) {
53 default_policy = policy->features;
54 }
55 if (strcmp(policy->program, link) == 0) {
56 return policy->features;
57 }
58 }
59
60 return default_policy;
61}
62
63enum command_context get_command_policy(const char *cmd) {
64 uint32_t default_policy = 0;
65
66 for (int i = 0; i < config->command_policies->length; ++i) {
67 struct command_policy *policy = config->command_policies->items[i];
68 if (strcmp(policy->command, "*") == 0) {
69 default_policy = policy->context;
70 }
71 if (strcmp(policy->command, cmd) == 0) {
72 return policy->context;
73 }
74 }
75
76 return default_policy;
77}
78
79const char *command_policy_str(enum command_context context) {
80 switch (context) {
81 case CONTEXT_ALL:
82 return "all";
83 case CONTEXT_CONFIG:
84 return "config";
85 case CONTEXT_BINDING:
86 return "binding";
87 case CONTEXT_IPC:
88 return "IPC";
89 case CONTEXT_CRITERIA:
90 return "criteria";
91 default:
92 return "unknown";
93 }
94}
diff --git a/sway/sway-security.7.txt b/sway/sway-security.7.txt
new file mode 100644
index 00000000..9a2581b1
--- /dev/null
+++ b/sway/sway-security.7.txt
@@ -0,0 +1,250 @@
1/////
2vim:set ts=4 sw=4 tw=82 noet:
3/////
4sway-security (7)
5=================
6
7Name
8----
9sway-security - Guidelines for securing your sway install
10
11Security Overview
12-----------------
13
14**Sway is NOT secure**. We are working on it but do not trust that we have it all
15figured out yet. The following man page is provisional.
16
17Securing sway requires careful configuration of your environment, the sort that's
18usually 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
20you must make a few changes external to sway first.
21
22Configuration security
23----------------------
24
25Many of Sway's security features are configurable. It's important that a possibly
26untrusted program is not able to edit this. Security rules are kept in
27_/etc/sway/config.d/security_ (usually), which should only be writable by root.
28However, configuration of security rules is not limited to this file - any config
29file that sway loads (including i.e. _~/.config/sway/config_) should not be editable
30by the user you intend to run programs as. One simple strategy is to use
31/etc/sway/config instead of a config file in your home directory, but that doesn't
32work well for multi-user systems. A more robust strategy is to run untrusted
33programs as another user, or in a sandbox. Configuring this is up to you.
34
35Note that _/etc/sway/config.d/*_ must be included explicitly from your config file.
36This is done by default in /etc/sway/config but you must check your own config if
37you choose to place it in other locations.
38
39Environment security
40--------------------
41
42LD_PRELOAD is a mechanism designed to ruin the security of your system. There are
43a number of strategies for dealing with this but they all suck a little. In order
44of most practical to least practical:
45
461. Only run important programs via exec. Sway's exec command will ensure that
47 LD_PRELOAD is unset when running programs.
48
492. Remove LD_PRELOAD support from your dynamic loader (requires patching libc).
50 This may break programs that rely on LD_PRELOAD for legitimate functionality,
51 but this is the most effective solution.
52
533. Use static linking for important programs. Of course statically linked programs
54 are unaffected by the dynamic linking security dumpster fire.
55
56Note that should you choose method 1, you MUST ensure that sway itself isn't
57compromised by LD_PRELOAD. It probably isn't, but you can be sure by setting
58/usr/bin/sway to a+s (setuid), which will instruct the dynamic linker not to
59permit LD_PRELOAD for it (and will also run it as root, which sway will shortly
60drop). You could also statically link sway itself.
61
62Note that LD_LIBRARY_PATH has all of the same problems, and all of the same
63solutions.
64
65Read your log
66-------------
67
68Sway does sanity checks and prints big red warnings to stderr if they fail. Read
69them.
70
71Feature policies
72----------------
73
74Certain sway features are security sensitive and may be configured with security
75policies. These features are:
76
77**background**::
78 Permission for a program to become the background.
79
80**fullscreen**::
81 Permission to become fullscreen. Note that users can always make a window
82 fullscreen themselves with the fullscreen command.
83
84**ipc**::
85 Permission to connect to sway's IPC socket.
86
87**keyboard**::
88 Permission to receive keyboard events (only while they are focused).
89
90**lock**::
91 Permission for a program to act as a screen locker. This involves becoming
92 fullscreen (on all outputs) and receiving _all_ keyboard and mouse input for
93 the duration of the process.
94
95**mouse**::
96 Permission to receive mouse events (only while the mouse is over them).
97
98**panel**::
99 Permission for a program to stick its windows to the sides of the screen.
100
101**screenshot**::
102 Permission to take screenshots or record the screen.
103
104By default, all programs are granted **fullscreen**, **keyboard**, **mouse**, and
105**ipc** permissions. You can use the following config commands to control a
106program's access:
107
108**permit** <executable> <features...>::
109 Permits <executable> to use <features> (each feature seperated by a space).
110 <executable> may be * to affect the default policy, or the full path to the
111 executable file.
112
113**reject** <executable> <features...>::
114 Disallows <executable> from using <features> (each feature seperated by a space).
115 <executable> may be * to affect the default policy, or the full path to the
116 executable file.
117
118Note that policy enforcement requires procfs to be mounted at /proc and the sway
119process to be able to access _/proc/[pid]/exe_ (see **procfs(5)** for details on
120this access - setcap cap_sys_ptrace=eip /usr/bin/sway should do the trick). If
121sway is unable to read _/proc/[pid]/exe_, it will apply the default policy.
122
123To work correctly, sway's own programs require the following permissions:
124
125- swaybg: background
126- swaylock: lock, keyboard
127- swaybar: panel, mouse, ipc
128- swaygrab: screenshot, ipc
129
130When you first declare a policy for an executable, it will inherit the default
131policy. Further changes to the default policy will not retroactively affect which
132permissions an earlier policy inherits. You must explicitly reject any features
133from the default policy that you do not want an executable to receive permission
134for.
135
136Command policies
137----------------
138
139You can also control the context from which a command may execute. The different
140contexts you can control are:
141
142**config**::
143 Can be run from your config file.
144
145**binding**::
146 Can be run from bindsym or bindcode commands.
147
148**ipc**::
149 Can be run by IPC clients.
150
151**criteria**::
152 Can be run when evaluating window criteria.
153
154**all**::
155 Shorthand for granting permission in all contexts.
156
157By default a command is allowed to execute in any context. To configure this, open
158a commands block and fill it with policies:
159
160 commands {
161 <name> <contexts...>
162 ...
163 }
164
165For example, you could do this to limit the use of the focus command to just
166binding and critiera:
167
168 commands {
169 focus binding criteria
170 }
171
172Setting a command policy overwrites any previous policy that was in place.
173
174IPC policies
175------------
176
177You may whitelist IPC access like so:
178
179 permit /usr/bin/swaybar ipc
180 permit /usr/bin/swaygrab ipc
181 # etc
182
183Note that it's suggested you do not enable swaymsg to access IPC if you intend to
184secure your IPC socket, because any program could just run swaymsg itself instead
185of connecting to IPC directly.
186
187You can also configure which features of IPC are available with an IPC block:
188
189 ipc {
190 ...
191 }
192
193The following commands are available within this block:
194
195**bar-config** <enabled|disabled>::
196 Controls GET_BAR_CONFIG (required for swaybar to work at all).
197
198**command** <enabled|disabled>::
199 Controls executing sway commands via IPC.
200
201**inputs** <enabled|disabled>::
202 Controls GET_INPUTS (input device information).
203
204**marks** <enabled|disabled>::
205 Controls GET_MARKS.
206
207**outputs** <enabled|disabled>::
208 Controls GET_OUTPUTS.
209
210**tree** <enabled|disabled>::
211 Controls GET_TREE.
212
213**workspaces** <enabled|disabled>::
214 Controls GET_WORKSPACES.
215
216You can also control which IPC events can be raised with an events block:
217
218 ipc {
219 events {
220 ...
221 }
222 }
223
224The following commands are vaild within an ipc events block:
225
226**binding** <enabled|disabled>::
227 Controls keybinding notifications (disabled by default).
228
229**input** <enabled|disabled>::
230 Controls input device hotplugging notifications.
231
232**mode** <enabled|disabled>::
233 Controls output hotplugging notifications.
234
235**output** <enabled|disabled>::
236 Controls output hotplugging notifications.
237
238**window** <enabled|disabled>::
239 Controls window event notifications.
240
241**workspace** <enabled|disabled>::
242 Controls workspace notifications.
243
244Disabling some of these may cause swaybar to behave incorrectly.
245
246Authors
247-------
248Maintained by Drew DeVault <sir@cmpwn.com>, who is assisted by other open
249source contributors. For more information about sway development, see
250<https://github.com/SirCmpwn/sway>.
diff --git a/swaylock/CMakeLists.txt b/swaylock/CMakeLists.txt
index febbd1af..4aec6424 100644
--- a/swaylock/CMakeLists.txt
+++ b/swaylock/CMakeLists.txt
@@ -42,7 +42,7 @@ install(
42 42
43install( 43install(
44 FILES ${CMAKE_CURRENT_SOURCE_DIR}/pam/swaylock 44 FILES ${CMAKE_CURRENT_SOURCE_DIR}/pam/swaylock
45 DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/pam.d/ 45 DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/pam.d/
46 COMPONENT data 46 COMPONENT data
47) 47)
48 48