diff --git a/src/basic/user-util.c b/src/basic/user-util.c index 9dcf16d7a43..2b7c923b5e6 100644 --- a/src/basic/user-util.c +++ b/src/basic/user-util.c @@ -220,9 +220,9 @@ static int synthesize_user_creds( if (ret_gid) *ret_gid = GID_NOBODY; if (ret_home) - *ret_home = FLAGS_SET(flags, USER_CREDS_CLEAN) ? NULL : "/"; + *ret_home = FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) ? NULL : "/"; if (ret_shell) - *ret_shell = FLAGS_SET(flags, USER_CREDS_CLEAN) ? NULL : NOLOGIN; + *ret_shell = FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) ? NULL : NOLOGIN; return 0; } @@ -244,6 +244,7 @@ int get_user_creds( assert(username); assert(*username); + assert((ret_home || ret_shell) || !(flags & (USER_CREDS_SUPPRESS_PLACEHOLDER|USER_CREDS_CLEAN))); if (!FLAGS_SET(flags, USER_CREDS_PREFER_NSS) || (!ret_home && !ret_shell)) { @@ -315,17 +316,14 @@ int get_user_creds( if (ret_home) /* Note: we don't insist on normalized paths, since there are setups that have /./ in the path */ - *ret_home = (FLAGS_SET(flags, USER_CREDS_CLEAN) && - (empty_or_root(p->pw_dir) || - !path_is_valid(p->pw_dir) || - !path_is_absolute(p->pw_dir))) ? NULL : p->pw_dir; + *ret_home = (FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) && empty_or_root(p->pw_dir)) || + (FLAGS_SET(flags, USER_CREDS_CLEAN) && (!path_is_valid(p->pw_dir) || !path_is_absolute(p->pw_dir))) + ? NULL : p->pw_dir; if (ret_shell) - *ret_shell = (FLAGS_SET(flags, USER_CREDS_CLEAN) && - (isempty(p->pw_shell) || - !path_is_valid(p->pw_shell) || - !path_is_absolute(p->pw_shell) || - is_nologin_shell(p->pw_shell))) ? NULL : p->pw_shell; + *ret_shell = (FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) && shell_is_placeholder(p->pw_shell)) || + (FLAGS_SET(flags, USER_CREDS_CLEAN) && (!path_is_valid(p->pw_shell) || !path_is_absolute(p->pw_shell))) + ? NULL : p->pw_shell; if (patch_username) *username = p->pw_name; diff --git a/src/basic/user-util.h b/src/basic/user-util.h index 777451b8c88..6f221ebfb0b 100644 --- a/src/basic/user-util.h +++ b/src/basic/user-util.h @@ -12,6 +12,8 @@ #include #include +#include "string-util.h" + /* Users managed by systemd-homed. See https://systemd.io/UIDS-GIDS for details how this range fits into the rest of the world */ #define HOME_UID_MIN ((uid_t) 60001) #define HOME_UID_MAX ((uid_t) 60513) @@ -36,10 +38,20 @@ static inline int parse_gid(const char *s, gid_t *ret_gid) { char* getlogname_malloc(void); char* getusername_malloc(void); +const char* default_root_shell_at(int rfd); +const char* default_root_shell(const char *root); + +bool is_nologin_shell(const char *shell); + +static inline bool shell_is_placeholder(const char *shell) { + return isempty(shell) || is_nologin_shell(shell); +} + typedef enum UserCredsFlags { - USER_CREDS_PREFER_NSS = 1 << 0, /* if set, only synthesize user records if database lacks them. Normally we bypass the userdb entirely for the records we can synthesize */ - USER_CREDS_ALLOW_MISSING = 1 << 1, /* if a numeric UID string is resolved, be OK if there's no record for it */ - USER_CREDS_CLEAN = 1 << 2, /* try to clean up shell and home fields with invalid data */ + USER_CREDS_PREFER_NSS = 1 << 0, /* if set, only synthesize user records if database lacks them. Normally we bypass the userdb entirely for the records we can synthesize */ + USER_CREDS_ALLOW_MISSING = 1 << 1, /* if a numeric UID string is resolved, be OK if there's no record for it */ + USER_CREDS_CLEAN = 1 << 2, /* try to clean up shell and home fields with invalid data */ + USER_CREDS_SUPPRESS_PLACEHOLDER = 1 << 3, /* suppress home and/or shell fields if value is placeholder (root/empty/nologin) */ } UserCredsFlags; int get_user_creds(const char **username, uid_t *ret_uid, gid_t *ret_gid, const char **ret_home, const char **ret_shell, UserCredsFlags flags); @@ -125,10 +137,6 @@ int fgetsgent_sane(FILE *stream, struct sgrp **sg); int putsgent_sane(const struct sgrp *sg, FILE *stream); #endif -bool is_nologin_shell(const char *shell); -const char* default_root_shell_at(int rfd); -const char* default_root_shell(const char *root); - int is_this_me(const char *username); const char* get_home_root(void); diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index 2f8924cd018..9d636f55295 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -855,9 +855,6 @@ static int get_fixed_user( assert(user_or_uid); assert(ret_username); - /* Note that we don't set $HOME or $SHELL if they are not particularly enlightening anyway - * (i.e. are "/" or "/bin/nologin"). */ - r = get_user_creds(&user_or_uid, ret_uid, ret_gid, ret_home, ret_shell, USER_CREDS_CLEAN); if (r < 0) return r; @@ -1883,7 +1880,10 @@ static int build_environment( } } - if (home && set_user_login_env) { + /* Note that we don't set $HOME or $SHELL if they are not particularly enlightening anyway + * (i.e. are "/" or "/bin/nologin"). */ + + if (home && set_user_login_env && !empty_or_root(home)) { x = strjoin("HOME=", home); if (!x) return -ENOMEM; @@ -1892,7 +1892,7 @@ static int build_environment( our_env[n_env++] = x; } - if (shell && set_user_login_env) { + if (shell && set_user_login_env && !shell_is_placeholder(shell)) { x = strjoin("SHELL=", shell); if (!x) return -ENOMEM; @@ -3471,20 +3471,16 @@ static int apply_working_directory( const ExecContext *context, const ExecParameters *params, ExecRuntime *runtime, - const char *home, - int *exit_status) { + const char *home) { const char *wd; int r; assert(context); - assert(exit_status); if (context->working_directory_home) { - if (!home) { - *exit_status = EXIT_CHDIR; + if (!home) return -ENXIO; - } wd = home; } else @@ -3503,13 +3499,7 @@ static int apply_working_directory( if (r >= 0) r = RET_NERRNO(fchdir(dfd)); } - - if (r < 0 && !context->working_directory_missing_ok) { - *exit_status = EXIT_CHDIR; - return r; - } - - return 0; + return context->working_directory_missing_ok ? 0 : r; } static int apply_root_directory( @@ -3785,7 +3775,7 @@ static int acquire_home(const ExecContext *c, const char **home, char **ret_buf) if (!c->working_directory_home) return 0; - if (c->dynamic_user) + if (c->dynamic_user || (c->user && is_this_me(c->user) <= 0)) return -EADDRNOTAVAIL; r = get_home_dir(ret_buf); @@ -4543,7 +4533,7 @@ int exec_invoke( r = acquire_home(context, &home, &home_buffer); if (r < 0) { *exit_status = EXIT_CHDIR; - return log_exec_error_errno(context, params, r, "Failed to determine $HOME for user: %m"); + return log_exec_error_errno(context, params, r, "Failed to determine $HOME for the invoking user: %m"); } /* If a socket is connected to STDIN/STDOUT/STDERR, we must drop O_NONBLOCK */ @@ -5382,9 +5372,11 @@ int exec_invoke( * running this service might have the correct privilege to change to the working directory. Also, it * is absolutely 💣 crucial 💣 we applied all mount namespacing rearrangements before this, so that * the cwd cannot be used to pin directories outside of the sandbox. */ - r = apply_working_directory(context, params, runtime, home, exit_status); - if (r < 0) + r = apply_working_directory(context, params, runtime, home); + if (r < 0) { + *exit_status = EXIT_CHDIR; return log_exec_error_errno(context, params, r, "Changing to the requested working directory failed: %m"); + } if (needs_sandboxing) { /* Apply other MAC contexts late, but before seccomp syscall filtering, as those should really be last to diff --git a/src/run/run.c b/src/run/run.c index c62dce8950f..1b13e74b83c 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -2297,7 +2297,8 @@ static int start_transient_scope(sd_bus *bus) { uid_t uid; gid_t gid; - r = get_user_creds(&arg_exec_user, &uid, &gid, &home, &shell, USER_CREDS_CLEAN|USER_CREDS_PREFER_NSS); + r = get_user_creds(&arg_exec_user, &uid, &gid, &home, &shell, + USER_CREDS_CLEAN|USER_CREDS_SUPPRESS_PLACEHOLDER|USER_CREDS_PREFER_NSS); if (r < 0) return log_error_errno(r, "Failed to resolve user %s: %m", arg_exec_user); diff --git a/test/units/TEST-07-PID1.working-directory.sh b/test/units/TEST-07-PID1.working-directory.sh new file mode 100755 index 00000000000..1cff3e0602c --- /dev/null +++ b/test/units/TEST-07-PID1.working-directory.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later + +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +(! systemd-run --wait -p DynamicUser=yes \ + -p EnvironmentFile=-/usr/lib/systemd/systemd-asan-env \ + -p WorkingDirectory='~' true) + +assert_eq "$(systemd-run --pipe --uid=root -p WorkingDirectory='~' pwd)" "/root" +assert_eq "$(systemd-run --pipe --uid=nobody -p WorkingDirectory='~' pwd)" "/" +assert_eq "$(systemd-run --pipe --uid=testuser -p WorkingDirectory='~' pwd)" "/home/testuser" + +(! systemd-run --wait -p DynamicUser=yes -p User=testuser \ + -p EnvironmentFile=-/usr/lib/systemd/systemd-asan-env \ + -p WorkingDirectory='~' true)