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.
This commit is contained in:
Daan De Meyer
2025-10-30 12:28:19 +01:00
parent 19bf12bff3
commit 5cabeed80b
5 changed files with 51 additions and 8 deletions

View File

@@ -139,8 +139,8 @@
<term><option>-g</option></term>
<listitem><para>Switches to the specified user/group. If not specified defaults to
<literal>root</literal>, unless <option>--area=</option> is used (see below), in which case this
defaults to the invoking user.</para>
<literal>root</literal>, unless <option>--area=</option> or <option>--empower</option> are used (see
below), in which case this defaults to the invoking user.</para>
<xi:include href="version-info.xml" xpointer="v256"/>
</listitem>
@@ -290,6 +290,17 @@
</listitem>
</varlistentry>
<varlistentry>
<term><option>--empower</option></term>
<listitem><para>If specified, run0 will elevate the privileges of the selected user (using
<option>--user=</option>) 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.</para>
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--machine=</option></term>

View File

@@ -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

View File

@@ -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}'

View File

@@ -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)

View File

@@ -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)