diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/core/load-fragment.c | 124 | ||||
-rw-r--r-- | src/test/test-unit-file.c | 156 |
2 files changed, 186 insertions, 94 deletions
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index 7430036f48..e9659ca344 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -547,9 +547,9 @@ int config_parse_exec(const char *unit, * overriding of argv[0]. */ for (;;) { int i; - const char *word, *state; + const char *word, *state, *reason; size_t l; - bool honour_argv0 = false, ignore = false; + bool separate_argv0 = false, ignore = false; path = NULL; nce = NULL; @@ -560,28 +560,23 @@ int config_parse_exec(const char *unit, if (rvalue[0] == 0) break; - for (i = 0; i < 2; i++) { - if (rvalue[0] == '-' && !ignore) { - ignore = true; - rvalue ++; - } - - if (rvalue[0] == '@' && !honour_argv0) { - honour_argv0 = true; - rvalue ++; - } - } - - if (*rvalue != '/') { - log_syntax(unit, LOG_ERR, filename, line, EINVAL, - "Executable path is not absolute, ignoring: %s", rvalue); - return 0; - } - k = 0; FOREACH_WORD_QUOTED(word, l, rvalue, state) { - if (strneq(word, ";", MAX(l, 1U))) - goto found; + if (k == 0) { + for (i = 0; i < 2; i++) { + if (*word == '-' && !ignore) { + ignore = true; + word ++; + } + + if (*word == '@' && !separate_argv0) { + separate_argv0 = true; + word ++; + } + } + } else + if (strneq(word, ";", MAX(l, 1U))) + goto found; k++; } @@ -592,60 +587,69 @@ int config_parse_exec(const char *unit, } found: - n = new(char*, k + !honour_argv0); + n = new(char*, k + !separate_argv0); if (!n) return log_oom(); k = 0; FOREACH_WORD_QUOTED(word, l, rvalue, state) { - if (strneq(word, ";", MAX(l, 1U))) - break; - else if (strneq(word, "\\;", MAX(l, 1U))) { - word ++; - l --; - } + char *c; + unsigned skip; - if (honour_argv0 && word == rvalue) { - assert(!path); + if (separate_argv0 ? path == NULL : k == 0) { + /* first word, very special */ + skip = separate_argv0 + ignore; - path = strndup(word, l); - if (!path) { - r = log_oom(); - goto fail; - } + /* skip special chars in the beginning */ + assert(skip < l); - if (!utf8_is_valid(path)) { - log_invalid_utf8(unit, LOG_ERR, filename, line, EINVAL, rvalue); - r = 0; - goto fail; - } + } else if (strneq(word, ";", MAX(l, 1U))) + /* new commandline */ + break; - } else { - char *c; + else + skip = strneq(word, "\\;", MAX(l, 1U)); - c = n[k++] = cunescape_length(word, l); - if (!c) { - r = log_oom(); - goto fail; - } + c = cunescape_length(word + skip, l - skip); + if (!c) { + r = log_oom(); + goto fail; + } - if (!utf8_is_valid(c)) { - log_invalid_utf8(unit, LOG_ERR, filename, line, EINVAL, rvalue); - r = 0; - goto fail; - } + if (!utf8_is_valid(c)) { + log_invalid_utf8(unit, LOG_ERR, filename, line, EINVAL, rvalue); + r = 0; + goto fail; } + + /* where to stuff this? */ + if (separate_argv0 && path == NULL) + path = c; + else + n[k++] = c; } n[k] = NULL; - if (!n[0]) { - log_syntax(unit, LOG_ERR, filename, line, EINVAL, - "Invalid command line, ignoring: %s", rvalue); - r = 0; - goto fail; - } + log_debug("path: %s", path ?: n[0]); + + if (!n[0]) + reason = "Empty executable name or zeroeth argument"; + else if (!string_is_safe(path ?: n[0])) + reason = "Executable path contains special characters"; + else if (!path_is_absolute(path ?: n[0])) + reason = "Executable path is not absolute"; + else if (endswith(path ?: n[0], "/")) + reason = "Executable path specifies a directory"; + else + goto ok; + + log_syntax(unit, LOG_ERR, filename, line, EINVAL, + "%s, ignoring: %s", reason, rvalue); + r = 0; + goto fail; +ok: if (!path) { path = strdup(n[0]); if (!path) { @@ -654,8 +658,6 @@ int config_parse_exec(const char *unit, } } - assert(path_is_absolute(path)); - nce = new0(ExecCommand, 1); if (!nce) { r = log_oom(); diff --git a/src/test/test-unit-file.c b/src/test/test-unit-file.c index 08da2ba4eb..6a146a702f 100644 --- a/src/test/test-unit-file.c +++ b/src/test/test-unit-file.c @@ -74,31 +74,34 @@ static void check_execcommand(ExecCommand *c, const char* argv2, bool ignore) { assert_se(c); - log_info("%s %s %s %s", + log_info("expect: \"%s\" [\"%s\" \"%s\" \"%s\"]", + path, argv0 ?: path, argv1, argv2); + log_info("actual: \"%s\" [\"%s\" \"%s\" \"%s\"]", c->path, c->argv[0], c->argv[1], c->argv[2]); assert_se(streq(c->path, path)); - assert_se(streq(c->argv[0], argv0)); - assert_se(streq(c->argv[1], argv1)); + assert_se(streq(c->argv[0], argv0 ?: path)); + assert_se(streq_ptr(c->argv[1], argv1)); assert_se(streq_ptr(c->argv[2], argv2)); assert_se(c->ignore == ignore); } static void test_config_parse_exec(void) { - /* int config_parse_exec( */ - /* const char *filename, */ - /* unsigned line, */ - /* const char *section, */ - /* unsigned section_line, */ - /* const char *lvalue, */ - /* int ltype, */ - /* const char *rvalue, */ - /* void *data, */ - /* void *userdata) */ + /* int config_parse_exec( + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) */ int r; ExecCommand *c = NULL, *c1; + const char *ccc; - /* basic test */ + log_info("/* basic test */"); r = config_parse_exec(NULL, "fake", 1, "section", 1, "LValue", 0, "/RValue r1", &c, NULL); @@ -106,52 +109,60 @@ static void test_config_parse_exec(void) { check_execcommand(c, "/RValue", "/RValue", "r1", NULL, false); r = config_parse_exec(NULL, "fake", 2, "section", 1, - "LValue", 0, "/RValue///slashes/// r1", + "LValue", 0, "/RValue///slashes r1///", &c, NULL); - /* test slashes */ + + log_info("/* test slashes */"); assert_se(r >= 0); c1 = c->command_next; - check_execcommand(c1, "/RValue/slashes", "/RValue///slashes///", "r1", NULL, false); + check_execcommand(c1, "/RValue/slashes", "/RValue///slashes", "r1///", NULL, false); - /* honour_argv0 */ + log_info("/* trailing slash */"); + r = config_parse_exec(NULL, "fake", 4, "section", 1, + "LValue", 0, "/RValue/ argv0 r1", + &c, NULL); + assert_se(r == 0); + assert_se(c1->command_next == NULL); + + log_info("/* honour_argv0 */"); r = config_parse_exec(NULL, "fake", 3, "section", 1, - "LValue", 0, "@/RValue///slashes2/// argv0 r1", + "LValue", 0, "@/RValue///slashes2 ///argv0 r1", &c, NULL); assert_se(r >= 0); c1 = c1->command_next; - check_execcommand(c1, "/RValue/slashes2", "argv0", "r1", NULL, false); + check_execcommand(c1, "/RValue/slashes2", "///argv0", "r1", NULL, false); - /* ignore && honour_argv0 */ + log_info("/* ignore && honour_argv0 */"); r = config_parse_exec(NULL, "fake", 4, "section", 1, - "LValue", 0, "-@/RValue///slashes3/// argv0a r1", + "LValue", 0, "-@/RValue///slashes3 argv0a r1", &c, NULL); assert_se(r >= 0); c1 = c1->command_next; check_execcommand(c1, "/RValue/slashes3", "argv0a", "r1", NULL, true); - /* ignore && honour_argv0 */ + log_info("/* ignore && honour_argv0 */"); r = config_parse_exec(NULL, "fake", 4, "section", 1, - "LValue", 0, "@-/RValue///slashes4/// argv0b r1", + "LValue", 0, "@-/RValue///slashes4 argv0b r1", &c, NULL); assert_se(r >= 0); c1 = c1->command_next; check_execcommand(c1, "/RValue/slashes4", "argv0b", "r1", NULL, true); - /* ignore && ignore */ + log_info("/* ignore && ignore */"); r = config_parse_exec(NULL, "fake", 4, "section", 1, "LValue", 0, "--/RValue argv0 r1", &c, NULL); assert_se(r == 0); assert_se(c1->command_next == NULL); - /* ignore && ignore */ + log_info("/* ignore && ignore (2) */"); r = config_parse_exec(NULL, "fake", 4, "section", 1, "LValue", 0, "-@-/RValue argv0 r1", &c, NULL); assert_se(r == 0); assert_se(c1->command_next == NULL); - /* semicolon */ + log_info("/* semicolon */"); r = config_parse_exec(NULL, "fake", 5, "section", 1, "LValue", 0, "-@/RValue argv0 r1 ; " @@ -162,9 +173,9 @@ static void test_config_parse_exec(void) { check_execcommand(c1, "/RValue", "argv0", "r1", NULL, true); c1 = c1->command_next; - check_execcommand(c1, "/goo/goo", "/goo/goo", "boo", NULL, false); + check_execcommand(c1, "/goo/goo", NULL, "boo", NULL, false); - /* trailing semicolon */ + log_info("/* trailing semicolon */"); r = config_parse_exec(NULL, "fake", 5, "section", 1, "LValue", 0, "-@/RValue argv0 r1 ; ", @@ -175,16 +186,16 @@ static void test_config_parse_exec(void) { assert_se(c1->command_next == NULL); - /* escaped semicolon */ + log_info("/* escaped semicolon */"); r = config_parse_exec(NULL, "fake", 5, "section", 1, "LValue", 0, "/bin/find \\;", &c, NULL); assert_se(r >= 0); c1 = c1->command_next; - check_execcommand(c1, "/bin/find", "/bin/find", ";", NULL, false); + check_execcommand(c1, "/bin/find", NULL, ";", NULL, false); - /* escaped semicolon with following arg */ + log_info("/* escaped semicolon with following arg */"); r = config_parse_exec(NULL, "fake", 5, "section", 1, "LValue", 0, "/sbin/find \\; x", @@ -192,7 +203,86 @@ static void test_config_parse_exec(void) { assert_se(r >= 0); c1 = c1->command_next; check_execcommand(c1, - "/sbin/find", "/sbin/find", ";", "x", false); + "/sbin/find", NULL, ";", "x", false); + + log_info("/* spaces in the filename */"); + r = config_parse_exec(NULL, "fake", 5, "section", 1, + "LValue", 0, + "\"/PATH WITH SPACES/daemon\" -1 -2", + &c, NULL); + assert_se(r >= 0); + c1 = c1->command_next; + check_execcommand(c1, + "/PATH WITH SPACES/daemon", NULL, "-1", "-2", false); + + log_info("/* spaces in the filename, no args */"); + r = config_parse_exec(NULL, "fake", 5, "section", 1, + "LValue", 0, + "\"/PATH WITH SPACES/daemon -1 -2\"", + &c, NULL); + assert_se(r >= 0); + c1 = c1->command_next; + check_execcommand(c1, + "/PATH WITH SPACES/daemon -1 -2", NULL, NULL, NULL, false); + + log_info("/* spaces in the filename, everything quoted */"); + r = config_parse_exec(NULL, "fake", 5, "section", 1, + "LValue", 0, + "\"/PATH WITH SPACES/daemon\" \"-1\" '-2'", + &c, NULL); + assert_se(r >= 0); + c1 = c1->command_next; + check_execcommand(c1, + "/PATH WITH SPACES/daemon", NULL, "-1", "-2", false); + + log_info("/* escaped spaces in the filename */"); + r = config_parse_exec(NULL, "fake", 5, "section", 1, + "LValue", 0, + "\"/PATH\\sWITH\\sSPACES/daemon\" '-1 -2'", + &c, NULL); + assert_se(r >= 0); + c1 = c1->command_next; + check_execcommand(c1, + "/PATH WITH SPACES/daemon", NULL, "-1 -2", NULL, false); + + log_info("/* escaped spaces in the filename (2) */"); + r = config_parse_exec(NULL, "fake", 5, "section", 1, + "LValue", 0, + "\"/PATH\\x20WITH\\x20SPACES/daemon\" \"-1 -2\"", + &c, NULL); + assert_se(r >= 0); + c1 = c1->command_next; + check_execcommand(c1, + "/PATH WITH SPACES/daemon", NULL, "-1 -2", NULL, false); + + for (ccc = "abfnrtv\\\'\"x"; *ccc; ccc++) { + /* \\x is an incomplete hexadecimal sequence, invalid because of the slash */ + char path[] = "/path\\X"; + path[sizeof(path) - 2] = *ccc; + + log_info("/* invalid character: \\%c */", *ccc); + r = config_parse_exec(NULL, "fake", 4, "section", 1, + "LValue", 0, path, + &c, NULL); + assert_se(r == 0); + assert_se(c1->command_next == NULL); + } + + log_info("/* valid character: \\s */"); + r = config_parse_exec(NULL, "fake", 4, "section", 1, + "LValue", 0, "/path\\s", + &c, NULL); + assert_se(r >= 0); + c1 = c1->command_next; + check_execcommand(c1, "/path ", NULL, NULL, NULL, false); + + log_info("/* trailing backslash: \\ */"); + /* backslash is invalid */ + r = config_parse_exec(NULL, "fake", 4, "section", 1, + "LValue", 0, "/path\\", + &c, NULL); + assert_se(r == 0); + assert_se(c1->command_next == NULL); exec_command_free_list(c); } |