diff --git a/src/basic/missing.h b/src/basic/missing.h index a6b10b16bb7..1280e6c4100 100644 --- a/src/basic/missing.h +++ b/src/basic/missing.h @@ -621,6 +621,10 @@ struct btrfs_ioctl_quota_ctl_args { # define SO_REUSEPORT 15 #endif +#ifndef SO_PEERGROUPS +# define SO_PEERGROUPS 59 +#endif + #ifndef EVIOCREVOKE # define EVIOCREVOKE _IOW('E', 0x91, int) #endif diff --git a/src/basic/socket-util.c b/src/basic/socket-util.c index 54d526a5217..56e0e8e434e 100644 --- a/src/basic/socket-util.c +++ b/src/basic/socket-util.c @@ -1019,6 +1019,39 @@ int getpeersec(int fd, char **ret) { return 0; } +int getpeergroups(int fd, gid_t **ret) { + socklen_t n = sizeof(gid_t) * 64; + _cleanup_free_ gid_t *d = NULL; + + assert(fd); + assert(ret); + + for (;;) { + d = malloc(n); + if (!d) + return -ENOMEM; + + if (getsockopt(fd, SOL_SOCKET, SO_PEERGROUPS, d, &n) >= 0) + break; + + if (errno != ERANGE) + return -errno; + + d = mfree(d); + } + + assert_se(n % sizeof(gid_t) == 0); + n /= sizeof(gid_t); + + if ((socklen_t) (int) n != n) + return -E2BIG; + + *ret = d; + d = NULL; + + return (int) n; +} + int send_one_fd_sa( int transport_fd, int fd, diff --git a/src/basic/socket-util.h b/src/basic/socket-util.h index ba5be9b67d1..83af91dbef2 100644 --- a/src/basic/socket-util.h +++ b/src/basic/socket-util.h @@ -138,6 +138,7 @@ bool address_label_valid(const char *p); int getpeercred(int fd, struct ucred *ucred); int getpeersec(int fd, char **ret); +int getpeergroups(int fd, gid_t **ret); int send_one_fd_sa(int transport_fd, int fd, diff --git a/src/test/test-socket-util.c b/src/test/test-socket-util.c index 6a91baf6d29..201ce6667de 100644 --- a/src/test/test-socket-util.c +++ b/src/test/test-socket-util.c @@ -18,6 +18,10 @@ along with systemd; If not, see . ***/ +#include +#include +#include + #include "alloc-util.h" #include "async.h" #include "fd-util.h" @@ -474,6 +478,68 @@ static void test_in_addr_is_multicast(void) { assert_se(in_addr_is_multicast(f, &b) == 0); } +static void test_getpeercred_getpeergroups(void) { + int r; + + r = safe_fork("(getpeercred)", FORK_DEATHSIG|FORK_LOG|FORK_WAIT, NULL); + assert_se(r >= 0); + + if (r == 0) { + static const gid_t gids[] = { 3, 4, 5, 6, 7 }; + gid_t *test_gids; + gid_t *peer_groups = NULL; + size_t n_test_gids; + uid_t test_uid; + gid_t test_gid; + struct ucred ucred; + int pair[2]; + + if (geteuid() == 0) { + test_uid = 1; + test_gid = 2; + test_gids = (gid_t*) gids; + n_test_gids = ELEMENTSOF(gids); + + assert_se(setgroups(n_test_gids, test_gids) >= 0); + assert_se(setresgid(test_gid, test_gid, test_gid) >= 0); + assert_se(setresuid(test_uid, test_uid, test_uid) >= 0); + + } else { + long ngroups_max; + + test_uid = getuid(); + test_gid = getgid(); + + ngroups_max = sysconf(_SC_NGROUPS_MAX); + assert(ngroups_max > 0); + + test_gids = newa(gid_t, ngroups_max); + + r = getgroups(ngroups_max, test_gids); + assert_se(r >= 0); + n_test_gids = (size_t) r; + } + + assert_se(socketpair(AF_UNIX, SOCK_STREAM, 0, pair) >= 0); + + assert_se(getpeercred(pair[0], &ucred) >= 0); + + assert_se(ucred.uid == test_uid); + assert_se(ucred.gid == test_gid); + assert_se(ucred.pid == getpid_cached()); + + r = getpeergroups(pair[0], &peer_groups); + assert_se(r >= 0 || IN_SET(r, -EOPNOTSUPP, -ENOPROTOOPT)); + + if (r >= 0) { + assert_se((size_t) r == n_test_gids); + assert_se(memcmp(peer_groups, test_gids, sizeof(gid_t) * n_test_gids) == 0); + } + + safe_close_pair(pair); + } +} + int main(int argc, char *argv[]) { log_set_max_level(LOG_DEBUG); @@ -502,5 +568,7 @@ int main(int argc, char *argv[]) { test_in_addr_is_multicast(); + test_getpeercred_getpeergroups(); + return 0; }