]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
run0: implement -k/-K to revoke temporary auth
authorRonan Pigott <ronan@rjp.ie>
Thu, 4 Jun 2026 00:36:25 +0000 (17:36 -0700)
committerLuca Boccassi <luca.boccassi@gmail.com>
Sat, 20 Jun 2026 08:55:02 +0000 (09:55 +0100)
This is meant to mirror sudo's -k/--reset-timestamp and
-K/--remove-timestamp options, which revoke the temporary authorization
provided by the timestamp files in /var/run/sudo/ts.

To achieve the same effect in run0, we ask polkit to revoke our
temporary authorization. If used with a command, run0 will revoke the
temporary auth and then immediately authorize the user again, just like
sudo -k. All the bus calls are completed synchronously, as they need to
complete before authorizing the user anyway.

Like sudo, the effect of -k/--reset-timestamp is to revoke only the
tmpauthz that polkit would have used to authorize the command, if
available. The -K/--remove-timestamp option will revoke all temporary
authorizations across all ttys.

man/run0.xml
shell-completion/bash/run0
shell-completion/zsh/_run0
src/run/run.c

index 186383dbbe172eb3bc0949aa205f895eae2c5595..1bc38470c500d20a6ab62d73b86e75e568753785 100644 (file)
         </listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>-k</option></term>
+        <term><option>--reset-timestamp</option></term>
+
+        <listitem><para>Revokes temporary polkit authorization for this terminal, if present.</para>
+
+        <xi:include href="version-info.xml" xpointer="v262"/>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>-K</option></term>
+        <term><option>--remove-timestamp</option></term>
+
+        <listitem><para>Revokes all temporary polkit authorizations for this user session.</para>
+
+        <xi:include href="version-info.xml" xpointer="v262"/>
+        </listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><option>--via-shell</option></term>
 
index 1bba3b8145ac74f9a50409be2a8b8e8777d8eda6..ab174492286eb7186d2393367b1ac5e3200cdea9 100644 (file)
@@ -38,6 +38,7 @@ _run0() {
         --setenv --background
     )
     local OPTS="${opts_with_values[*]} -h --help -V --version --no-ask-password --slice-inherit --empower"
+    OPTS="$OPTS -k --reset-timestamp -K --remove-timestamp"
 
     local i
     for (( i=1; i <= COMP_CWORD; i++ )); do
