]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sd-bus: add ability to connect to bus as a specific user
authorLennart Poettering <lennart@poettering.net>
Tue, 7 Nov 2023 15:37:39 +0000 (16:37 +0100)
committerLennart Poettering <lennart@poettering.net>
Thu, 14 Mar 2024 10:34:04 +0000 (11:34 +0100)
src/libsystemd/sd-bus/bus-internal.h
src/libsystemd/sd-bus/bus-socket.c
src/libsystemd/sd-bus/sd-bus.c

index 8229c432c20d83a95d3e0689b0ca16f79190436b..e0f4746b7ed10ccc3ec658841183b8734aa3b63d 100644 (file)
@@ -254,6 +254,9 @@ struct sd_bus {
         char *address;
         unsigned address_index;
 
+        uid_t connect_as_uid;
+        gid_t connect_as_gid;
+
         int last_connect_error;
 
         enum bus_auth auth;
index f0f518a35011d8fdf62fd71273d8b5046fd18f6d..07179e070523493a507ce676e276fe38fb78cb42 100644 (file)
@@ -503,11 +503,38 @@ static int bus_socket_write_auth(sd_bus *b) {
         if (b->prefer_writev)
                 k = writev(b->output_fd, b->auth_iovec + b->auth_index, ELEMENTSOF(b->auth_iovec) - b->auth_index);
         else {
+                CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct ucred))) control = {};
+
                 struct msghdr mh = {
                         .msg_iov = b->auth_iovec + b->auth_index,
                         .msg_iovlen = ELEMENTSOF(b->auth_iovec) - b->auth_index,
                 };
 
