]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
run0: Add --empower
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Thu, 30 Oct 2025 11:28:19 +0000 (12:28 +0100)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Thu, 30 Oct 2025 14:28:36 +0000 (15:28 +0100)
--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.

man/run0.xml
shell-completion/bash/run0
shell-completion/zsh/_run0
src/run/run.c
test/units/TEST-74-AUX-UTILS.run.sh

index 5ad91240d589a430b5f8ee50ea7ecd4d11a4fc52..ee2074b4ab4585c0f66dc154e326285a67a90255 100644 (file)
         <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>
         </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>
 
index 0850b6ef9628e350e30b5f2c091db05683e9bd5c..1bba3b8145ac74f9a50409be2a8b8e8777d8eda6 100644 (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
index 5998f4101637ad9ebf35a69f05b7f2a57587c70e..dc95486bef3193042a52cf60e101d517341d619c 100644 (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}'
index 1c01d4485b15f856c09897b06e7fd757d90f34a0..b5030b9cb72b2c21e4fa1474ec195422aded35b2 100644 (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)
index c7ca8ce61533a3bee41dd36715fb369d6991d633..f0799f6ca659bd6d9af4323821a1c0b04717134d 100755 (executable)
@@ -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)