index f76be2e5ff0dccf63c7a33ae4b709a050d828bd2..f8c1e8a08ce7a91b23f50bf2912b3e7fa344e414 100644 (file)
@@ -47,6 +47,8 @@ local -a args=(
     '(--group -g)'{--group=,-g+}'[Switch to the specified group]:group:_groups'
     '--nice=[Run with specified nice level]:nice value'
     '(--chdir -D -i --same-root-dir)'{--chdir=,-D+}'[Run within the specified working directory]:directory:_files -/'
+    '(-k --reset-timestamp)'{-k,--reset-timestamp}'[Revoke temporary authorization for this terminal]'
+    '(-K --remove-timestamp)'{-K,--remove-timestamp}'[Revoke temporary authorizations for this user session]'
     '(-i)'--via-shell"[Invoke command via target user's login shell]"
     '(--via-shell --chdir -D --same-root-dir)'-i"[Shortcut for --via-shell --chdir='~']"
     '*--setenv=[Set the specified environment variable in the session]:environment variable:_parameters -g "*export*" -S = -q'
index 9c85874db1b4e98da23d77048f53abaefddd8417..4ef3fc940c54ae9257637653440d751207ab99a6 100644 (file)
@@ -4,10 +4,12 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <sys/mount.h>
+#include <sys/pidfd.h>
 #include <sys/resource.h>
 #include <sys/stat.h>
 #include <unistd.h>
 
+#include "sd-bus-protocol.h"
 #include "sd-bus.h"
 #include "sd-daemon.h"
 #include "sd-event.h"
@@ -21,6 +23,7 @@
 #include "bus-locator.h"
 #include "bus-map-properties.h"
 #include "bus-message-util.h"
+#include "bus-polkit.h"
 #include "bus-unit-util.h"
 #include "bus-util.h"
 #include "bus-wait-for-jobs.h"
@@ -72,6 +75,9 @@ static bool arg_scope = false;
 static bool arg_remain_after_exit = false;
 static bool arg_no_block = false;
 static bool arg_wait = false;
+static bool arg_default_command = false;
+static bool arg_remove_timestamp = false;
+static bool arg_reset_timestamp = false;
 static const char *arg_unit = NULL;
 static char *arg_description = NULL;
 static char *arg_slice = NULL;
@@ -825,6 +831,14 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) {
                         arg_slice_inherit = true;
                         break;
 
+                OPTION('k', "reset-timestamp", NULL, "Revoke temporary authorization"):
+                        arg_reset_timestamp = true;
+                        break;
+
+                OPTION('K', "remove-timestamp", NULL, "Revoke all temporary authorizations for this user session"):
+                        arg_remove_timestamp = true;
+                        break;
+
                 OPTION('u', "user", "USER", "Run as system user"):
                         r = free_and_strdup_warn(&arg_exec_user, opts.arg);
                         if (r < 0)
@@ -976,6 +990,7 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) {
         } else if (!arg_via_shell) {
                 const char *e;
 
+                arg_default_command = true;
                 e = strv_env_get(arg_environment, "SHELL");
                 if (e) {
                         arg_exec_path = strdup(e);
@@ -2525,7 +2540,7 @@ static int start_transient_scope(sd_bus *bus) {
                 if (r < 0)
                         return bus_log_create_error(r);
 
-                r = sd_bus_call(bus, m, 0, &error, &reply);
+                r = sd_bus_call(bus, m, /* usec = */ 0, &error, &reply);
                 if (r < 0) {
                         if (sd_bus_error_has_names(&error, SD_BUS_ERROR_UNKNOWN_PROPERTY, SD_BUS_ERROR_PROPERTY_READ_ONLY) && allow_pidfd) {
                                 log_debug("Retrying with classic PIDs.");
@@ -2855,6 +2870,282 @@ static bool shall_make_executable_absolute(void) {
         return true;
 }
 
+static int polkit_check_authorization(sd_bus *bus, PolkitFlags flags, char **ret_tmpauthz_id) {
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        pid_t pid;
+        _cleanup_close_ int pidfd = -EBADF;
+        _cleanup_free_ char *tmpauthz_id = NULL;
+        int is_authorized, is_challenge;
+        int r;
+
+        assert(bus);
+
+        r = sd_bus_message_new_method_call(bus, &m,
+                        "org.freedesktop.PolicyKit1",
+                        "/org/freedesktop/PolicyKit1/Authority",
+                        "org.freedesktop.PolicyKit1.Authority",
+                        "CheckAuthorization");
+        if (r < 0)
+                return bus_log_create_error(r);
+
+        pid = getpid_cached();
+
+        /* Polkit requires pidfd to honor temporary authorizations */
+        pidfd = pidfd_open(pid, 0);
+        if (pidfd < 0)
+                return log_debug_errno(errno, "pidfd_open failed: %m");
+
+        r = sd_bus_message_append(m, "(sa{sv})s", "unix-process", 4, "pid", "u", (uint32_t) pid,
+                        "start-time", "t", UINT64_C(0), "uid", "i", (uint32_t) geteuid(), "pidfd", "h", pidfd,
+                        "org.freedesktop.systemd1.manage-units");
+        if (r < 0)
+                return bus_log_create_error(r);
+
+        r = sd_bus_message_append(m, "a{ss}us", /* details = */ 0, (uint32_t) flags, /* cancel_id = */ NULL);
+        if (r < 0)
+                return bus_log_create_error(r);
+
+        r = sd_bus_call(bus, m, /* usec = */ 0, &error, &reply);
+        if (r < 0)
+                return log_error_errno(r, "Failed to check authorization: %s", bus_error_message(&error, r));
+
+        r = sd_bus_message_enter_container(reply, 'r', "bba{ss}");
+        if (r < 0)
+                return bus_log_parse_error(r);
+
+        r = sd_bus_message_read(reply, "bb", &is_authorized, &is_challenge);
+        if (r < 0)
+                return bus_log_parse_error(r);
+
+        r = sd_bus_message_enter_container(reply, 'a', "{ss}");
+        if (r < 0)
+                return bus_log_parse_error(r);
+
+        for (;;) {
+                const char *key, *value;
+                r = sd_bus_message_enter_container(reply, 'e', "ss");
+                if (r < 0)
+                        return bus_log_parse_error(r);
+                if (r == 0)
+                        break;
+
+                r = sd_bus_message_read(reply, "ss", &key, &value);
+                if (r < 0)
+                        return bus_log_parse_error(r);
+
+                if (streq(key, "polkit.temporary_authorization_id")) {
+                        r = free_and_strdup(&tmpauthz_id, value);
+                        if (r < 0)
+                                return log_oom();
+                }
+
+                r = sd_bus_message_exit_container(reply);
+                if (r < 0)
+                        return bus_log_parse_error(r);
+        }
+
+        r = sd_bus_message_exit_container(reply); /* a{ss} */
+        if (r < 0)
+                return bus_log_parse_error(r);
+
+        r = sd_bus_message_exit_container(reply); /* (bba{ss}) */
+        if (r < 0)
+                return bus_log_parse_error(r);
+
+        if (ret_tmpauthz_id && is_authorized)
+                *ret_tmpauthz_id = TAKE_PTR(tmpauthz_id);
+
+        return is_authorized;
+}
+
+static int revoke_temporary_authorization_by_id(sd_bus *bus, const char *id) {
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        int r;
+
+        assert(bus);
+        assert(id);
+
+        r = sd_bus_message_new_method_call(bus, &m,
+                        "org.freedesktop.PolicyKit1",
+                        "/org/freedesktop/PolicyKit1/Authority",
+                        "org.freedesktop.PolicyKit1.Authority",
+                        "RevokeTemporaryAuthorizationById");
+        if (r < 0)
+                return bus_log_create_error(r);
+
+        r = sd_bus_message_append(m, "s", id);
+        if (r < 0)
+                return bus_log_create_error(r);
+
+        log_debug("Revoking temporary authorization %s", id);
+        r = sd_bus_call(bus, m, /* usec = */ 0, &error, /* ret_reply= */ NULL);
+        if (r < 0)
+                return log_error_errno(r, "Failed to revoke temporary authorization %s: %s",
+                                id, bus_error_message(&error, r));
+
+        return 0;
+}
+
+static int check_polkit_subject_for_uid(sd_bus_message *m) {
+        const char *kind = NULL;
+        uid_t uid = UID_INVALID;
+        int r;
+
+        r = sd_bus_message_enter_container(m, 'r', "sa{sv}");
+        if (r < 0)
+                return bus_log_parse_error(r);
+
+        r = sd_bus_message_read(m, "s", &kind);
+        if (r < 0)
+                return bus_log_parse_error(r);
+
+        r = sd_bus_message_enter_container(m, 'a', "{sv}");
+        if (r < 0)
+                return bus_log_parse_error(r);
+
+        for (;;) {
+                const char *key, *contents;
+                char type;
+
+                r = sd_bus_message_enter_container(m, 'e', "sv");
+                if (r < 0)
+                        return bus_log_parse_error(r);
+                if (r == 0)
+                        break;
+
+                r = sd_bus_message_read(m, "s", &key);
+                if (r < 0)
+                        return bus_log_parse_error(r);
+
+                r = sd_bus_message_peek_type(m, &type, &contents);
+                if (r < 0)
+                        return bus_log_parse_error(r);
+
+                if (streq(key, "pid")) {
+                        if (*contents != SD_BUS_TYPE_UINT32)
+                                return bus_log_parse_error(SYNTHETIC_ERRNO(EINVAL));
+                        r = sd_bus_message_skip(m, "v");
+                        if (r < 0)
+                                return bus_log_parse_error(r);
+                } else if (streq(key, "start-time")) {
+                        if (*contents != SD_BUS_TYPE_UINT64)
+                                return bus_log_parse_error(SYNTHETIC_ERRNO(EINVAL));
+                        r = sd_bus_message_skip(m, "v");
+                        if (r < 0)
+                                return bus_log_parse_error(r);
+                } else if (streq(key, "uid")) {
+                        if (*contents != SD_BUS_TYPE_INT32)
+                                return bus_log_parse_error(SYNTHETIC_ERRNO(EINVAL));
+                        r = sd_bus_message_read(m, "v", "i", &uid);
+                        if (r < 0)
+                                return bus_log_parse_error(r);
+                } else if (streq(key, "pidfd")) {
+                        if (*contents != SD_BUS_TYPE_UNIX_FD)
+                                return bus_log_parse_error(SYNTHETIC_ERRNO(EINVAL));
+                        r = sd_bus_message_skip(m, "v");
+                        if (r < 0)
+                                return bus_log_parse_error(r);
+                } else {
+                        r = sd_bus_message_skip(m, "v");
+                        if (r < 0)
+                                return bus_log_parse_error(r);
+                }
+
+                r = sd_bus_message_exit_container(m);
+                if (r < 0)
+                        return bus_log_parse_error(r);
+        }
+
+        r = sd_bus_message_exit_container(m); /* a(sa{sv}) */
+        if (r < 0)
+                return bus_log_parse_error(r);
+
+        r = sd_bus_message_exit_container(m); /* (a(sa{sv})) */
+        if (r < 0)
+                return bus_log_parse_error(r);
+
+        return uid_is_valid(uid) && uid == geteuid();
+}
+
+static int revoke_temporary_authorizations(sd_bus *bus) {
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        const char *session_id = NULL;
+        int r;
+
+        assert(bus);
+
+        session_id = getenv("XDG_SESSION_ID");
+        if (!session_id)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "XDG_SESSION_ID is not set");
+
+        r = sd_bus_message_new_method_call(bus, &m,
+                        "org.freedesktop.PolicyKit1",
+                        "/org/freedesktop/PolicyKit1/Authority",
+                        "org.freedesktop.PolicyKit1.Authority",
+                        "EnumerateTemporaryAuthorizations");
+        if (r < 0)
+                return bus_log_create_error(r);
+
+        r = sd_bus_message_append(m, "(sa{sv})", "unix-session", 1, "session-id", "s", session_id);
+        if (r < 0)
+                return bus_log_create_error(r);
+
+        r = sd_bus_call(bus, m, /* usec = */ 0, &error, &reply);
+        if (r < 0)
+                return log_error_errno(r, "Failed to enumerate temporary authorizations: %s",
+                                bus_error_message(&error, r));
+
+        r = sd_bus_message_enter_container(reply, 'a', "(ss(sa{sv})tt)");
+        if (r < 0)
+                return bus_log_parse_error(r);
+
+        for (;;) {
+                const char *id = NULL, *action_id = NULL;
+
+                r = sd_bus_message_enter_container(reply, 'r', "ss(sa{sv})tt");
+                if (r < 0)
+                        return bus_log_parse_error(r);
+                if (r == 0)
+                        break;
+
+                r = sd_bus_message_read(reply, "ss", &id, &action_id);
+                if (r < 0)
+                        return bus_log_parse_error(r);
+
+                if (streq(action_id, "org.freedesktop.systemd1.manage-units")) {
+                        r = check_polkit_subject_for_uid(reply);
+                        if (r < 0)
+                                return r;
+                        if (r > 0) {
+                                r = revoke_temporary_authorization_by_id(bus, id);
+                                if (r < 0)
+                                        return r;
+                        }
+                } else {
+                        r = sd_bus_message_skip(reply, "(sa{sv})");
+                        if (r < 0)
+                                return bus_log_parse_error(r);
+                }
+
+                r = sd_bus_message_skip(reply, "tt");
+                if (r < 0)
+                        return bus_log_parse_error(r);
+
+                r = sd_bus_message_exit_container(reply);
+                if (r < 0)
+                        return bus_log_parse_error(r);
+        }
+
+        r = sd_bus_message_exit_container(reply);
+        if (r < 0)
+                return bus_log_parse_error(r);
+
+        return 0;
+}
+
 static int run(int argc, char* argv[]) {
         _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
         int r;
@@ -2919,6 +3210,31 @@ static int run(int argc, char* argv[]) {
         if (r < 0)
                 return r;
 
+        if (arg_remove_timestamp) {
+                r = revoke_temporary_authorizations(bus);
+                if (r < 0)
+                        return r;
+                if (arg_validate)
+                        return polkit_validate(bus);
+                if (arg_default_command)
+                        return 0;
+        } else if (arg_reset_timestamp) {
+                _cleanup_free_ char *tmpauthz_id = NULL;
+                const PolkitFlags flags = POLKIT_ALWAYS_QUERY;
+                r = polkit_check_authorization(bus, (uint32_t) (flags & _POLKIT_MASK_PUBLIC), &tmpauthz_id);
+                if (r < 0)
+                        return r;
+                if (r > 0 && tmpauthz_id) {
+                        r = revoke_temporary_authorization_by_id(bus, tmpauthz_id);
+                        if (r < 0)
+                                return r;
+                }
+                if (arg_validate)
+                        return polkit_validate(bus);
+                if (arg_default_command)
+                        return 0;
+        }
+
         if (arg_scope)
                 return start_transient_scope(bus);
         if (arg_path_property)