From ba7fb8cf5f8f6ad26ff5509722ab4795e566bf09 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Tue, 8 Apr 2025 22:35:14 +0200 Subject: [PATCH 1/4] run0: make sure we submit $SHELL to remote Normally, the service manager sets $SHELL to the target user's login shell, but run0 always overrides that with either originating user's shell or value from --setenv=SHELL=. In both cases $SHELL needs to be sent. Fixes #35007 --- src/run/run.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/run/run.c b/src/run/run.c index e4ae5373b9..cc00f93041 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -1029,18 +1029,25 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) { const char *e; e = strv_env_get(arg_environment, "SHELL"); - if (e) + if (e) { arg_exec_path = strdup(e); - else { + if (!arg_exec_path) + return log_oom(); + } else { if (arg_transport == BUS_TRANSPORT_LOCAL) { r = get_shell(&arg_exec_path); if (r < 0) return log_error_errno(r, "Failed to determine shell: %m"); - } else + } else { arg_exec_path = strdup("/bin/sh"); + if (!arg_exec_path) + return log_oom(); + } + + r = strv_env_assign(&arg_environment, "SHELL", arg_exec_path); + if (r < 0) + return log_error_errno(r, "Failed to set $SHELL environment variable: %m"); } - if (!arg_exec_path) - return log_oom(); l = make_login_shell_cmdline(arg_exec_path); } From 2fb438cc88826399f39305765a7881cbf63b5e49 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Tue, 8 Apr 2025 16:43:28 +0200 Subject: [PATCH 2/4] core/exec-invoke: never override acquired user cred with fallback one --- src/core/exec-invoke.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index 7e2cdd8c46..1c01a46615 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -1973,7 +1973,9 @@ static int build_environment( * could cause problem for e.g. getty, since login doesn't override $HOME, and $LOGNAME and $SHELL don't * really make much sense since we're not logged in. Hence we conditionalize the three based on * SetLoginEnvironment= switch. */ - if (!c->user && !c->dynamic_user && p->runtime_scope == RUNTIME_SCOPE_SYSTEM) { + if (!username && !c->dynamic_user && p->runtime_scope == RUNTIME_SCOPE_SYSTEM) { + assert(!c->user); + r = get_fixed_user("root", &username, NULL, NULL, &home, &shell); if (r < 0) return log_exec_debug_errno(c, From 9c0d8b8c4f18de76f945e82030db82135e7ffa34 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Tue, 8 Apr 2025 15:20:21 +0200 Subject: [PATCH 3/4] core/exec-invoke: consult NSS for root user creds if SetLoginEnvironment=/PAMName= --- src/core/exec-invoke.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index 1c01a46615..9f2ce4d42b 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -854,6 +854,7 @@ restore_stdio: static int get_fixed_user( const char *user_or_uid, + bool prefer_nss, const char **ret_username, uid_t *ret_uid, gid_t *ret_gid, @@ -865,7 +866,8 @@ static int get_fixed_user( assert(user_or_uid); assert(ret_username); - r = get_user_creds(&user_or_uid, ret_uid, ret_gid, ret_home, ret_shell, USER_CREDS_CLEAN); + r = get_user_creds(&user_or_uid, ret_uid, ret_gid, ret_home, ret_shell, + USER_CREDS_CLEAN|(prefer_nss ? USER_CREDS_PREFER_NSS : 0)); if (r < 0) return r; @@ -1976,7 +1978,7 @@ static int build_environment( if (!username && !c->dynamic_user && p->runtime_scope == RUNTIME_SCOPE_SYSTEM) { assert(!c->user); - r = get_fixed_user("root", &username, NULL, NULL, &home, &shell); + r = get_fixed_user("root", /* prefer_nss = */ false, &username, NULL, NULL, &home, &shell); if (r < 0) return log_exec_debug_errno(c, p, @@ -4906,7 +4908,14 @@ int exec_invoke( u = NULL; if (u) { - r = get_fixed_user(u, &username, &uid, &gid, &pwent_home, &shell); + /* We can't use nss unconditionally for root without risking deadlocks if some IPC services + * will be started by pid1 and are ordered after us. But if SetLoginEnvironment= is + * enabled *explicitly* (i.e. no exec_context_get_set_login_environment() here), + * or PAM shall be invoked, let's consult NSS even for root, so that the user + * gets accurate $SHELL in session(-like) contexts. */ + r = get_fixed_user(u, + /* prefer_nss = */ context->set_login_environment > 0 || context->pam_name, + &username, &uid, &gid, &pwent_home, &shell); if (r < 0) { *exit_status = EXIT_USER; return log_exec_error_errno(context, params, r, "Failed to determine user credentials: %m"); From aadb8f978475d622ead0814db0d970f09592cb25 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Wed, 9 Apr 2025 01:38:52 +0200 Subject: [PATCH 4/4] test: add test case for PAMName= $SHELL acquisition for root --- test/units/TEST-74-AUX-UTILS.run.sh | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/test/units/TEST-74-AUX-UTILS.run.sh b/test/units/TEST-74-AUX-UTILS.run.sh index f6f422a305..3229a522b7 100755 --- a/test/units/TEST-74-AUX-UTILS.run.sh +++ b/test/units/TEST-74-AUX-UTILS.run.sh @@ -248,13 +248,19 @@ if [[ -e /usr/lib/pam.d/systemd-run0 ]] || [[ -e /etc/pam.d/systemd-run0 ]]; the run0 ls / assert_eq "$(run0 echo foo)" "foo" # Check if we set some expected environment variables - for arg in "" "--user=root" "--user=0" "--user=testuser"; do - assert_eq "$(run0 ${arg:+"$arg"} bash -c 'echo $SUDO_USER')" "$USER" - assert_eq "$(run0 ${arg:+"$arg"} bash -c 'echo $SUDO_UID')" "$(id -u "$USER")" - assert_eq "$(run0 ${arg:+"$arg"} bash -c 'echo $SUDO_GID')" "$(id -u "$USER")" + for tu in "" "root" "0" "testuser"; do + assert_eq "$(run0 ${tu:+"--user=$tu"} bash -c 'echo $SUDO_USER')" "$USER" + assert_eq "$(run0 ${tu:+"--user=$tu"} bash -c 'echo $SUDO_UID')" "$(id -u "$USER")" + assert_eq "$(run0 ${tu:+"--user=$tu"} bash -c 'echo $SUDO_GID')" "$(id -u "$USER")" # Validate that we actually went properly through PAM (XDG_SESSION_TYPE is set by pam_systemd) - assert_eq "$(run0 ${arg:+"$arg"} bash -c 'echo $XDG_SESSION_TYPE')" "unspecified" + assert_eq "$(run0 ${tu:+"--user=$tu"} bash -c 'echo $XDG_SESSION_TYPE')" "unspecified" + + if [[ -n "$tu" ]]; then + # Validate that $SHELL is set to login shell of target user when cmdline is supplied (not invoking shell) + TARGET_LOGIN_SHELL="$(getent passwd "$tu" | cut -d: -f7)" + assert_eq "$(run0 --user="$tu" printenv SHELL)" "$TARGET_LOGIN_SHELL" + fi done # Let's chain a couple of run0 calls together, for fun readarray -t cmdline < <(printf "%.0srun0\n" {0..31})