From: Ronan Pigott Date: Thu, 4 Jun 2026 00:36:25 +0000 (-0700) Subject: run0: implement -k/-K to revoke temporary auth X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=78b53d0132fd2ff758a08e5f82310c94f1db5015;p=thirdparty%2Fsystemd.git run0: implement -k/-K to revoke temporary auth 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. --- diff --git a/man/run0.xml b/man/run0.xml index 186383dbbe1..1bc38470c50 100644 --- a/man/run0.xml +++ b/man/run0.xml @@ -167,6 +167,26 @@ + + + + + Revokes temporary polkit authorization for this terminal, if present. + + + + + + + + + + Revokes all temporary polkit authorizations for this user session. + + + + + diff --git a/shell-completion/bash/run0 b/shell-completion/bash/run0 index 1bba3b8145a..ab174492286 100644 --- a/shell-completion/bash/run0 +++ b/shell-completion/bash/run0 @@ -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 diff --git a/shell-completion/zsh/_run0 b/shell-completion/zsh/_run0 index f76be2e5ff0..f8c1e8a08ce 100644 --- a/shell-completion/zsh/_run0 +++ b/shell-completion/zsh/_run0 @@ -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' diff --git a/src/run/run.c b/src/run/run.c index 9c85874db1b..4ef3fc940c5 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -4,10 +4,12 @@ #include #include #include +#include #include #include #include +#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)