From 8c35c10d20ff7b369bfff3fbbb92a43ca2fd5938 Mon Sep 17 00:00:00 2001 From: alexlzhu Date: Thu, 8 Jul 2021 17:10:47 -0700 Subject: [PATCH] core: Add ExecSearchPath parameter to specify the directory relative to which binaries executed by Exec*= should be found Currently there does not exist a way to specify a path relative to which all binaries executed by Exec should be found. The only way is to specify the absolute path. This change implements the functionality to specify a path relative to which binaries executed by Exec*= can be found. Closes #6308 --- man/org.freedesktop.systemd1.xml | 24 +++++ man/systemd.exec.xml | 14 +++ src/analyze/analyze-verify.c | 2 +- src/basic/path-util.c | 26 ++++- src/basic/path-util.h | 4 +- src/core/dbus-execute.c | 31 ++++++ src/core/execute.c | 29 +++++- src/core/execute.h | 1 + src/core/load-fragment-gperf.gperf.in | 1 + src/core/load-fragment.c | 59 ++++++++++++ src/core/load-fragment.h | 1 + src/run/run.c | 1 + src/shared/bus-unit-util.c | 1 + src/test/test-execute.c | 94 +++++++++++++++++++ src/test/test-path-util.c | 28 +++++- test/fuzz/fuzz-unit-file/directives.mount | 1 + test/fuzz/fuzz-unit-file/directives.service | 1 + test/fuzz/fuzz-unit-file/directives.socket | 1 + test/fuzz/fuzz-unit-file/directives.swap | 1 + ...xecsearchpath-environment-path-set.service | 5 + .../exec-execsearchpath-environment.service | 5 + ...execsearchpath-environmentfile-set.service | 8 ++ ...xec-execsearchpath-environmentfile.service | 8 ++ ...execsearchpath-passenvironment-set.service | 8 ++ ...xec-execsearchpath-passenvironment.service | 8 ++ ...exec-execsearchpath-unit-specifier.service | 7 ++ test/test-execute/exec-execsearchpath.service | 4 + 27 files changed, 360 insertions(+), 13 deletions(-) create mode 100644 test/test-execute/exec-execsearchpath-environment-path-set.service create mode 100644 test/test-execute/exec-execsearchpath-environment.service create mode 100644 test/test-execute/exec-execsearchpath-environmentfile-set.service create mode 100644 test/test-execute/exec-execsearchpath-environmentfile.service create mode 100644 test/test-execute/exec-execsearchpath-passenvironment-set.service create mode 100644 test/test-execute/exec-execsearchpath-passenvironment.service create mode 100644 test/test-execute/exec-execsearchpath-unit-specifier.service create mode 100644 test/test-execute/exec-execsearchpath.service diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml index d063d6d4d9f..e4972104d95 100644 --- a/man/org.freedesktop.systemd1.xml +++ b/man/org.freedesktop.systemd1.xml @@ -2737,6 +2737,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly as NoExecPaths = ['...', ...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly as ExecSearchPath = ['...', ...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly t MountFlags = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly b PrivateTmp = ...; @@ -3260,6 +3262,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + @@ -3858,6 +3862,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + @@ -4567,6 +4573,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly as NoExecPaths = ['...', ...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly as ExecSearchPath = ['...', ...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly t MountFlags = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly b PrivateTmp = ...; @@ -5118,6 +5126,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { + + @@ -5714,6 +5724,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { + + @@ -6320,6 +6332,8 @@ node /org/freedesktop/systemd1/unit/home_2emount { @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly as NoExecPaths = ['...', ...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly as ExecSearchPath = ['...', ...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly t MountFlags = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly b PrivateTmp = ...; @@ -6799,6 +6813,8 @@ node /org/freedesktop/systemd1/unit/home_2emount { + + @@ -7313,6 +7329,8 @@ node /org/freedesktop/systemd1/unit/home_2emount { + + @@ -8040,6 +8058,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly as NoExecPaths = ['...', ...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly as ExecSearchPath = ['...', ...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly t MountFlags = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly b PrivateTmp = ...; @@ -8505,6 +8525,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { + + @@ -9005,6 +9027,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { + + diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index ddcd0f1c257..3cef36d3c31 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -89,6 +89,20 @@ + + ExecSearchPath= + + Takes a colon separated list of absolute paths relative to which the executable + used by the Exec*= (e.g. ExecStart=, + ExecStop=, etc.) properties can be found. ExecSearchPath= + overrides $PATH if $PATH is not supplied by the user through + Environment=, EnvironmentFile= or + PassEnvironment=. Assigning an empty string removes previous assignments + and setting ExecSearchPath= to a value multiple times will append + to the previous setting. + + + WorkingDirectory= diff --git a/src/analyze/analyze-verify.c b/src/analyze/analyze-verify.c index 2a436c545ef..f3e5d3530d6 100644 --- a/src/analyze/analyze-verify.c +++ b/src/analyze/analyze-verify.c @@ -142,7 +142,7 @@ int verify_executable(Unit *u, const ExecCommand *exec, const char *root) { if (exec->flags & EXEC_COMMAND_IGNORE_FAILURE) return 0; - r = find_executable_full(exec->path, root, false, NULL, NULL); + r = find_executable_full(exec->path, root, NULL, false, NULL, NULL); if (r < 0) return log_unit_error_errno(u, r, "Command %s is not executable: %m", exec->path); diff --git a/src/basic/path-util.c b/src/basic/path-util.c index a21981616b5..eee07b6ea56 100644 --- a/src/basic/path-util.c +++ b/src/basic/path-util.c @@ -682,8 +682,8 @@ static int find_executable_impl(const char *name, const char *root, char **ret_f return 0; } -int find_executable_full(const char *name, const char *root, bool use_path_envvar, char **ret_filename, int *ret_fd) { - int last_error, r; +int find_executable_full(const char *name, const char *root, char **exec_search_path, bool use_path_envvar, char **ret_filename, int *ret_fd) { + int last_error = -ENOENT, r = 0; const char *p = NULL; assert(name); @@ -698,7 +698,27 @@ int find_executable_full(const char *name, const char *root, bool use_path_envva if (!p) p = DEFAULT_PATH; - last_error = -ENOENT; + if (exec_search_path) { + char **element; + + STRV_FOREACH(element, exec_search_path) { + _cleanup_free_ char *full_path = NULL; + if (!path_is_absolute(*element)) + continue; + full_path = path_join(*element, name); + if (!full_path) + return -ENOMEM; + + r = find_executable_impl(full_path, root, ret_filename, ret_fd); + if (r < 0) { + if (r != -EACCES) + last_error = r; + continue; + } + return 0; + } + return last_error; + } /* Resolve a single-component name to a full path */ for (;;) { diff --git a/src/basic/path-util.h b/src/basic/path-util.h index 0d69145497d..518f3340bf2 100644 --- a/src/basic/path-util.h +++ b/src/basic/path-util.h @@ -99,9 +99,9 @@ int path_strv_make_absolute_cwd(char **l); char** path_strv_resolve(char **l, const char *root); char** path_strv_resolve_uniq(char **l, const char *root); -int find_executable_full(const char *name, const char *root, bool use_path_envvar, char **ret_filename, int *ret_fd); +int find_executable_full(const char *name, const char *root, char **exec_search_path, bool use_path_envvar, char **ret_filename, int *ret_fd); static inline int find_executable(const char *name, char **ret_filename) { - return find_executable_full(name, /* root= */ NULL, true, ret_filename, NULL); + return find_executable_full(name, /* root= */ NULL, NULL, true, ret_filename, NULL); } bool paths_check_timestamp(const char* const* paths, usec_t *paths_ts_usec, bool update); diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index 13a64809e0c..8f6042708cd 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -1161,6 +1161,7 @@ const sd_bus_vtable bus_exec_vtable[] = { SD_BUS_PROPERTY("InaccessiblePaths", "as", NULL, offsetof(ExecContext, inaccessible_paths), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("ExecPaths", "as", NULL, offsetof(ExecContext, exec_paths), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("NoExecPaths", "as", NULL, offsetof(ExecContext, no_exec_paths), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ExecSearchPath", "as", NULL, offsetof(ExecContext, exec_search_path), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("MountFlags", "t", bus_property_get_ulong, offsetof(ExecContext, mount_flags), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("PrivateTmp", "b", bus_property_get_bool, offsetof(ExecContext, private_tmp), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("PrivateDevices", "b", bus_property_get_bool, offsetof(ExecContext, private_devices), SD_BUS_VTABLE_PROPERTY_CONST), @@ -3140,6 +3141,36 @@ int bus_exec_context_set_transient_property( return 1; + } else if (streq(name, "ExecSearchPath")) { + _cleanup_strv_free_ char **l = NULL; + char **p; + + r = sd_bus_message_read_strv(message, &l); + if (r < 0) + return r; + + STRV_FOREACH(p, l) { + if (!path_is_absolute(*p) || !path_is_normalized(*p) || strchr(*p, ':')) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid %s", name); + } + if (!UNIT_WRITE_FLAGS_NOOP(flags)) { + if (strv_isempty(l)) { + c->exec_search_path = strv_free(c->exec_search_path); + unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "ExecSearchPath="); + } else { + _cleanup_free_ char *joined = NULL; + r = strv_extend_strv(&c->exec_search_path, l, true); + if (r < 0) + return -ENOMEM; + joined = strv_join(c->exec_search_path, ":"); + if (!joined) + return log_oom(); + unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "ExecSearchPath=%s", joined); + } + } + + return 1; + } else if (STR_IN_SET(name, "RuntimeDirectory", "StateDirectory", "CacheDirectory", "LogsDirectory", "ConfigurationDirectory")) { _cleanup_strv_free_ char **l = NULL; char **p; diff --git a/src/core/execute.c b/src/core/execute.c index 007aab7b059..e7034ce6960 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -3742,7 +3742,7 @@ static int exec_child( int user_lookup_fd, int *exit_status) { - _cleanup_strv_free_ char **our_env = NULL, **pass_env = NULL, **accum_env = NULL, **replaced_argv = NULL; + _cleanup_strv_free_ char **our_env = NULL, **pass_env = NULL, **joined_exec_search_path = NULL, **accum_env = NULL, **replaced_argv = NULL; int r, ngids = 0, exec_fd; _cleanup_free_ gid_t *supplementary_gids = NULL; const char *username = NULL, *groupname = NULL; @@ -4158,8 +4158,31 @@ static int exec_child( return log_oom(); } + /* The PATH variable is set to the default path in params->environment. + * However, this is overridden if user specified fields have PATH set. + * The intention is to also override PATH if the user does + * not specify PATH and the user has specified ExecSearchPath + */ + + if (!strv_isempty(context->exec_search_path)) { + _cleanup_free_ char *joined = NULL; + + joined = strv_join(context->exec_search_path, ":"); + if (!joined) { + *exit_status = EXIT_MEMORY; + return log_oom(); + } + + r = strv_env_assign(&joined_exec_search_path, "PATH", joined); + if (r < 0) { + *exit_status = EXIT_MEMORY; + return log_oom(); + } + } + accum_env = strv_env_merge(params->environment, our_env, + joined_exec_search_path, pass_env, context->environment, files_env); @@ -4350,7 +4373,7 @@ static int exec_child( _cleanup_free_ char *executable = NULL; _cleanup_close_ int executable_fd = -1; - r = find_executable_full(command->path, /* root= */ NULL, false, &executable, &executable_fd); + r = find_executable_full(command->path, /* root= */ NULL, context->exec_search_path, false, &executable, &executable_fd); if (r < 0) { if (r != -ENOMEM && (command->flags & EXEC_COMMAND_IGNORE_FAILURE)) { log_unit_struct_errno(unit, LOG_INFO, r, @@ -4926,6 +4949,7 @@ void exec_context_done(ExecContext *c) { c->inaccessible_paths = strv_free(c->inaccessible_paths); c->exec_paths = strv_free(c->exec_paths); c->no_exec_paths = strv_free(c->no_exec_paths); + c->exec_search_path = strv_free(c->exec_search_path); bind_mount_free_many(c->bind_mounts, c->n_bind_mounts); c->bind_mounts = NULL; @@ -5597,6 +5621,7 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) { strv_dump(f, prefix, "InaccessiblePaths", c->inaccessible_paths); strv_dump(f, prefix, "ExecPaths", c->exec_paths); strv_dump(f, prefix, "NoExecPaths", c->no_exec_paths); + strv_dump(f, prefix, "ExecSearchPath", c->exec_search_path); for (size_t i = 0; i < c->n_bind_mounts; i++) fprintf(f, "%s%s: %s%s:%s:%s\n", prefix, diff --git a/src/core/execute.h b/src/core/execute.h index fceb8bb6f79..64a38b2d26a 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -254,6 +254,7 @@ struct ExecContext { char *smack_process_label; char **read_write_paths, **read_only_paths, **inaccessible_paths, **exec_paths, **no_exec_paths; + char **exec_search_path; unsigned long mount_flags; BindMount *bind_mounts; size_t n_bind_mounts; diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in index 17c43005bca..7bef95bec75 100644 --- a/src/core/load-fragment-gperf.gperf.in +++ b/src/core/load-fragment-gperf.gperf.in @@ -105,6 +105,7 @@ {{type}}.InaccessiblePaths, config_parse_namespace_path_strv, 0, offsetof({{type}}, exec_context.inaccessible_paths) {{type}}.ExecPaths, config_parse_namespace_path_strv, 0, offsetof({{type}}, exec_context.exec_paths) {{type}}.NoExecPaths, config_parse_namespace_path_strv, 0, offsetof({{type}}, exec_context.no_exec_paths) +{{type}}.ExecSearchPath, config_parse_colon_separated_paths, 0, offsetof({{type}}, exec_context.exec_search_path) {{type}}.BindPaths, config_parse_bind_paths, 0, offsetof({{type}}, exec_context) {{type}}.BindReadOnlyPaths, config_parse_bind_paths, 0, offsetof({{type}}, exec_context) {{type}}.TemporaryFileSystem, config_parse_temporary_filesystems, 0, offsetof({{type}}, exec_context) diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index 4f74f44c7dc..f971084c28e 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -305,6 +305,64 @@ int config_parse_unit_path_printf( return config_parse_path(unit, filename, line, section, section_line, lvalue, ltype, k, data, userdata); } +int config_parse_colon_separated_paths( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + char ***sv = data; + const Unit *u = userdata; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + /* Empty assignment resets the list */ + *sv = strv_free(*sv); + return 0; + } + + for (const char *p = rvalue;;) { + _cleanup_free_ char *word = NULL, *k = NULL; + + r = extract_first_word(&p, &word, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to extract first word, ignoring: %s", rvalue); + return 0; + } + if (r == 0) + break; + + r = unit_path_printf(u, word, &k); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to resolve unit specifiers in '%s', ignoring: %m", word); + return 0; + } + + r = path_simplify_and_warn(k, PATH_CHECK_ABSOLUTE, unit, filename, line, lvalue); + if (r < 0) + return 0; + + r = strv_consume(sv, TAKE_PTR(k)); + if (r < 0) + return log_oom(); + } + + return 0; +} + int config_parse_unit_path_strv_printf( const char *unit, const char *filename, @@ -5915,6 +5973,7 @@ void unit_dump_config_items(FILE *f) { { config_parse_string, "STRING" }, { config_parse_path, "PATH" }, { config_parse_unit_path_printf, "PATH" }, + { config_parse_colon_separated_paths, "PATH" }, { config_parse_strv, "STRING [...]" }, { config_parse_exec_nice, "NICE" }, { config_parse_exec_oom_score_adjust, "OOMSCOREADJUST" }, diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index 0d8863aa635..e84f9ee3910 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -19,6 +19,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_obsolete_unit_deps); CONFIG_PARSER_PROTOTYPE(config_parse_unit_string_printf); CONFIG_PARSER_PROTOTYPE(config_parse_unit_strv_printf); CONFIG_PARSER_PROTOTYPE(config_parse_unit_path_printf); +CONFIG_PARSER_PROTOTYPE(config_parse_colon_separated_paths); CONFIG_PARSER_PROTOTYPE(config_parse_unit_path_strv_printf); CONFIG_PARSER_PROTOTYPE(config_parse_documentation); CONFIG_PARSER_PROTOTYPE(config_parse_socket_listen); diff --git a/src/run/run.c b/src/run/run.c index 664153137df..29ab3f33929 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -1714,6 +1714,7 @@ static int run(int argc, char* argv[]) { if (!strv_isempty(arg_cmdline) && arg_transport == BUS_TRANSPORT_LOCAL && !strv_find_startswith(arg_property, "RootDirectory=") && + !strv_find_startswith(arg_property, "ExecSearchPath=") && !strv_find_startswith(arg_property, "RootImage=")) { /* Patch in an absolute path to fail early for user convenience, but only when we can do it * (i.e. we will be running from the same file system). This also uses the user's $PATH, diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index f9637ac0cce..e33e0c21e02 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -969,6 +969,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con "InaccessiblePaths", "ExecPaths", "NoExecPaths", + "ExecSearchPath", "RuntimeDirectory", "StateDirectory", "CacheDirectory", diff --git a/src/test/test-execute.c b/src/test/test-execute.c index 092c78f2b9d..99beb05d29d 100644 --- a/src/test/test-execute.c +++ b/src/test/test-execute.c @@ -8,6 +8,7 @@ #include "capability-util.h" #include "cpu-set-util.h" +#include "copy.h" #include "dropin.h" #include "errno-list.h" #include "fd-util.h" @@ -28,6 +29,7 @@ #include "static-destruct.h" #include "stat-util.h" #include "tests.h" +#include "tmpfile-util.h" #include "unit.h" #include "user-util.h" #include "util.h" @@ -273,6 +275,93 @@ static void test_exec_workingdirectory(Manager *m) { (void) rm_rf("/tmp/test-exec_workingdirectory", REMOVE_ROOT|REMOVE_PHYSICAL); } +static void test_exec_execsearchpath(Manager *m) { + assert_se(mkdir_p("/tmp/test-exec_execsearchpath", 0755) >= 0); + + assert_se(copy_file("/bin/ls", "/tmp/test-exec_execsearchpath/ls_temp", 0, 0777, 0, 0, COPY_REPLACE) >= 0); + + test(m, "exec-execsearchpath.service", 0, CLD_EXITED); + + assert_se(rm_rf("/tmp/test-exec_execsearchpath", REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); + + test(m, "exec-execsearchpath.service", EXIT_EXEC, CLD_EXITED); +} + +static void test_exec_execsearchpath_specifier(Manager *m) { + test(m, "exec-execsearchpath-unit-specifier.service", 0, CLD_EXITED); +} + +static void test_exec_execsearchpath_environment(Manager *m) { + test(m, "exec-execsearchpath-environment.service", 0, CLD_EXITED); + test(m, "exec-execsearchpath-environment-path-set.service", 0, CLD_EXITED); +} + +static void test_exec_execsearchpath_environment_files(Manager *m) { + static const char path_not_set[] = + "VAR1='word1 word2'\n" + "VAR2=word3 \n" + "# comment1\n" + "\n" + "; comment2\n" + " ; # comment3\n" + "line without an equal\n" + "VAR3='$word 5 6'\n" + "VAR4='new\nline'\n" + "VAR5=password\\with\\backslashes"; + + static const char path_set[] = + "VAR1='word1 word2'\n" + "VAR2=word3 \n" + "# comment1\n" + "\n" + "; comment2\n" + " ; # comment3\n" + "line without an equal\n" + "VAR3='$word 5 6'\n" + "VAR4='new\nline'\n" + "VAR5=password\\with\\backslashes\n" + "PATH=/usr"; + + int r; + + r = write_string_file("/tmp/test-exec_execsearchpath_environmentfile.conf", path_not_set, WRITE_STRING_FILE_CREATE); + + assert_se(r == 0); + + test(m, "exec-execsearchpath-environmentfile.service", 0, CLD_EXITED); + + (void) unlink("/tmp/test-exec_environmentfile.conf"); + + + r = write_string_file("/tmp/test-exec_execsearchpath_environmentfile-set.conf", path_set, WRITE_STRING_FILE_CREATE); + + assert_se(r == 0); + + test(m, "exec-execsearchpath-environmentfile-set.service", 0, CLD_EXITED); + + (void) unlink("/tmp/test-exec_environmentfile-set.conf"); +} + +static void test_exec_execsearchpath_passenvironment(Manager *m) { + assert_se(setenv("VAR1", "word1 word2", 1) == 0); + assert_se(setenv("VAR2", "word3", 1) == 0); + assert_se(setenv("VAR3", "$word 5 6", 1) == 0); + assert_se(setenv("VAR4", "new\nline", 1) == 0); + assert_se(setenv("VAR5", "passwordwithbackslashes", 1) == 0); + + test(m, "exec-execsearchpath-passenvironment.service", 0, CLD_EXITED); + + assert_se(setenv("PATH", "/usr", 1) == 0); + test(m, "exec-execsearchpath-passenvironment-set.service", 0, CLD_EXITED); + + assert_se(unsetenv("VAR1") == 0); + assert_se(unsetenv("VAR2") == 0); + assert_se(unsetenv("VAR3") == 0); + assert_se(unsetenv("VAR4") == 0); + assert_se(unsetenv("VAR5") == 0); + assert_se(unsetenv("PATH") == 0); +} + static void test_exec_personality(Manager *m) { #if defined(__x86_64__) test(m, "exec-personality-x86-64.service", 0, CLD_EXITED); @@ -1089,11 +1178,16 @@ int main(int argc, char *argv[]) { entry(test_exec_unsetenvironment), entry(test_exec_user), entry(test_exec_workingdirectory), + entry(test_exec_execsearchpath), + entry(test_exec_execsearchpath_environment), + entry(test_exec_execsearchpath_environment_files), + entry(test_exec_execsearchpath_passenvironment), {}, }; static const test_entry system_tests[] = { entry(test_exec_dynamicuser), entry(test_exec_specifier), + entry(test_exec_execsearchpath_specifier), entry(test_exec_systemcallfilter_system), {}, }; diff --git a/src/test/test-path-util.c b/src/test/test-path-util.c index 1a0fda5d1af..0e8648aa6da 100644 --- a/src/test/test-path-util.c +++ b/src/test/test-path-util.c @@ -6,6 +6,7 @@ #include "alloc-util.h" #include "exec-util.h" #include "fd-util.h" +#include "fs-util.h" #include "macro.h" #include "path-util.h" #include "process-util.h" @@ -14,6 +15,7 @@ #include "string-util.h" #include "strv.h" #include "tests.h" +#include "tmpfile-util.h" #include "util.h" static void test_print_paths(void) { @@ -201,15 +203,18 @@ static void test_path_equal_root(void) { static void test_find_executable_full(void) { char *p; + char* test_file_name; + _cleanup_close_ int fd = -1; + char fn[] = "/tmp/test-XXXXXX"; log_info("/* %s */", __func__); - assert_se(find_executable_full("sh", NULL, true, &p, NULL) == 0); + assert_se(find_executable_full("sh", NULL, NULL, true, &p, NULL) == 0); puts(p); assert_se(streq(basename(p), "sh")); free(p); - assert_se(find_executable_full("sh", NULL, false, &p, NULL) == 0); + assert_se(find_executable_full("sh", NULL, NULL, false, &p, NULL) == 0); puts(p); assert_se(streq(basename(p), "sh")); free(p); @@ -221,18 +226,31 @@ static void test_find_executable_full(void) { assert_se(unsetenv("PATH") == 0); - assert_se(find_executable_full("sh", NULL, true, &p, NULL) == 0); + assert_se(find_executable_full("sh", NULL, NULL, true, &p, NULL) == 0); puts(p); assert_se(streq(basename(p), "sh")); free(p); - assert_se(find_executable_full("sh", NULL, false, &p, NULL) == 0); + assert_se(find_executable_full("sh", NULL, NULL, false, &p, NULL) == 0); puts(p); assert_se(streq(basename(p), "sh")); free(p); if (oldpath) assert_se(setenv("PATH", oldpath, true) >= 0); + + assert_se((fd = mkostemp_safe(fn)) >= 0); + assert_se(fchmod(fd, 0755) >= 0); + + test_file_name = basename(fn); + + assert_se(find_executable_full(test_file_name, NULL, STRV_MAKE("/doesnotexist", "/tmp", "/bin"), false, &p, NULL) == 0); + puts(p); + assert_se(streq(p, fn)); + free(p); + + (void) unlink(fn); + assert_se(find_executable_full(test_file_name, NULL, STRV_MAKE("/doesnotexist", "/tmp", "/bin"), false, &p, NULL) == -ENOENT); } static void test_find_executable(const char *self) { @@ -277,7 +295,7 @@ static void test_find_executable_exec_one(const char *path) { pid_t pid; int r; - r = find_executable_full(path, NULL, false, &t, &fd); + r = find_executable_full(path, NULL, NULL, false, &t, &fd); log_info_errno(r, "%s: %s → %s: %d/%m", __func__, path, t ?: "-", fd); diff --git a/test/fuzz/fuzz-unit-file/directives.mount b/test/fuzz/fuzz-unit-file/directives.mount index 33c44c79c5d..bdb8f95917e 100644 --- a/test/fuzz/fuzz-unit-file/directives.mount +++ b/test/fuzz/fuzz-unit-file/directives.mount @@ -5,6 +5,7 @@ AllowedMemoryNodes= AmbientCapabilities= AppArmorProfile= BPFProgram= +ExecSearchPath= BindPaths= BindReadOnlyPaths= BlockIOAccounting= diff --git a/test/fuzz/fuzz-unit-file/directives.service b/test/fuzz/fuzz-unit-file/directives.service index d92d90e1181..aeea1d8731c 100644 --- a/test/fuzz/fuzz-unit-file/directives.service +++ b/test/fuzz/fuzz-unit-file/directives.service @@ -31,6 +31,7 @@ AssertUser= AssertVirtualization= BPFProgram= Before= +ExecSearchPath= BindTo= BindsTo= CollectMode= diff --git a/test/fuzz/fuzz-unit-file/directives.socket b/test/fuzz/fuzz-unit-file/directives.socket index fa28ff10421..04aeb476f11 100644 --- a/test/fuzz/fuzz-unit-file/directives.socket +++ b/test/fuzz/fuzz-unit-file/directives.socket @@ -7,6 +7,7 @@ AmbientCapabilities= AppArmorProfile= BPFProgram= Backlog= +ExecSearchPath= BindIPv6Only= BindPaths= BindReadOnlyPaths= diff --git a/test/fuzz/fuzz-unit-file/directives.swap b/test/fuzz/fuzz-unit-file/directives.swap index abb3cd54e71..ff54ebfc189 100644 --- a/test/fuzz/fuzz-unit-file/directives.swap +++ b/test/fuzz/fuzz-unit-file/directives.swap @@ -5,6 +5,7 @@ AllowedMemoryNodes= AmbientCapabilities= AppArmorProfile= BPFProgram= +ExecSearchPath= BindPaths= BindReadOnlyPaths= BlockIOAccounting= diff --git a/test/test-execute/exec-execsearchpath-environment-path-set.service b/test/test-execute/exec-execsearchpath-environment-path-set.service new file mode 100644 index 00000000000..dd27de84a85 --- /dev/null +++ b/test/test-execute/exec-execsearchpath-environment-path-set.service @@ -0,0 +1,5 @@ +[Service] +ExecStart=/bin/sh -x -c 'test "$$PATH" = "/usr" && test "$$VAR1" = word3 && test "$$VAR2" = "\\$$word 5 6"' +Type=oneshot +ExecSearchPath=/tmp:/bin +Environment="PATH=/usr" VAR1=word3 "VAR2=$word 5 6" diff --git a/test/test-execute/exec-execsearchpath-environment.service b/test/test-execute/exec-execsearchpath-environment.service new file mode 100644 index 00000000000..ba477fbdcdb --- /dev/null +++ b/test/test-execute/exec-execsearchpath-environment.service @@ -0,0 +1,5 @@ +[Service] +ExecStart=/bin/sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$PATH" = "/tmp:/bin"' +Type=oneshot +ExecSearchPath=/tmp:/bin +Environment="VAR1=word1 word2" VAR2=word3 "VAR3=$word 5 6" diff --git a/test/test-execute/exec-execsearchpath-environmentfile-set.service b/test/test-execute/exec-execsearchpath-environmentfile-set.service new file mode 100644 index 00000000000..45877b1d127 --- /dev/null +++ b/test/test-execute/exec-execsearchpath-environmentfile-set.service @@ -0,0 +1,8 @@ +[Unit] +Description=Test for ExecSearchPath with EnvironmentFile where EnvironmentFile sets PATH + +[Service] +ExecStart=/bin/sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$VAR4" = "new\nline" && test "$$VAR5" = passwordwithbackslashes && test "$$PATH" = /usr' +Type=oneshot +EnvironmentFile=/tmp/test-exec_execsearchpath_environmentfile-set.conf +ExecSearchPath=/tmp:/bin diff --git a/test/test-execute/exec-execsearchpath-environmentfile.service b/test/test-execute/exec-execsearchpath-environmentfile.service new file mode 100644 index 00000000000..65a04e7dbe4 --- /dev/null +++ b/test/test-execute/exec-execsearchpath-environmentfile.service @@ -0,0 +1,8 @@ +[Unit] +Description=Test for ExecSearchPath with EnvironmentFile where EnvironmentFile does not set PATH + +[Service] +ExecStart=/bin/sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$VAR4" = "new\nline" && test "$$VAR5" = passwordwithbackslashes && test "$$PATH" = "/tmp:/bin"' +Type=oneshot +ExecSearchPath=/tmp:/bin +EnvironmentFile=/tmp/test-exec_execsearchpath_environmentfile.conf diff --git a/test/test-execute/exec-execsearchpath-passenvironment-set.service b/test/test-execute/exec-execsearchpath-passenvironment-set.service new file mode 100644 index 00000000000..62c7742b10a --- /dev/null +++ b/test/test-execute/exec-execsearchpath-passenvironment-set.service @@ -0,0 +1,8 @@ +[Unit] +Description=Test for PassEnvironment with ExecSearchPath with PATH set by user + +[Service] +ExecStart=/bin/sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$VAR4" = "new\nline" && test "$$VAR5" = passwordwithbackslashes && test "$$PATH" = "/usr"' +Type=oneshot +PassEnvironment=VAR1 VAR2 VAR3 VAR4 VAR5 PATH +ExecSearchPath=/tmp:/bin diff --git a/test/test-execute/exec-execsearchpath-passenvironment.service b/test/test-execute/exec-execsearchpath-passenvironment.service new file mode 100644 index 00000000000..83eb0d680ad --- /dev/null +++ b/test/test-execute/exec-execsearchpath-passenvironment.service @@ -0,0 +1,8 @@ +[Unit] +Description=Test for PassEnvironment with ExecSearchPath with PATH not set by user + +[Service] +ExecStart=/bin/sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$VAR4" = "new\nline" && test "$$VAR5" = passwordwithbackslashes && test "$$PATH" = "/tmp:/bin"' +Type=oneshot +PassEnvironment=VAR1 VAR2 VAR3 VAR4 VAR5 +ExecSearchPath=/tmp:/bin diff --git a/test/test-execute/exec-execsearchpath-unit-specifier.service b/test/test-execute/exec-execsearchpath-unit-specifier.service new file mode 100644 index 00000000000..740bab1d416 --- /dev/null +++ b/test/test-execute/exec-execsearchpath-unit-specifier.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test for specifiers with exec search path + +[Service] +Type=oneshot +ExecSearchPath=/tmp:/bin:/usr/bin:%V +ExecStart=/bin/sh -x -c 'test %V = /var/tmp && test "$$PATH" = "/tmp:/bin:/usr/bin:/var/tmp"' diff --git a/test/test-execute/exec-execsearchpath.service b/test/test-execute/exec-execsearchpath.service new file mode 100644 index 00000000000..67b71709f36 --- /dev/null +++ b/test/test-execute/exec-execsearchpath.service @@ -0,0 +1,4 @@ +[Service] +ExecStart=ls_temp +Type=oneshot +ExecSearchPath=/tmp/test-exec_execsearchpath -- 2.39.5