diff options
author | Matt Coffin <mcoffin13@gmail.com> | 2019-06-11 12:10:17 -0600 |
---|---|---|
committer | Brian Ashworth <bosrsf04@gmail.com> | 2019-06-11 14:40:36 -0400 |
commit | 2b5bf78fafdf027624ca88e1f703bc9e577f4690 (patch) | |
tree | ee24a2a3740563aecfab9fdc922eafebf1527c97 | |
parent | Add docs for new IPC keyboard properties (diff) | |
download | sway-2b5bf78fafdf027624ca88e1f703bc9e577f4690.tar.gz sway-2b5bf78fafdf027624ca88e1f703bc9e577f4690.tar.zst sway-2b5bf78fafdf027624ca88e1f703bc9e577f4690.zip |
Fix segfaults caused by faulty command parsing
This patch fixes faulty command parsing introduced by
f0f5de9a9e87ca1f0d74e7cbf82ffceba51ffbe6. When that commit allowed
criteria reset on ';' delimeters in commands lists, it failed to account
for its inner ','-parsing loop eating threw the entire rest of the
string.
This patch refactors argsep to use a list of multiple separators, and
(optionally) return the separator that it matched against in this
iteration via a pointer. This allows it to hint at the command parser
which separator was used at the end of the last command, allowing it to
trigger a potential secondary read of the criteria.
Fixes #4239
-rw-r--r-- | common/stringop.c | 56 | ||||
-rw-r--r-- | include/stringop.h | 2 | ||||
-rw-r--r-- | sway/commands.c | 113 | ||||
-rw-r--r-- | sway/tree/workspace.c | 4 |
4 files changed, 98 insertions, 77 deletions
diff --git a/common/stringop.c b/common/stringop.c index dea152cc..ac7df296 100644 --- a/common/stringop.c +++ b/common/stringop.c | |||
@@ -251,37 +251,61 @@ char *join_args(char **argv, int argc) { | |||
251 | return res; | 251 | return res; |
252 | } | 252 | } |
253 | 253 | ||
254 | char *argsep(char **stringp, const char *delim) { | 254 | static inline char *argsep_next_interesting(const char *src, const char *delim) { |
255 | char *special = strpbrk(src, "\"'\\"); | ||
256 | char *next_delim = strpbrk(src, delim); | ||
257 | if (!special) { | ||
258 | return next_delim; | ||
259 | } | ||
260 | if (!next_delim) { | ||
261 | return special; | ||
262 | } | ||
263 | return (next_delim < special) ? next_delim : special; | ||
264 | } | ||
265 | |||
266 | char *argsep(char **stringp, const char *delim, char *matched) { | ||
255 | char *start = *stringp; | 267 | char *start = *stringp; |
256 | char *end = start; | 268 | char *end = start; |
257 | bool in_string = false; | 269 | bool in_string = false; |
258 | bool in_char = false; | 270 | bool in_char = false; |
259 | bool escaped = false; | 271 | bool escaped = false; |
260 | while (1) { | 272 | char *interesting = NULL; |
261 | if (*end == '"' && !in_char && !escaped) { | 273 | |
274 | while ((interesting = argsep_next_interesting(end, delim))) { | ||
275 | if (escaped && interesting != end) { | ||
276 | escaped = false; | ||
277 | } | ||
278 | if (*interesting == '"' && !in_char && !escaped) { | ||
262 | in_string = !in_string; | 279 | in_string = !in_string; |
263 | } else if (*end == '\'' && !in_string && !escaped) { | 280 | end = interesting + 1; |
281 | } else if (*interesting == '\'' && !in_string && !escaped) { | ||
264 | in_char = !in_char; | 282 | in_char = !in_char; |
265 | } else if (*end == '\\') { | 283 | end = interesting + 1; |
284 | } else if (*interesting == '\\') { | ||
266 | escaped = !escaped; | 285 | escaped = !escaped; |
267 | } else if (*end == '\0') { | 286 | end = interesting + 1; |
268 | *stringp = NULL; | 287 | } else if (!in_string && !in_char && !escaped) { |
269 | break; | 288 | // We must have matched a separator |
270 | } else if (!in_string && !in_char && !escaped && strchr(delim, *end)) { | 289 | end = interesting; |
290 | if (matched) { | ||
291 | *matched = *end; | ||
292 | } | ||
271 | if (end - start) { | 293 | if (end - start) { |
272 | *(end++) = 0; | 294 | *(end++) = 0; |
273 | *stringp = end + strspn(end, delim);; | 295 | *stringp = end; |
274 | if (!**stringp) *stringp = NULL; | ||
275 | break; | 296 | break; |
276 | } else { | 297 | } else { |
277 | ++start; | 298 | end = ++start; |
278 | end = start; | ||
279 | } | 299 | } |
300 | } else { | ||
301 | end++; | ||
280 | } | 302 | } |
281 | if (*end != '\\') { | 303 | } |
282 | escaped = false; | 304 | if (!interesting) { |
305 | *stringp = NULL; | ||
306 | if (matched) { | ||
307 | *matched = '\0'; | ||
283 | } | 308 | } |
284 | ++end; | ||
285 | } | 309 | } |
286 | return start; | 310 | return start; |
287 | } | 311 | } |
diff --git a/include/stringop.h b/include/stringop.h index 6f920999..2aabcee7 100644 --- a/include/stringop.h +++ b/include/stringop.h | |||
@@ -24,6 +24,6 @@ int unescape_string(char *string); | |||
24 | char *join_args(char **argv, int argc); | 24 | char *join_args(char **argv, int argc); |
25 | 25 | ||
26 | // Split string into 2 by delim, handle quotes | 26 | // Split string into 2 by delim, handle quotes |
27 | char *argsep(char **stringp, const char *delim); | 27 | char *argsep(char **stringp, const char *delim, char *matched_delim); |
28 | 28 | ||
29 | #endif | 29 | #endif |
diff --git a/sway/commands.c b/sway/commands.c index 377f2d01..a670f813 100644 --- a/sway/commands.c +++ b/sway/commands.c | |||
@@ -211,8 +211,8 @@ list_t *execute_command(char *_exec, struct sway_seat *seat, | |||
211 | list_t *res_list = create_list(); | 211 | list_t *res_list = create_list(); |
212 | char *exec = strdup(_exec); | 212 | char *exec = strdup(_exec); |
213 | char *head = exec; | 213 | char *head = exec; |
214 | char *cmdlist; | ||
215 | char *cmd; | 214 | char *cmd; |
215 | char matched_delim = ';'; | ||
216 | list_t *views = NULL; | 216 | list_t *views = NULL; |
217 | 217 | ||
218 | if (seat == NULL) { | 218 | if (seat == NULL) { |
@@ -227,16 +227,13 @@ list_t *execute_command(char *_exec, struct sway_seat *seat, | |||
227 | 227 | ||
228 | head = exec; | 228 | head = exec; |
229 | do { | 229 | do { |
230 | // Split command list | 230 | for (; isspace(*head); ++head) {} |
231 | cmdlist = argsep(&head, ";"); | 231 | // Extract criteria (valid for this command list only). |
232 | do { | 232 | if (matched_delim == ';') { |
233 | // Skip leading whitespace | ||
234 | for (; isspace(*cmdlist); ++cmdlist) {} | ||
235 | // Extract criteria (valid for this command chain only). | ||
236 | config->handler_context.using_criteria = false; | 233 | config->handler_context.using_criteria = false; |
237 | if (*cmdlist == '[') { | 234 | if (*head == '[') { |
238 | char *error = NULL; | 235 | char *error = NULL; |
239 | struct criteria *criteria = criteria_parse(cmdlist, &error); | 236 | struct criteria *criteria = criteria_parse(head, &error); |
240 | if (!criteria) { | 237 | if (!criteria) { |
241 | list_add(res_list, | 238 | list_add(res_list, |
242 | cmd_results_new(CMD_INVALID, "%s", error)); | 239 | cmd_results_new(CMD_INVALID, "%s", error)); |
@@ -245,71 +242,71 @@ list_t *execute_command(char *_exec, struct sway_seat *seat, | |||
245 | } | 242 | } |
246 | list_free(views); | 243 | list_free(views); |
247 | views = criteria_get_views(criteria); | 244 | views = criteria_get_views(criteria); |
248 | cmdlist += strlen(criteria->raw); | 245 | head += strlen(criteria->raw); |
249 | criteria_destroy(criteria); | 246 | criteria_destroy(criteria); |
250 | config->handler_context.using_criteria = true; | 247 | config->handler_context.using_criteria = true; |
251 | // Skip leading whitespace | 248 | // Skip leading whitespace |
252 | for (; isspace(*cmdlist); ++cmdlist) {} | 249 | for (; isspace(*head); ++head) {} |
253 | } | ||
254 | // Split command chain into commands | ||
255 | cmd = argsep(&cmdlist, ","); | ||
256 | for (; isspace(*cmd); ++cmd) {} | ||
257 | if (strcmp(cmd, "") == 0) { | ||
258 | sway_log(SWAY_INFO, "Ignoring empty command."); | ||
259 | continue; | ||
260 | } | 250 | } |
261 | sway_log(SWAY_INFO, "Handling command '%s'", cmd); | 251 | } |
262 | //TODO better handling of argv | 252 | // Split command list |
263 | int argc; | 253 | cmd = argsep(&head, ";,", &matched_delim); |
264 | char **argv = split_args(cmd, &argc); | 254 | for (; isspace(*cmd); ++cmd) {} |
265 | if (strcmp(argv[0], "exec") != 0 && | 255 | |
266 | strcmp(argv[0], "exec_always") != 0 && | 256 | if (strcmp(cmd, "") == 0) { |
267 | strcmp(argv[0], "mode") != 0) { | 257 | sway_log(SWAY_INFO, "Ignoring empty command."); |
268 | int i; | 258 | continue; |
269 | for (i = 1; i < argc; ++i) { | 259 | } |
270 | if (*argv[i] == '\"' || *argv[i] == '\'') { | 260 | sway_log(SWAY_INFO, "Handling command '%s'", cmd); |
271 | strip_quotes(argv[i]); | 261 | //TODO better handling of argv |
272 | } | 262 | int argc; |
263 | char **argv = split_args(cmd, &argc); | ||
264 | if (strcmp(argv[0], "exec") != 0 && | ||
265 | strcmp(argv[0], "exec_always") != 0 && | ||
266 | strcmp(argv[0], "mode") != 0) { | ||
267 | for (int i = 1; i < argc; ++i) { | ||
268 | if (*argv[i] == '\"' || *argv[i] == '\'') { | ||
269 | strip_quotes(argv[i]); | ||
273 | } | 270 | } |
274 | } | 271 | } |
275 | struct cmd_handler *handler = find_handler(argv[0], NULL, 0); | 272 | } |
276 | if (!handler) { | 273 | struct cmd_handler *handler = find_handler(argv[0], NULL, 0); |
277 | list_add(res_list, cmd_results_new(CMD_INVALID, | 274 | if (!handler) { |
278 | "Unknown/invalid command '%s'", argv[0])); | 275 | list_add(res_list, cmd_results_new(CMD_INVALID, |
276 | "Unknown/invalid command '%s'", argv[0])); | ||
277 | free_argv(argc, argv); | ||
278 | goto cleanup; | ||
279 | } | ||
280 | |||
281 | // Var replacement, for all but first argument of set | ||
282 | for (int i = handler->handle == cmd_set ? 2 : 1; i < argc; ++i) { | ||
283 | argv[i] = do_var_replacement(argv[i]); | ||
284 | } | ||
285 | |||
286 | if (!config->handler_context.using_criteria) { | ||
287 | // The container or workspace which this command will run on. | ||
288 | struct sway_node *node = con ? &con->node : | ||
289 | seat_get_focus_inactive(seat, &root->node); | ||
290 | set_config_node(node); | ||
291 | struct cmd_results *res = handler->handle(argc-1, argv+1); | ||
292 | list_add(res_list, res); | ||
293 | if (res->status == CMD_INVALID) { | ||
279 | free_argv(argc, argv); | 294 | free_argv(argc, argv); |
280 | goto cleanup; | 295 | goto cleanup; |
281 | } | 296 | } |
282 | 297 | } else { | |
283 | // Var replacement, for all but first argument of set | 298 | for (int i = 0; i < views->length; ++i) { |
284 | for (int i = handler->handle == cmd_set ? 2 : 1; i < argc; ++i) { | 299 | struct sway_view *view = views->items[i]; |
285 | argv[i] = do_var_replacement(argv[i]); | 300 | set_config_node(&view->container->node); |
286 | } | ||
287 | |||
288 | if (!config->handler_context.using_criteria) { | ||
289 | // The container or workspace which this command will run on. | ||
290 | struct sway_node *node = con ? &con->node : | ||
291 | seat_get_focus_inactive(seat, &root->node); | ||
292 | set_config_node(node); | ||
293 | struct cmd_results *res = handler->handle(argc-1, argv+1); | 301 | struct cmd_results *res = handler->handle(argc-1, argv+1); |
294 | list_add(res_list, res); | 302 | list_add(res_list, res); |
295 | if (res->status == CMD_INVALID) { | 303 | if (res->status == CMD_INVALID) { |
296 | free_argv(argc, argv); | 304 | free_argv(argc, argv); |
297 | goto cleanup; | 305 | goto cleanup; |
298 | } | 306 | } |
299 | } else { | ||
300 | for (int i = 0; i < views->length; ++i) { | ||
301 | struct sway_view *view = views->items[i]; | ||
302 | set_config_node(&view->container->node); | ||
303 | struct cmd_results *res = handler->handle(argc-1, argv+1); | ||
304 | list_add(res_list, res); | ||
305 | if (res->status == CMD_INVALID) { | ||
306 | free_argv(argc, argv); | ||
307 | goto cleanup; | ||
308 | } | ||
309 | } | ||
310 | } | 307 | } |
311 | free_argv(argc, argv); | 308 | } |
312 | } while(cmdlist); | 309 | free_argv(argc, argv); |
313 | } while(head); | 310 | } while(head); |
314 | cleanup: | 311 | cleanup: |
315 | free(exec); | 312 | free(exec); |
diff --git a/sway/tree/workspace.c b/sway/tree/workspace.c index 1a1f5c49..e1ef40f4 100644 --- a/sway/tree/workspace.c +++ b/sway/tree/workspace.c | |||
@@ -212,9 +212,9 @@ static void workspace_name_from_binding(const struct sway_binding * binding, | |||
212 | char *name = NULL; | 212 | char *name = NULL; |
213 | 213 | ||
214 | // workspace n | 214 | // workspace n |
215 | char *cmd = argsep(&cmdlist, " "); | 215 | char *cmd = argsep(&cmdlist, " ", NULL); |
216 | if (cmdlist) { | 216 | if (cmdlist) { |
217 | name = argsep(&cmdlist, ",;"); | 217 | name = argsep(&cmdlist, ",;", NULL); |
218 | } | 218 | } |
219 | 219 | ||
220 | // TODO: support "move container to workspace" bindings as well | 220 | // TODO: support "move container to workspace" bindings as well |