sd-bus: add ability to connect to bus as a specific user

This commit is contained in:
Lennart Poettering 2023-11-07 16:37:39 +01:00
parent 837eda0522
commit fc772c61e8
3 changed files with 121 additions and 5 deletions

View 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;

View 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;
}

View 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;