From: Luca Boccassi Date: Wed, 12 Nov 2025 22:19:12 +0000 (+0000) Subject: sysext: add polkit support to varlink service X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=34f41471fc9fec02245a425a2a06a9b6d5d53a1c;p=thirdparty%2Fsystemd.git sysext: add polkit support to varlink service --- diff --git a/src/shared/varlink-io.systemd.sysext.c b/src/shared/varlink-io.systemd.sysext.c index 90eb8177d1e..13d39a89ff5 100644 --- a/src/shared/varlink-io.systemd.sysext.c +++ b/src/shared/varlink-io.systemd.sysext.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "bus-polkit.h" #include "varlink-io.systemd.sysext.h" static SD_VARLINK_DEFINE_ENUM_TYPE( @@ -19,24 +20,28 @@ static SD_VARLINK_DEFINE_METHOD( SD_VARLINK_DEFINE_INPUT_BY_TYPE(class, ImageClass, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_INPUT(force, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_INPUT(noReload, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), - SD_VARLINK_DEFINE_INPUT(noexec, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); + SD_VARLINK_DEFINE_INPUT(noexec, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + VARLINK_DEFINE_POLKIT_INPUT); static SD_VARLINK_DEFINE_METHOD( Unmerge, SD_VARLINK_DEFINE_INPUT_BY_TYPE(class, ImageClass, SD_VARLINK_NULLABLE), - SD_VARLINK_DEFINE_INPUT(noReload, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); + SD_VARLINK_DEFINE_INPUT(noReload, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + VARLINK_DEFINE_POLKIT_INPUT); static SD_VARLINK_DEFINE_METHOD( Refresh, SD_VARLINK_DEFINE_INPUT_BY_TYPE(class, ImageClass, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_INPUT(force, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_INPUT(noReload, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), - SD_VARLINK_DEFINE_INPUT(noexec, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); + SD_VARLINK_DEFINE_INPUT(noexec, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + VARLINK_DEFINE_POLKIT_INPUT); static SD_VARLINK_DEFINE_METHOD_FULL( List, SD_VARLINK_REQUIRES_MORE, SD_VARLINK_DEFINE_INPUT_BY_TYPE(class, ImageClass, SD_VARLINK_NULLABLE), + VARLINK_DEFINE_POLKIT_INPUT, SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(Class, ImageClass, 0), SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(Type, ImageType, 0), SD_VARLINK_DEFINE_OUTPUT(Name, SD_VARLINK_STRING, 0), diff --git a/src/sysext/io.systemd.sysext.policy b/src/sysext/io.systemd.sysext.policy new file mode 100644 index 00000000000..317b9d43783 --- /dev/null +++ b/src/sysext/io.systemd.sysext.policy @@ -0,0 +1,60 @@ + + + + + + + + The systemd Project + https://systemd.io + + + Allow managing (merging, unmerging, ...) of system extension images. + Authentication is required for an application to manage a system extension image. + + auth_admin + auth_admin + auth_admin_keep + + + + + Allow reading (listing, ...) of system extension images. + Authentication is required for an application to list system extension images. + + yes + yes + yes + + + + + Allow managing (merging, unmerging, ...) of configuration extension images. + Authentication is required for an application to manage a configuration extension image. + + auth_admin + auth_admin + auth_admin_keep + + + + + Allow reading (listing, ...) of configuration extension images. + Authentication is required for an application to list configuration extension images. + + yes + yes + yes + + + diff --git a/src/sysext/meson.build b/src/sysext/meson.build index daaabec777a..75d06163c0c 100644 --- a/src/sysext/meson.build +++ b/src/sysext/meson.build @@ -18,4 +18,7 @@ if conf.get('ENABLE_SYSEXT') == 1 install_symlink('systemd-confext', pointing_to : 'systemd-sysext', install_dir : bindir) + + install_data('io.systemd.sysext.policy', + install_dir : polkitpolicydir) endif diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index 2f6bee58c46..7b60dd346b5 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -14,6 +14,7 @@ #include "blkid-util.h" #include "blockdev-util.h" #include "build.h" +#include "bus-polkit.h" #include "bus-unit-util.h" #include "bus-util.h" #include "capability-util.h" @@ -117,6 +118,8 @@ static const struct { const char *full_identifier; const char *short_identifier; const char *short_identifier_plural; + const char *polkit_rw_action_id; + const char *polkit_ro_action_id; const char *blurb; const char *dot_directory_name; const char *directory_name; @@ -132,6 +135,8 @@ static const struct { .full_identifier = "systemd-sysext", .short_identifier = "sysext", .short_identifier_plural = "extensions", + .polkit_rw_action_id = "io.systemd.sysext.manage", + .polkit_ro_action_id = "io.systemd.sysext.read", .blurb = "Merge system extension images into /usr/ and /opt/.", .dot_directory_name = ".systemd-sysext", .level_env = "SYSEXT_LEVEL", @@ -146,6 +151,8 @@ static const struct { .full_identifier = "systemd-confext", .short_identifier = "confext", .short_identifier_plural = "confexts", + .polkit_rw_action_id = "io.systemd.confext.manage", + .polkit_ro_action_id = "io.systemd.confext.read", .blurb = "Merge configuration extension images into /etc/.", .dot_directory_name = ".systemd-confext", .level_env = "CONFEXT_LEVEL", @@ -623,13 +630,16 @@ static int vl_method_unmerge(sd_varlink *link, sd_json_variant *parameters, sd_v static const sd_json_dispatch_field dispatch_table[] = { { "class", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MethodUnmergeParameters, class), 0 }, { "noReload", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(MethodUnmergeParameters, no_reload), 0 }, + VARLINK_DISPATCH_POLKIT_FIELD, {} }; MethodUnmergeParameters p = { .no_reload = -1, }; + Hashmap **polkit_registry = ASSERT_PTR(userdata); _cleanup_strv_free_ char **hierarchies = NULL; ImageClass image_class = arg_image_class; + bool no_reload; int r; assert(link); @@ -638,13 +648,24 @@ static int vl_method_unmerge(sd_varlink *link, sd_json_variant *parameters, sd_v if (r != 0) return r; + no_reload = p.no_reload >= 0 ? p.no_reload : arg_no_reload; + r = parse_image_class_parameter(link, p.class, &image_class, &hierarchies); if (r < 0) return r; - r = unmerge(image_class, - hierarchies ?: arg_hierarchies, - p.no_reload >= 0 ? p.no_reload : arg_no_reload); + r = varlink_verify_polkit_async( + link, + /* bus= */ NULL, + image_class_info[image_class].polkit_rw_action_id, + (const char**) STRV_MAKE( + "verb", "unmerge", + "noReload", one_zero(no_reload)), + polkit_registry); + if (r <= 0) + return r; + + r = unmerge(image_class, hierarchies ?: arg_hierarchies, no_reload); if (r < 0) return r; @@ -2261,6 +2282,7 @@ static int parse_merge_parameters(sd_varlink *link, sd_json_variant *parameters, { "force", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(MethodMergeParameters, force), 0 }, { "noReload", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(MethodMergeParameters, no_reload), 0 }, { "noexec", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(MethodMergeParameters, noexec), 0 }, + VARLINK_DISPATCH_POLKIT_FIELD, {} }; @@ -2272,6 +2294,7 @@ static int parse_merge_parameters(sd_varlink *link, sd_json_variant *parameters, } static int vl_method_merge(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + Hashmap **polkit_registry = ASSERT_PTR(userdata); _cleanup_hashmap_free_ Hashmap *images = NULL; MethodMergeParameters p = { .force = -1, @@ -2280,7 +2303,8 @@ static int vl_method_merge(sd_varlink *link, sd_json_variant *parameters, sd_var }; _cleanup_strv_free_ char **hierarchies = NULL; ImageClass image_class = arg_image_class; - int r; + bool force, no_reload; + int r, noexec; assert(link); @@ -2292,6 +2316,23 @@ static int vl_method_merge(sd_varlink *link, sd_json_variant *parameters, sd_var if (r < 0) return r; + force = p.force >= 0 ? p.force : arg_force; + no_reload = p.no_reload >= 0 ? p.no_reload : arg_no_reload; + noexec = p.noexec >= 0 ? p.noexec : arg_noexec; + + r = varlink_verify_polkit_async( + link, + /* bus= */ NULL, + image_class_info[image_class].polkit_rw_action_id, + (const char**) STRV_MAKE( + "verb", "merge", + "force", one_zero(force), + "noReload", one_zero(no_reload), + "noexec", one_zero(noexec > 0)), + polkit_registry); + if (r <= 0) + return r; + r = image_discover_and_read_metadata(image_class, &images); if (r < 0) return r; @@ -2306,12 +2347,7 @@ static int vl_method_merge(sd_varlink *link, sd_json_variant *parameters, sd_var if (r > 0) return sd_varlink_errorbo(link, "io.systemd.sysext.AlreadyMerged", SD_JSON_BUILD_PAIR_STRING("hierarchy", which)); - r = merge(image_class, - hierarchies ?: arg_hierarchies, - p.force >= 0 ? p.force : arg_force, - p.no_reload >= 0 ? p.no_reload : arg_no_reload, - p.noexec >= 0 ? p.noexec : arg_noexec, - images); + r = merge(image_class, hierarchies ?: arg_hierarchies, force, no_reload, noexec, images); if (r < 0) return r; @@ -2381,9 +2417,11 @@ static int vl_method_refresh(sd_varlink *link, sd_json_variant *parameters, sd_v .no_reload = -1, .noexec = -1, }; + Hashmap **polkit_registry = ASSERT_PTR(userdata); _cleanup_strv_free_ char **hierarchies = NULL; ImageClass image_class = arg_image_class; - int r; + bool force, no_reload; + int r, noexec; assert(link); @@ -2395,11 +2433,24 @@ static int vl_method_refresh(sd_varlink *link, sd_json_variant *parameters, sd_v if (r < 0) return r; - r = refresh(image_class, - hierarchies ?: arg_hierarchies, - p.force >= 0 ? p.force : arg_force, - p.no_reload >= 0 ? p.no_reload : arg_no_reload, - p.noexec >= 0 ? p.noexec : arg_noexec); + force = p.force >= 0 ? p.force : arg_force; + no_reload = p.no_reload >= 0 ? p.no_reload : arg_no_reload; + noexec = p.noexec >= 0 ? p.noexec : arg_noexec; + + r = varlink_verify_polkit_async( + link, + /* bus= */ NULL, + image_class_info[image_class].polkit_rw_action_id, + (const char**) STRV_MAKE( + "verb", "refresh", + "force", one_zero(force), + "noReload", one_zero(no_reload), + "noexec", one_zero(noexec > 0)), + polkit_registry); + if (r <= 0) + return r; + + r = refresh(image_class, hierarchies ?: arg_hierarchies, force, no_reload, noexec); if (r < 0) return r; @@ -2445,9 +2496,11 @@ static int vl_method_list(sd_varlink *link, sd_json_variant *parameters, sd_varl static const sd_json_dispatch_field dispatch_table[] = { { "class", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, 0, 0 }, + VARLINK_DISPATCH_POLKIT_FIELD, {} }; _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + Hashmap **polkit_registry = ASSERT_PTR(userdata); int r; assert(link); @@ -2462,6 +2515,15 @@ static int vl_method_list(sd_varlink *link, sd_json_variant *parameters, sd_varl if (r < 0) return r; + r = varlink_verify_polkit_async( + link, + /* bus= */ NULL, + image_class_info[image_class].polkit_ro_action_id, + (const char**) STRV_MAKE("verb", "list"), + polkit_registry); + if (r <= 0) + return r; + _cleanup_hashmap_free_ Hashmap *images = NULL; r = image_discover(RUNTIME_SCOPE_SYSTEM, image_class, arg_root, &images); if (r < 0) @@ -2727,10 +2789,14 @@ static int run(int argc, char *argv[]) { if (arg_varlink) { _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL; + _cleanup_hashmap_free_ Hashmap *polkit_registry = NULL; /* Invocation as Varlink service */ - r = varlink_server_new(&varlink_server, SD_VARLINK_SERVER_ROOT_ONLY, NULL); + r = varlink_server_new( + &varlink_server, + SD_VARLINK_SERVER_ACCOUNT_UID|SD_VARLINK_SERVER_INHERIT_USERDATA, + &polkit_registry); if (r < 0) return log_error_errno(r, "Failed to allocate Varlink server: %m"); diff --git a/test/units/TEST-50-DISSECT.dissect.sh b/test/units/TEST-50-DISSECT.dissect.sh index 5dd26281abf..47ea82c3b77 100755 --- a/test/units/TEST-50-DISSECT.dissect.sh +++ b/test/units/TEST-50-DISSECT.dissect.sh @@ -811,6 +811,33 @@ grep -q -F "MARKER=1" /usr/lib/systemd/system/some_file varlinkctl call /run/systemd/io.systemd.sysext io.systemd.sysext.Unmerge '{}' (! grep -q -F "MARKER=1" /usr/lib/systemd/system/some_file ) +# And again, but unprivileged, if we have pidfds and polkit support +# Also check that the required policy is installed, as packages might be out of date with the test +if systemd-analyze compare-versions "$(uname -r)" ge 6.5 && \ + systemd-analyze compare-versions "$(pkcheck --version | awk '{print $3}')" ge 124 && \ + test -f /usr/share/polkit-1/actions/io.systemd.sysext.policy; then + mkdir -p /etc/polkit-1/rules.d + cat >/etc/polkit-1/rules.d/sysext-unpriv.rules <<'EOF' +polkit.addRule(function(action, subject) { + if (action.id == "io.systemd.sysext.manage" && + subject.user == "testuser") { + return polkit.Result.YES; + } +}); +EOF + systemctl try-reload-or-restart polkit.service + run0 -u testuser varlinkctl call --more /run/systemd/io.systemd.sysext io.systemd.sysext.List '{}' + (! grep -q -F "MARKER=1" /usr/lib/systemd/system/some_file ) + run0 -u testuser varlinkctl call /run/systemd/io.systemd.sysext io.systemd.sysext.Merge '{"allowInteractiveAuthentication": true}' + grep -q -F "MARKER=1" /usr/lib/systemd/system/some_file + run0 -u testuser varlinkctl call /run/systemd/io.systemd.sysext io.systemd.sysext.Refresh '{"allowInteractiveAuthentication": true}' + grep -q -F "MARKER=1" /usr/lib/systemd/system/some_file + run0 -u testuser varlinkctl call /run/systemd/io.systemd.sysext io.systemd.sysext.Unmerge '{"allowInteractiveAuthentication": true}' + (! grep -q -F "MARKER=1" /usr/lib/systemd/system/some_file ) + rm -f /etc/polkit-1/rules.d/sysext-unpriv.rules + systemctl try-reload-or-restart polkit.service +fi + # Check that extensions cannot contain os-release mkdir -p /run/extensions/app-reject/usr/lib/{extension-release.d/,systemd/system} echo "ID=_any" >/run/extensions/app-reject/usr/lib/extension-release.d/extension-release.app-reject diff --git a/test/units/TEST-50-DISSECT.sh b/test/units/TEST-50-DISSECT.sh index b42aa6e7b32..40b50252978 100755 --- a/test/units/TEST-50-DISSECT.sh +++ b/test/units/TEST-50-DISSECT.sh @@ -26,6 +26,8 @@ at_exit() { rm -rf "$IMAGE_DIR" + rm -f /etc/polkit-1/rules.d/sysext-unpriv.rules + loginctl disable-linger testuser } diff --git a/units/systemd-sysext.socket b/units/systemd-sysext.socket index 78475cf1967..97d1695780e 100644 --- a/units/systemd-sysext.socket +++ b/units/systemd-sysext.socket @@ -18,7 +18,7 @@ ConditionCapability=CAP_SYS_ADMIN [Socket] ListenStream=/run/systemd/io.systemd.sysext FileDescriptorName=varlink -SocketMode=0600 +SocketMode=0666 Accept=yes MaxConnectionsPerSource=16