+                if (uid_is_valid(b->connect_as_uid) || gid_is_valid(b->connect_as_gid)) {
+
+                        /* If we shall connect under some specific UID/GID, then synthesize an
+                         * SCM_CREDENTIALS record accordingly. After all we want to adopt this UID/GID both
+                         * for SO_PEERCRED (where we have to fork()) and SCM_CREDENTIALS (where we can just
+                         * fake it via sendmsg()) */
+
+                        struct ucred ucred = {
+                                .pid = getpid_cached(),
+                                .uid = uid_is_valid(b->connect_as_uid) ? b->connect_as_uid : getuid(),
+                                .gid = gid_is_valid(b->connect_as_gid) ? b->connect_as_gid : getgid(),
+                        };
+
+                        mh.msg_control = &control;
+                        mh.msg_controllen = sizeof(control);
+                        struct cmsghdr *cmsg = CMSG_FIRSTHDR(&mh);
+                        *cmsg = (struct cmsghdr) {
+                                .cmsg_level = SOL_SOCKET,
+                                .cmsg_type = SCM_CREDENTIALS,
+                                .cmsg_len = CMSG_LEN(sizeof(struct ucred)),
+                        };
+
+                        memcpy(CMSG_DATA(cmsg), &ucred, sizeof(struct ucred));
+                }
+
                 k = sendmsg(b->output_fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL);
                 if (k < 0 && errno == ENOTSOCK) {
                         b->prefer_writev = true;
@@ -949,6 +976,66 @@ static int bind_description(sd_bus *b, int fd, int family) {
         return 0;
 }
 
+static int connect_as(int fd, const struct sockaddr *sa, socklen_t salen, uid_t uid, gid_t gid) {
+        _cleanup_(close_pairp) int pfd[2] = EBADF_PAIR;
+        ssize_t n;
+        int r;
+
+        /* Shortcut if we are not supposed to drop privileges */
+        if (!uid_is_valid(uid) && !gid_is_valid(gid))
+                return RET_NERRNO(connect(fd, sa, salen));
+
+        /* This changes identity to the specified uid/gid and issues connect() as that. This is useful to
+         * make sure SO_PEERCRED reports the selected UID/GID rather than the usual one of the caller. */
+
+        if (pipe2(pfd, O_CLOEXEC) < 0)
+                return -errno;
+
+        r = safe_fork("(sd-setresuid)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL|FORK_WAIT, /* ret_pid= */ NULL);
+        if (r < 0)
+                return r;
+        if (r == 0) {
+                /* child */
+
+                pfd[0] = safe_close(pfd[0]);
+
+                r = RET_NERRNO(setgroups(0, NULL));
+                if (r < 0)
+                        goto child_finish;
+
+                if (gid_is_valid(gid)) {
+                        r = RET_NERRNO(setresgid(gid, gid, gid));
+                        if (r < 0)
+                                goto child_finish;
+                }
+
+                if (uid_is_valid(uid)) {
+                        r = RET_NERRNO(setresuid(uid, uid, uid));
+                        if (r < 0)
+                                goto child_finish;
+                }
+
+                r = RET_NERRNO(connect(fd, sa, salen));
+                if (r < 0)
+                        goto child_finish;
+
+                r = 0;
+
+        child_finish:
+                n = write(pfd[1], &r, sizeof(r));
+                if (n != sizeof(r))
+                        _exit(EXIT_FAILURE);
+
+                _exit(EXIT_SUCCESS);
+        }
+
+        n = read(pfd[0], &r, sizeof(r));
+        if (n != sizeof(r))
+                return -EIO;
+
+        return r;
+}
+
 int bus_socket_connect(sd_bus *b) {
         bool inotify_done = false;
         int r;
@@ -980,8 +1067,9 @@ int bus_socket_connect(sd_bus *b) {
                 b->output_fd = b->input_fd;
                 bus_socket_setup(b);
 
-                if (connect(b->input_fd, &b->sockaddr.sa, b->sockaddr_size) < 0) {
-                        if (errno == EINPROGRESS) {
+                r = connect_as(b->input_fd, &b->sockaddr.sa, b->sockaddr_size, b->connect_as_uid, b->connect_as_gid);
+                if (r < 0) {
+                        if (r == -EINPROGRESS) {
 
                                 /* If we have any inotify watches open, close them now, we don't need them anymore, as
                                  * we have successfully initiated a connection */
@@ -994,7 +1082,7 @@ int bus_socket_connect(sd_bus *b) {
                                 return 1;
                         }
 
-                        if (IN_SET(errno, ENOENT, ECONNREFUSED) &&  /* ENOENT → unix socket doesn't exist at all; ECONNREFUSED → unix socket stale */
+                        if (IN_SET(r, -ENOENT, -ECONNREFUSED) &&  /* ENOENT → unix socket doesn't exist at all; ECONNREFUSED → unix socket stale */
                             b->watch_bind &&
                             b->sockaddr.sa.sa_family == AF_UNIX &&
                             b->sockaddr.un.sun_path[0] != 0) {
@@ -1022,7 +1110,7 @@ int bus_socket_connect(sd_bus *b) {
                                 inotify_done = true;
 
                         } else
-                                return -errno;
+                                return r;
                 } else
                         break;
         }
index a25369e930b994082a729b5f65ac2a98f5bf0780..22784e8f66e0615f9b7f98c55449e995a4e11e5c 100644 (file)
@@ -259,6 +259,8 @@ _public_ int sd_bus_new(sd_bus **ret) {
                 .ucred = UCRED_INVALID,
                 .pidfd = -EBADF,
                 .runtime_scope = _RUNTIME_SCOPE_INVALID,
+                .connect_as_uid = UID_INVALID,
+                .connect_as_gid = GID_INVALID,
         };
 
         /* We guarantee that wqueue always has space for at least one entry */
@@ -716,7 +718,7 @@ static void skip_address_key(const char **p) {
 }
 
 static int parse_unix_address(sd_bus *b, const char **p, char **guid) {
-        _cleanup_free_ char *path = NULL, *abstract = NULL;
+        _cleanup_free_ char *path = NULL, *abstract = NULL, *uids = NULL, *gids = NULL;
         size_t l;
         int r;
 
@@ -744,6 +746,18 @@ static int parse_unix_address(sd_bus *b, const char **p, char **guid) {
                 else if (r > 0)
                         continue;
 
+                r = parse_address_key(p, "uid", &uids);
+                if (r < 0)
+                        return r;
+                else if (r > 0)
+                        continue;
+
+                r = parse_address_key(p, "gid", &gids);
+                if (r < 0)
+                        return r;
+                else if (r > 0)
+                        continue;
+
                 skip_address_key(p);
         }
 
@@ -780,6 +794,17 @@ static int parse_unix_address(sd_bus *b, const char **p, char **guid) {
                 b->sockaddr_size = offsetof(struct sockaddr_un, sun_path) + 1 + l;
         }
 
+        if (uids) {
+                r = parse_uid(uids, &b->connect_as_uid);
+                if (r < 0)
+                        return r;
+        }
+        if (gids) {
+                r = parse_gid(gids, &b->connect_as_gid);
+                if (r < 0)
+                        return r;
+        }
+
         b->is_local = true;
 
         return 0;