From 621514762443bc536cd489a221a8669fdfd54241 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Fri, 20 Mar 2026 16:25:42 +0100 Subject: [PATCH 1/3] core: add `io.systemd.Manager.{PowerOff,Reboot,SoftReboot,Halt,Kexec}` This adds the low-level io.systemd.Manager shutdown support. This is (much) simpler than the logind one. It mimics dbus but uses a shared helper for the simple cases. Note that this is more restrictive than the dbus version. The dbus version uses SD_BUS_VTABLE_CAPABILITY(CAP_SYS_BOOT) but the varlink version uses varlink_check_privileged_peer(link). This is mostly because I'm not sure how to do the equivalent in a race-free way. Thanks to Daan for suggesting this. --- src/core/dbus-manager.c | 1 + src/core/varlink-manager.c | 82 +++++++++++++++++++++++++ src/core/varlink-manager.h | 5 ++ src/core/varlink.c | 5 ++ src/shared/varlink-io.systemd.Manager.c | 19 ++++++ 5 files changed, 112 insertions(+) diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index 5e02d18907..fec53341ca 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -1690,6 +1690,7 @@ static int method_soft_reboot(sd_bus_message *message, void *userdata, sd_bus_er return sd_bus_error_set(reterr_error, SD_BUS_ERROR_NOT_SUPPORTED, "Soft reboot is only supported by system manager."); + /* Keep the checks in sync with varlink-manager.c:vl_method_soft_reboot_manager() */ r = mac_selinux_access_check(message, "reboot", reterr_error); if (r < 0) return r; diff --git a/src/core/varlink-manager.c b/src/core/varlink-manager.c index d00f7e5a24..53db0a5a2e 100644 --- a/src/core/varlink-manager.c +++ b/src/core/varlink-manager.c @@ -16,6 +16,7 @@ #include "glyph-util.h" #include "json-util.h" #include "manager.h" +#include "path-util.h" #include "pidref.h" #include "selinux-access.h" #include "set.h" @@ -398,3 +399,84 @@ int vl_method_enqueue_marked_jobs_manager(sd_varlink *link, sd_json_variant *par return ret; } + +static int manager_do_set_objective(sd_varlink *link, sd_json_variant *parameters, ManagerObjective objective, const char *selinux_permission, bool can_do_root) { + Manager *m = ASSERT_PTR(sd_varlink_get_userdata(link)); + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + _cleanup_free_ char *rt = NULL; + const char *root = NULL; + int r; + + assert(link); + assert(parameters); + + if (!MANAGER_IS_SYSTEM(m)) + return sd_varlink_error(link, SD_VARLINK_ERROR_METHOD_NOT_IMPLEMENTED, NULL); + + if (can_do_root) { + static const sd_json_dispatch_field dispatch_table[] = { + { "root", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, 0, 0 }, + {} + }; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &root); + } else + r = sd_varlink_dispatch(link, parameters, /* dispatch_table= */ NULL, /* userdata= */ NULL); + if (r != 0) + return r; + + r = mac_selinux_access_check_varlink(link, selinux_permission); + if (r < 0) + return r; + + /* dbus uses SD_BUS_VTABLE_CAPABILITY(CAP_SYS_BOOT) in its checking. We cannot do the same + * because reading capabilities from /proc is racy (TOCTOU). So we use the stricter check + * TODO: figure out a way to check for CAP_SYS_BOOT */ + r = varlink_check_privileged_peer(link); + if (r < 0) + return r; + + if (!isempty(root)) { + if (!path_is_valid(root)) + return sd_varlink_error_invalid_parameter_name(link, "root"); + if (!path_is_absolute(root)) + return sd_varlink_error_invalid_parameter_name(link, "root"); + + r = path_simplify_alloc(root, &rt); + if (r < 0) + return r; + } + + /* We need at least the pidref, otherwise there's nothing to log about. */ + r = varlink_get_peer_pidref(link, &pidref); + if (r < 0) + log_debug_errno(r, "Failed to get peer pidref, ignoring: %m"); + else + manager_log_caller(m, &pidref, manager_objective_to_string(objective)); + + if (can_do_root) + free_and_replace(m->switch_root, rt); + m->objective = objective; + + return sd_varlink_reply(link, NULL); +} + +int vl_method_poweroff_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return manager_do_set_objective(link, parameters, MANAGER_POWEROFF, "halt", /* can_do_root= */ false); +} + +int vl_method_reboot_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return manager_do_set_objective(link, parameters, MANAGER_REBOOT, "reboot", /* can_do_root= */ false); +} + +int vl_method_halt_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return manager_do_set_objective(link, parameters, MANAGER_HALT, "halt", /* can_do_root= */ false); +} + +int vl_method_kexec_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return manager_do_set_objective(link, parameters, MANAGER_KEXEC, "reboot", /* can_do_root= */ false); +} + +int vl_method_soft_reboot_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return manager_do_set_objective(link, parameters, MANAGER_SOFT_REBOOT, "reboot", /* can_do_root= */ true); +} diff --git a/src/core/varlink-manager.h b/src/core/varlink-manager.h index e5111eb58d..0e477e761b 100644 --- a/src/core/varlink-manager.h +++ b/src/core/varlink-manager.h @@ -9,3 +9,8 @@ int vl_method_describe_manager(sd_varlink *link, sd_json_variant *parameters, sd int vl_method_reexecute_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); int vl_method_reload_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); int vl_method_enqueue_marked_jobs_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_poweroff_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_reboot_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_halt_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_kexec_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_soft_reboot_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); diff --git a/src/core/varlink.c b/src/core/varlink.c index ec4f8abad9..77b2d3bd82 100644 --- a/src/core/varlink.c +++ b/src/core/varlink.c @@ -390,6 +390,11 @@ int manager_setup_varlink_server(Manager *m) { "io.systemd.Manager.Reexecute", vl_method_reexecute_manager, "io.systemd.Manager.Reload", vl_method_reload_manager, "io.systemd.Manager.EnqueueMarkedJobs", vl_method_enqueue_marked_jobs_manager, + "io.systemd.Manager.PowerOff", vl_method_poweroff_manager, + "io.systemd.Manager.Reboot", vl_method_reboot_manager, + "io.systemd.Manager.Halt", vl_method_halt_manager, + "io.systemd.Manager.KExec", vl_method_kexec_manager, + "io.systemd.Manager.SoftReboot", vl_method_soft_reboot_manager, "io.systemd.Unit.List", vl_method_list_units, "io.systemd.Unit.SetProperties", vl_method_set_unit_properties, "io.systemd.service.Ping", varlink_method_ping, diff --git a/src/shared/varlink-io.systemd.Manager.c b/src/shared/varlink-io.systemd.Manager.c index cb304f2295..f33cab34b3 100644 --- a/src/shared/varlink-io.systemd.Manager.c +++ b/src/shared/varlink-io.systemd.Manager.c @@ -193,6 +193,15 @@ static SD_VARLINK_DEFINE_METHOD_FULL( SD_VARLINK_FIELD_COMMENT("Job enqueue error message (on failure)"), SD_VARLINK_DEFINE_OUTPUT(errorMessage, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); +static SD_VARLINK_DEFINE_METHOD(PowerOff); +static SD_VARLINK_DEFINE_METHOD(Reboot); +static SD_VARLINK_DEFINE_METHOD(Halt); +static SD_VARLINK_DEFINE_METHOD(KExec); +static SD_VARLINK_DEFINE_METHOD( + SoftReboot, + SD_VARLINK_FIELD_COMMENT("New root directory for the soft reboot"), + SD_VARLINK_DEFINE_INPUT(root, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); + static SD_VARLINK_DEFINE_ERROR(RateLimitReached); SD_VARLINK_DEFINE_INTERFACE( @@ -205,6 +214,16 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_method_Reload, SD_VARLINK_SYMBOL_COMMENT("Enqueue all marked jobs"), &vl_method_EnqueueMarkedJobs, + SD_VARLINK_SYMBOL_COMMENT("Power off the system"), + &vl_method_PowerOff, + SD_VARLINK_SYMBOL_COMMENT("Reboot the system"), + &vl_method_Reboot, + SD_VARLINK_SYMBOL_COMMENT("Halt the system"), + &vl_method_Halt, + SD_VARLINK_SYMBOL_COMMENT("Reboot the system via kexec"), + &vl_method_KExec, + SD_VARLINK_SYMBOL_COMMENT("Soft-reboot the userspace"), + &vl_method_SoftReboot, &vl_error_RateLimitReached, &vl_type_ManagerContext, &vl_type_ManagerRuntime, From 955115546c2bd23cb5ae19ca04ebc4b4bfa235ec Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Sat, 21 Mar 2026 22:12:02 +0100 Subject: [PATCH 2/3] core: extract varlink_log_caller() helper Extract a common helper varlink_log_caller() and use in the varlink code when logging the caller of a method. It also logs the method now that was tried (but failed) to be logged with log_notice just like manager_log_caller() would do. I was looking into modifying `manager_log_caller` instead and accept a NULL pidref but could not log more than the method without pidref and would make the manager_log_caller slightly less nice. Thanks to keszybz for suggesting this. --- src/core/varlink-manager.c | 41 +++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/src/core/varlink-manager.c b/src/core/varlink-manager.c index 53db0a5a2e..a12f14e121 100644 --- a/src/core/varlink-manager.c +++ b/src/core/varlink-manager.c @@ -212,8 +212,24 @@ int vl_method_describe_manager(sd_varlink *link, sd_json_variant *parameters, sd return sd_varlink_reply(link, v); } -int vl_method_reload_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { +static void varlink_log_caller(sd_varlink *link, Manager *manager, const char *method) { _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + int r; + + assert(link); + assert(manager); + assert(method); + + /* We need at least the pidref, otherwise there's nothing to log about. */ + r = varlink_get_peer_pidref(link, &pidref); + if (r < 0) + /* We use log_notice here just as manager_log_caller would */ + log_notice_errno(r, "Failed to get peer pidref when trying to log caller for %s, ignoring: %m", method); + else + manager_log_caller(manager, &pidref, method); +} + +int vl_method_reload_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { Manager *manager = ASSERT_PTR(userdata); int r; @@ -237,12 +253,7 @@ int vl_method_reload_manager(sd_varlink *link, sd_json_variant *parameters, sd_v if (r <= 0) return r; - /* We need at least the pidref, otherwise there's nothing to log about. */ - r = varlink_get_peer_pidref(link, &pidref); - if (r < 0) - log_debug_errno(r, "Failed to get peer pidref, ignoring: %m"); - else - manager_log_caller(manager, &pidref, "Reload"); + varlink_log_caller(link, manager, "Reload"); /* Check the rate limit after the authorization succeeds, to avoid denial-of-service issues. */ if (!ratelimit_below(&manager->reload_reexec_ratelimit)) { @@ -263,7 +274,6 @@ int vl_method_reload_manager(sd_varlink *link, sd_json_variant *parameters, sd_v } int vl_method_reexecute_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { - _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; Manager *manager = ASSERT_PTR(userdata); int r; @@ -287,12 +297,7 @@ int vl_method_reexecute_manager(sd_varlink *link, sd_json_variant *parameters, s if (r <= 0) return r; - /* We need at least the pidref, otherwise there's nothing to log about. */ - r = varlink_get_peer_pidref(link, &pidref); - if (r < 0) - log_debug_errno(r, "Failed to get peer pidref, ignoring: %m"); - else - manager_log_caller(manager, &pidref, "Reexecute"); + varlink_log_caller(link, manager, "Reexecute"); /* Check the rate limit after the authorization succeeds, to avoid denial-of-service issues. */ if (!ratelimit_below(&manager->reload_reexec_ratelimit)) { @@ -402,7 +407,6 @@ int vl_method_enqueue_marked_jobs_manager(sd_varlink *link, sd_json_variant *par static int manager_do_set_objective(sd_varlink *link, sd_json_variant *parameters, ManagerObjective objective, const char *selinux_permission, bool can_do_root) { Manager *m = ASSERT_PTR(sd_varlink_get_userdata(link)); - _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; _cleanup_free_ char *rt = NULL; const char *root = NULL; int r; @@ -447,12 +451,7 @@ static int manager_do_set_objective(sd_varlink *link, sd_json_variant *parameter return r; } - /* We need at least the pidref, otherwise there's nothing to log about. */ - r = varlink_get_peer_pidref(link, &pidref); - if (r < 0) - log_debug_errno(r, "Failed to get peer pidref, ignoring: %m"); - else - manager_log_caller(m, &pidref, manager_objective_to_string(objective)); + varlink_log_caller(link, m, manager_objective_to_string(objective)); if (can_do_root) free_and_replace(m->switch_root, rt); From 114c89cc9437bf9793295cf0beee92148844c845 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Sat, 21 Mar 2026 22:36:20 +0100 Subject: [PATCH 3/3] core: allow unset pidref in manager_log_caller This commit allows unset pidref when calling manager_log_caller(). With that we can log manager calls even if we cannot resolve the caller. Currently when we cannot resolve the caller we are just not logging anything. With this commit we at least log the call (even though we don't know what caller it was). Thanks to keszybz for the suggestion. --- src/core/manager.c | 6 +++++- src/core/varlink-manager.c | 8 +++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/core/manager.c b/src/core/manager.c index 79fa19d976..e8c5f00895 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -5220,9 +5220,13 @@ void manager_log_caller(Manager *manager, PidRef *caller, const char *method) { _cleanup_free_ char *comm = NULL; assert(manager); - assert(pidref_is_set(caller)); assert(method); + if (!pidref_is_set(caller)) { + log_notice("%s requested from unknown client PID...", method); + return; + } + (void) pidref_get_comm(caller, &comm); Unit *caller_unit = manager_get_unit_by_pidref(manager, caller); diff --git a/src/core/varlink-manager.c b/src/core/varlink-manager.c index a12f14e121..bad3720632 100644 --- a/src/core/varlink-manager.c +++ b/src/core/varlink-manager.c @@ -220,13 +220,11 @@ static void varlink_log_caller(sd_varlink *link, Manager *manager, const char *m assert(manager); assert(method); - /* We need at least the pidref, otherwise there's nothing to log about. */ r = varlink_get_peer_pidref(link, &pidref); if (r < 0) - /* We use log_notice here just as manager_log_caller would */ - log_notice_errno(r, "Failed to get peer pidref when trying to log caller for %s, ignoring: %m", method); - else - manager_log_caller(manager, &pidref, method); + log_debug_errno(r, "Failed to get peer pidref, ignoring: %m"); + + manager_log_caller(manager, &pidref, method); } int vl_method_reload_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {