From: Daan De Meyer Date: Thu, 30 Oct 2025 11:28:19 +0000 (+0100) Subject: run0: Add --empower X-Git-Tag: v259-rc1~219 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5cabeed80b30972babc7a082ca794c6b197e72ab;p=thirdparty%2Fsystemd.git run0: Add --empower --empower gives full privileges to a non-root user. Currently this includes all capabilities but we leave the option open to add more privileges via this option in the future. Why is this useful? When running privileged development or debugging commands from your home directory (think bpftrace, strace and such), you want any files written by these tools to be owned by your current user, and not by the root user. run0 --empower will allow you to run all privileged operations (assuming the tools check for capabilities and not UIDs), while any files written by the tools will still be owned by the current user. --- diff --git a/man/run0.xml b/man/run0.xml index 5ad91240d58..ee2074b4ab4 100644 --- a/man/run0.xml +++ b/man/run0.xml @@ -139,8 +139,8 @@ Switches to the specified user/group. If not specified defaults to - root, unless is used (see below), in which case this - defaults to the invoking user. + root, unless or are used (see + below), in which case this defaults to the invoking user. @@ -290,6 +290,17 @@ + + + + If specified, run0 will elevate the privileges of the selected user (using + ) or the current user if no user is explicitly selected. Currently this means + we give the user all available capabilities, but other privileges may be granted in the future as + well when using this option. + + + + diff --git a/shell-completion/bash/run0 b/shell-completion/bash/run0 index 0850b6ef962..1bba3b8145a 100644 --- a/shell-completion/bash/run0 +++ b/shell-completion/bash/run0 @@ -37,7 +37,7 @@ _run0() { --machine --unit --property --description --slice -u --user -g --group --nice -D --chdir --setenv --background ) - local OPTS="${opts_with_values[*]} -h --help -V --version --no-ask-password --slice-inherit" + local OPTS="${opts_with_values[*]} -h --help -V --version --no-ask-password --slice-inherit --empower" local i for (( i=1; i <= COMP_CWORD; i++ )); do diff --git a/shell-completion/zsh/_run0 b/shell-completion/zsh/_run0 index 5998f410163..dc95486bef3 100644 --- a/shell-completion/zsh/_run0 +++ b/shell-completion/zsh/_run0 @@ -52,6 +52,7 @@ local -a args=( '--machine=[Execute the operation on a local container]:machine:_sd_machines' {-h,--help}'[Show the help text and exit]' '--version[Print a short version string and exit]' + '--empower[Give privileges to selected or current user]' ) _arguments -S $args '*:: :{_normal -p $service}' diff --git a/src/run/run.c b/src/run/run.c index 1c01d4485b1..b5030b9cb72 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -25,6 +25,7 @@ #include "bus-util.h" #include "bus-wait-for-jobs.h" #include "calendarspec.h" +#include "capability-util.h" #include "capsule-util.h" #include "chase.h" #include "env-util.h" @@ -117,6 +118,7 @@ static char *arg_shell_prompt_prefix = NULL; static int arg_lightweight = -1; static char *arg_area = NULL; static bool arg_via_shell = false; +static bool arg_empower = false; STATIC_DESTRUCTOR_REGISTER(arg_description, freep); STATIC_DESTRUCTOR_REGISTER(arg_environment, strv_freep); @@ -244,6 +246,7 @@ static int help_sudo_mode(void) { " --lightweight=BOOLEAN Control whether to register a session with service manager\n" " or without\n" " --area=AREA Home area to log into\n" + " --empower Give privileges to selected or current user\n" "\nSee the %s for details.\n", program_invocation_short_name, ansi_highlight(), @@ -253,11 +256,15 @@ static int help_sudo_mode(void) { return 0; } +static bool become_root(void) { + return !arg_exec_user || STR_IN_SET(arg_exec_user, "root", "0"); +} + static bool privileged_execution(void) { if (arg_runtime_scope != RUNTIME_SCOPE_SYSTEM) return false; - return !arg_exec_user || STR_IN_SET(arg_exec_user, "root", "0"); + return become_root() || arg_empower; } static int add_timer_property(const char *name, const char *val) { @@ -859,6 +866,7 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) { ARG_LIGHTWEIGHT, ARG_AREA, ARG_VIA_SHELL, + ARG_EMPOWER, }; /* If invoked as "run0" binary, let's expose a more sudo-like interface. We add various extensions @@ -888,6 +896,7 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) { { "shell-prompt-prefix", required_argument, NULL, ARG_SHELL_PROMPT_PREFIX }, { "lightweight", required_argument, NULL, ARG_LIGHTWEIGHT }, { "area", required_argument, NULL, ARG_AREA }, + { "empower", no_argument, NULL, ARG_EMPOWER }, {}, }; @@ -1027,6 +1036,10 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) { arg_via_shell = true; break; + case ARG_EMPOWER: + arg_empower = true; + break; + case '?': return -EINVAL; @@ -1034,9 +1047,13 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) { assert_not_reached(); } - if (!arg_exec_user && arg_area) { + if (!arg_exec_user && (arg_area || arg_empower)) { /* If the user specifies --area= but not --user= then consider this an area switch request, - * and default to logging into our own account */ + * and default to logging into our own account. + * + * If the user specifies --empower but not --user= then consider this a request to empower + * the current user. */ + arg_exec_user = getusername_malloc(); if (!arg_exec_user) return log_oom(); @@ -1211,8 +1228,8 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) { if (arg_lightweight >= 0) { const char *class = - arg_lightweight ? (arg_stdio == ARG_STDIO_PTY ? (privileged_execution() ? "user-early-light" : "user-light") : "background-light") : - (arg_stdio == ARG_STDIO_PTY ? (privileged_execution() ? "user-early" : "user") : "background"); + arg_lightweight ? (arg_stdio == ARG_STDIO_PTY ? (become_root() ? "user-early-light" : "user-light") : "background-light") : + (arg_stdio == ARG_STDIO_PTY ? (become_root() ? "user-early" : "user") : "background"); log_debug("Setting XDG_SESSION_CLASS to '%s'.", class); @@ -1371,6 +1388,12 @@ static int transient_service_set_properties(sd_bus_message *m, const char *pty_p return bus_log_create_error(r); } + if (arg_empower) { + r = sd_bus_message_append(m, "(sv)", "AmbientCapabilities", "t", CAP_MASK_ALL); + if (r < 0) + return bus_log_create_error(r); + } + if (arg_nice_set) { r = sd_bus_message_append(m, "(sv)", "Nice", "i", arg_nice); if (r < 0) diff --git a/test/units/TEST-74-AUX-UTILS.run.sh b/test/units/TEST-74-AUX-UTILS.run.sh index c7ca8ce6153..f0799f6ca65 100755 --- a/test/units/TEST-74-AUX-UTILS.run.sh +++ b/test/units/TEST-74-AUX-UTILS.run.sh @@ -297,6 +297,14 @@ if [[ -e /usr/lib/pam.d/systemd-run0 ]] || [[ -e /etc/pam.d/systemd-run0 ]]; the # Validate when we invoke run0 without a tty, that depending on --pty it either allocates a tty or not assert_neq "$(run0 --pty tty < /dev/null)" "not a tty" assert_eq "$(run0 --pipe tty < /dev/null)" "not a tty" + + # Validate that --empower gives all capabilities to a non-root user. + caps="$(run0 -u testuser --empower systemd-analyze capability --mask "$(grep CapEff /proc/self/status | cut -d':' -f2)" --json=pretty | jq -r length)" + assert_neq "$caps" "0" + + run0 -u testuser --empower touch /run/empower + assert_eq "$(stat -c "%U" /run/empower)" testuser + rm /run/empower fi # Tests whether intermediate disconnects corrupt us (modified testcase from https://github.com/systemd/systemd/issues/27204)