]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
bus-polkit: add support for authenticating varlink peers via polkit
authorLennart Poettering <lennart@poettering.net>
Thu, 23 Nov 2023 17:21:21 +0000 (18:21 +0100)
committerLuca Boccassi <bluca@debian.org>
Wed, 3 Jan 2024 10:53:29 +0000 (11:53 +0100)
This extends our current polkit logic, so that we can in a very similar
fashion as we already can authenticate dbus peers authenticate varlink
connection peers.

polkit natively speaks dbus and can authentication dbus peers. To get
the same level of support for varlink we'll use authentication by
pidfd+uid. This requires polkit v124, and if that's not available it
will fallback to authorizing root only as before.

Co-authored-by: Luca Boccassi <bluca@debian.org>
src/shared/bus-polkit.c
src/shared/bus-polkit.h
src/shared/varlink.h

index 9f923372a4b3e3ecdddf523a1c8c57cb5ae89821..928b2c3d5b1174e1ffb7b2c01542d886223fe555 100644 (file)
@@ -4,10 +4,11 @@
 #include "bus-message.h"
 #include "bus-polkit.h"
 #include "bus-util.h"
+#include "process-util.h"
 #include "strv.h"
 #include "user-util.h"
 
-static int check_good_user(sd_bus_message *m, uid_t good_user) {
+static int bus_message_check_good_user(sd_bus_message *m, uid_t good_user) {
         _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
         uid_t sender_uid;
         int r;
@@ -15,7 +16,7 @@ static int check_good_user(sd_bus_message *m, uid_t good_user) {
         assert(m);
 
         if (good_user == UID_INVALID)
-                return 0;
+                return false;
 
         r = sd_bus_query_sender_creds(m, SD_BUS_CREDS_EUID, &creds);
         if (r < 0)
@@ -54,7 +55,7 @@ static int bus_message_append_strv_key_value(sd_bus_message *m, const char **l)
         return r;
 }
 
-static int bus_message_new_polkit_auth_call(
+static int bus_message_new_polkit_auth_call_for_bus(
                 sd_bus_message *m,
                 const char *action,
                 const char **details,
@@ -115,7 +116,7 @@ int bus_test_polkit(
 
         /* Tests non-interactively! */
 
-        r = check_good_user(call, good_user);
+        r = bus_message_check_good_user(call, good_user);
         if (r != 0)
                 return r;
 
@@ -129,7 +130,7 @@ int bus_test_polkit(
         _cleanup_(sd_bus_message_unrefp) sd_bus_message *request = NULL, *reply = NULL;
         int authorized = false, challenge = false;
 
-        r = bus_message_new_polkit_auth_call(call, action, details, /* interactive = */ false, &request);
+        r = bus_message_new_polkit_auth_call_for_bus(call, action, details, /* interactive = */ false, &request);
         if (r < 0)
                 return r;
 
@@ -190,8 +191,10 @@ typedef struct AsyncPolkitQuery {
 
         AsyncPolkitQueryAction *action;
 
+        sd_bus *bus;
         sd_bus_message *request;
         sd_bus_slot *slot;
+        Varlink *link;
 
         Hashmap *registry;
         sd_event_source *defer_event_source;
@@ -213,6 +216,9 @@ static AsyncPolkitQuery *async_polkit_query_free(AsyncPolkitQuery *q) {
 
         sd_bus_message_unref(q->request);
 
+        sd_bus_unref(q->bus);
+        varlink_unref(q->link);
+
         async_polkit_query_action_free(q->action);
 
         sd_event_source_disable_unref(q->defer_event_source);
@@ -316,7 +322,7 @@ static int async_polkit_process_reply(sd_bus_message *reply, AsyncPolkitQuery *q
 
         if (!q->defer_event_source) {
                 r = sd_event_add_defer(
-                                sd_bus_get_event(sd_bus_message_get_bus(reply)),
+                                sd_bus_get_event(q->bus),
                                 &q->defer_event_source,
                                 async_polkit_defer,
                                 q);
@@ -332,13 +338,21 @@ static int async_polkit_process_reply(sd_bus_message *reply, AsyncPolkitQuery *q
         if (r < 0)
                 return r;
 
-        r = sd_bus_message_rewind(q->request, true);
-        if (r < 0)
-                return r;
+        if (q->request) {
+                r = sd_bus_message_rewind(q->request, true);
+                if (r < 0)
+                        return r;
 
-        r = sd_bus_enqueue_for_read(sd_bus_message_get_bus(q->request), q->request);
-        if (r < 0)
-                return r;
+                r = sd_bus_enqueue_for_read(q->bus, q->request);
+                if (r < 0)
+                        return r;
+        }
+
+        if (q->link) {
+                r = varlink_dispatch_again(q->link);
+                if (r < 0)
+                        return r;
+        }
 
         return 1;
 }
@@ -352,7 +366,10 @@ static int async_polkit_callback(sd_bus_message *reply, void *userdata, sd_bus_e
         r = async_polkit_process_reply(reply, q);
         if (r < 0) {
                 log_debug_errno(r, "Processing asynchronous PolicyKit reply failed, ignoring: %m");
-                (void) sd_bus_reply_method_errno(q->request, r, NULL);
+                if (q->request)
+                        (void) sd_bus_reply_method_errno(q->request, r, NULL);
+                if (q->link)
+                        varlink_error_errno(q->link, r);
                 async_polkit_query_unref(q);
         }
         return r;
@@ -366,11 +383,10 @@ static int async_polkit_query_check_action(
 
         assert(q);
         assert(action);
-        assert(ret_error);
 
         LIST_FOREACH(authorized, a, q->authorized_actions)
                 if (streq(a->action, action) && strv_equal(a->details, (char**) details))
-                        return 1;
+                        return 1; /* Allow! */
 
         if (q->error_action && streq(q->error_action->action, action))
                 return sd_bus_error_copy(ret_error, &q->error);
@@ -480,7 +496,7 @@ int bus_verify_polkit_async_full(
         assert(registry);
         assert(ret_error);
 
-        r = check_good_user(call, good_user);
+        r = bus_message_check_good_user(call, good_user);
         if (r != 0)
                 return r;
 
@@ -512,11 +528,7 @@ int bus_verify_polkit_async_full(
         if (c > 0)
                 interactive = true;
 
-        r = hashmap_ensure_allocated(registry, NULL);
-        if (r < 0)
-                return r;
-
-        r = bus_message_new_polkit_auth_call(call, action, details, interactive, &pk);
+        r = bus_message_new_polkit_auth_call_for_bus(call, action, details, interactive, &pk);
         if (r < 0)
                 return r;
 
@@ -528,6 +540,7 @@ int bus_verify_polkit_async_full(
                 *q = (AsyncPolkitQuery) {
                         .n_ref = 1,
                         .request = sd_bus_message_ref(call),
+                        .bus = sd_bus_ref(sd_bus_message_get_bus(call)),
                 };
         }
 
@@ -544,7 +557,7 @@ int bus_verify_polkit_async_full(
                 return -ENOMEM;
 
         if (!q->registry) {
-                r = hashmap_put(*registry, call, q);
+                r = hashmap_ensure_put(registry, /* hash_ops= */ NULL, call, q);
                 if (r < 0)
                         return r;
 
@@ -571,3 +584,232 @@ Hashmap *bus_verify_polkit_async_registry_free(Hashmap *registry) {
         return hashmap_free(registry);
 #endif
 }
+
+static int varlink_check_good_user(Varlink *link, uid_t good_user) {
+        int r;
+
+        assert(link);
+
+        if (good_user == UID_INVALID)
+                return false;
+
+        uid_t peer_uid;
+        r = varlink_get_peer_uid(link, &peer_uid);
+        if (r < 0)
+                return r;
+
+        return good_user == peer_uid;
+}
+
+static int varlink_check_peer_privilege(Varlink *link) {
+        int r;
+
+        assert(link);
+
+        uid_t peer_uid;
+        r = varlink_get_peer_uid(link, &peer_uid);
+        if (r < 0)
+                return r;
+
+        uid_t our_uid = getuid();
+        return peer_uid == our_uid ||
+                (our_uid != 0 && peer_uid == 0);
+}
+
+#if ENABLE_POLKIT
+static int bus_message_new_polkit_auth_call_for_varlink(
+                sd_bus *bus,
+                Varlink *link,
+                const char *action,
+                const char **details,
+                bool interactive,
+                sd_bus_message **ret) {
+
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *c = NULL;
+        _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
+        int r;
+
+        assert(bus);
+        assert(link);
+        assert(action);
+        assert(ret);
+
+        r = varlink_get_peer_pidref(link, &pidref);
+        if (r < 0)
+                return r;
+        if (r == 0) /* if we couldn't get a pidfd this returns == 0 */
+                return log_debug_errno(SYNTHETIC_ERRNO(EPERM), "Failed to get peer pidfd, cannot securely authenticate.");
+
+        uid_t uid;
+        r = varlink_get_peer_uid(link, &uid);
+        if (r < 0)
+                return r;
+
+        r = sd_bus_message_new_method_call(
+                        bus,
+                        &c,
+                        "org.freedesktop.PolicyKit1",
+                        "/org/freedesktop/PolicyKit1/Authority",
+                        "org.freedesktop.PolicyKit1.Authority",
+                        "CheckAuthorization");
+        if (r < 0)
+                return r;
+
+        r = sd_bus_message_append(
+                        c,
+                        "(sa{sv})s",
+                        "unix-process", 2,
+                        "pidfd", "h", (uint32_t) pidref.fd,
+                        "uid", "i", (int32_t) uid,
+                        action);
+        if (r < 0)
+                return r;
+
+        r = bus_message_append_strv_key_value(c, details);
+        if (r < 0)
+                return r;
+
+        r = sd_bus_message_append(c, "us", interactive, NULL);
+        if (r < 0)
+                return r;
+
+        *ret = TAKE_PTR(c);
+        return 0;
+}
+
+static bool varlink_allow_interactive_authentication(Varlink *link) {
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+        int r;
+
+        assert(link);
+
+        /* We look for the allowInteractiveAuthentication field in the message currently being dispatched,
+         * always under the same name. */
+
+        r = varlink_get_current_parameters(link, &v);
+        if (r < 0)
+                return r;
+
+        JsonVariant *b;
+        b = json_variant_by_key(v, "allowInteractiveAuthentication");
+        if (b) {
+                if (json_variant_is_boolean(b))
+                        return json_variant_boolean(b);
+
+                log_debug("Incoming 'allowInteractiveAuthentication' field is not a boolean, ignoring.");
+        }
+
+        return false;
+}
+#endif
+
+int varlink_verify_polkit_async(
+                Varlink *link,
+                sd_bus *bus,
+                const char *action,
+                const char **details,
+                uid_t good_user,
+                Hashmap **registry) {
+
+        int r;
+
+        assert(link);
+        assert(registry);
+
+        /* This is the same as bus_verify_polkit_async_full(), but authenticates the peer of a varlink
+         * connection rather than the sender of a bus message. */
+
+        r = varlink_check_good_user(link, good_user);
+        if (r != 0)
+                return r;
+
+        r = varlink_check_peer_privilege(link);
+        if (r != 0)
+                return r;
+
+#if ENABLE_POLKIT
+        _cleanup_(async_polkit_query_unrefp) AsyncPolkitQuery *q = NULL;
+
+        q = async_polkit_query_ref(hashmap_get(*registry, link));
+        /* This is a repeated invocation of this function, hence let's check if we've already got
+         * a response from polkit for this action */
+        if (q) {
+                _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+                r = async_polkit_query_check_action(q, action, details, &error);
+                if (r < 0) {
+                        /* Reply with a nice error */
+                        if (sd_bus_error_has_name(&error, SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED))
+                                return varlink_error(link, VARLINK_ERROR_INTERACTIVE_AUTHENTICATION_REQUIRED, NULL);
+
+                        if (ERRNO_IS_NEG_PRIVILEGE(r))
+                                return varlink_error(link, VARLINK_ERROR_PERMISSION_DENIED, NULL);
+
+                        return r;
+                }
+                if (r > 0)
+                        return r;
+        }
+
+        _cleanup_(sd_bus_unrefp) sd_bus *mybus = NULL;
+        if (!bus) {
+                r = sd_bus_open_system(&mybus);
+                if (r < 0)
+                        return r;
+
+                r = sd_bus_attach_event(mybus, varlink_get_event(link), 0);
+                if (r < 0)
+                        return r;
+
+                bus = mybus;
+        }
+
+        bool interactive = varlink_allow_interactive_authentication(link);
+
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *pk = NULL;
+        r = bus_message_new_polkit_auth_call_for_varlink(bus, link, action, details, interactive, &pk);
+        if (r < 0)
+                return r;
+
+        if (!q) {
+                q = new(AsyncPolkitQuery, 1);
+                if (!q)
+                        return -ENOMEM;
+
+                *q = (AsyncPolkitQuery) {
+                        .n_ref = 1,
+                        .link = varlink_ref(link),
+                        .bus = sd_bus_ref(bus),
+                };
+        }
+
+        assert(!q->action);
+        q->action = new(AsyncPolkitQueryAction, 1);
+        if (!q->action)
+                return -ENOMEM;
+
+        *q->action = (AsyncPolkitQueryAction) {
+                .action = strdup(action),
+                .details = strv_copy((char**) details),
+        };
+        if (!q->action->action || !q->action->details)
+                return -ENOMEM;
+
+        if (!q->registry) {
+                r = hashmap_ensure_put(registry, /* hash_ops= */ NULL, link, q);
+                if (r < 0)
+                        return r;
+
+                q->registry = *registry;
+        }
+
+        r = sd_bus_call_async(bus, &q->slot, pk, async_polkit_callback, q, 0);
+        if (r < 0)
+                return r;
+
+        TAKE_PTR(q);
+
+        return 0;
+#endif
+
+        return -EACCES;
+}
index d82ac4679c617ee70cb024aadb9b0c1d77b74871..ac2a90c3c70e12b69b6a5008c9a4b3a94ebfa5c8 100644 (file)
@@ -5,6 +5,7 @@
 
 #include "hashmap.h"
 #include "user-util.h"
+#include "varlink.h"
 
 int bus_test_polkit(sd_bus_message *call, const char *action, const char **details, uid_t good_user, bool *_challenge, sd_bus_error *e);
 
@@ -14,3 +15,13 @@ static inline int bus_verify_polkit_async(sd_bus_message *call, const char *acti
 }
 
 Hashmap *bus_verify_polkit_async_registry_free(Hashmap *registry);
+
+int varlink_verify_polkit_async(Varlink *link, sd_bus *bus, const char *action, const char **details, uid_t good_user, Hashmap **registry);
+
+/* A JsonDispatch initializer that makes sure the allowInteractiveAuthentication boolean field we want for
+ * polkit support in Varlink calls is ignored while regular dispatching (and does not result in errors
+ * regarding unexpected fields) */
+#define VARLINK_DISPATCH_POLKIT_FIELD {                          \
+                .name = "allowInteractiveAuthentication",        \
+                .type = JSON_VARIANT_BOOLEAN,                    \
+        }
index bca4fab05d84401d02d6c56f50c203a330547705..a971762a511c65ed9b8af5c4f80e4e3804886180 100644 (file)
@@ -222,6 +222,9 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(VarlinkServer *, varlink_server_unref);
 /* This one we invented, and use for generically propagating system errors (errno) to clients */
 #define VARLINK_ERROR_SYSTEM "io.systemd.System"
 
+/* This one we invented and is a weaker version of "org.varlink.service.PermissionDenied", and indicates that if user would allow interactive auth, we might allow access */
+#define VARLINK_ERROR_INTERACTIVE_AUTHENTICATION_REQUIRED "io.systemd.InteractiveAuthenticationRequired"
+
 /* These are errors defined in the Varlink spec */
 #define VARLINK_ERROR_INTERFACE_NOT_FOUND "org.varlink.service.InterfaceNotFound"
 #define VARLINK_ERROR_METHOD_NOT_FOUND "org.varlink.service.MethodNotFound"