nix/daemon: receive supplementary groups on Linux

On Linux, SO_PEERGROUPS is an option introduced in 2017, that is, in
Linux 4.13.

Thankfully, Lix targets 5.10+ kernels minimum.

It was chosen to allocate 128 gids by default and ramp up (2x) as needed
rather than allocate a full 65k of integers as it seems wasteful. I bet
the time to the 16 allocations should incur at most an additional
millisecond on a modern system, don't quote me on that though.

This is preparation to enable ability for the daemon to vet based on
supplementary groups.

Related to #968.

Suggested-by: alois31
Suggested-by: eldritch horrors
Change-Id: I26d698327db5d174bf70ca25b0afede132bd9169
Signed-off-by: Raito Bezarius <raito@lix.systems>
This commit is contained in:
Raito Bezarius
2026-01-28 08:24:09 +00:00
parent dbb3edcfea
commit 9e55dd6b8f
+39 -1
View File
@@ -173,6 +173,7 @@ struct PeerInfo
std::optional<pid_t> pid;
uid_t uid;
gid_t gid;
std::vector<gid_t> supplementaryGids;
};
@@ -181,13 +182,50 @@ struct PeerInfo
*/
static PeerInfo getPeerInfo(int remote)
{
std::vector<gid_t> supplementaryGids;
#if defined(SO_PEERCRED)
ucred cred;
socklen_t credLen = sizeof(cred);
if (getsockopt(remote, SOL_SOCKET, SO_PEERCRED, &cred, &credLen) == -1)
throw SysError("getting peer credentials");
PeerInfo peer = {cred.pid, cred.uid, cred.gid};
#if defined(SO_PEERGROUPS)
// NOTE(Raito):
// Linux can go up to NGROUPS_MAX supplementary groups (65K).
// It is safe to assume that most users will have a couple of supplementary
// groups by default (here, my Linux user has ~7).
// We allocate 128 because integers are tiny.
supplementaryGids.resize(128);
// Initially attempt to retrieve 128 groups.
socklen_t nrSupplementaryGids = supplementaryGids.size();
while (true) {
if (getsockopt(remote, SOL_SOCKET, SO_PEERGROUPS, supplementaryGids.data(), &nrSupplementaryGids)
== -1
&& errno != ERANGE)
{
throw SysError("getting peer groups");
}
// If the number of groups returned is less than the requested size, we are done.
if (nrSupplementaryGids <= supplementaryGids.size()) {
// We ensure the vector matches exactly the number of groups to avoid
// letting the rest of the vector imply that the vector is full of `root` groups.
supplementaryGids.resize(nrSupplementaryGids);
break;
}
// Otherwise, the vector is too small. Resize and try again.
nrSupplementaryGids *= 2;
// We ensure the vector is big enough in response to our latest known allocation requirement.
supplementaryGids.resize(nrSupplementaryGids);
}
#endif
PeerInfo peer = {cred.pid, cred.uid, cred.gid, supplementaryGids};
#elif defined(LOCAL_PEERCRED)