mirror of
https://github.com/NixOS/nix
synced 2025-06-24 22:11:15 +02:00
Merge 1d358ebb1c
into f9afc1e68c
This commit is contained in:
commit
cb1b2a80a2
2 changed files with 169 additions and 9 deletions
|
@ -484,6 +484,35 @@ public:
|
||||||
"id-count",
|
"id-count",
|
||||||
"The number of UIDs/GIDs to use for dynamic ID allocation."};
|
"The number of UIDs/GIDs to use for dynamic ID allocation."};
|
||||||
|
|
||||||
|
Setting<StringSet> supplementaryGroups{
|
||||||
|
this, {"kvm"}, "supplementary-groups",
|
||||||
|
R"(
|
||||||
|
A list of supplementary groups to be added to build users when dynamic
|
||||||
|
UID/GID allocation [`auto-allocate-uids`](#conf-auto-allocate-uids) is
|
||||||
|
enabled.
|
||||||
|
|
||||||
|
By default, each listed group is mapped into the build sandbox using its
|
||||||
|
host system GID. You can override the mapped GID by appending `:GID` to
|
||||||
|
the group name. If a group does not exist on the host (i.e., has no
|
||||||
|
assigned GID), it is silently ignored.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
extra-supplementary-groups = nixbld users:10100
|
||||||
|
```
|
||||||
|
|
||||||
|
This maps the `nixbld` group using its assigned GID (usually 30000).
|
||||||
|
And the `users` group gets assigned GID 10100.
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
>
|
||||||
|
> When using [`build-users-group`](#conf-build-users-group),
|
||||||
|
> supplementary groups should instead be specified directly in the
|
||||||
|
> user definitions.
|
||||||
|
)",
|
||||||
|
{"build-supplementary-groups"}};
|
||||||
|
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
Setting<bool> useCgroups{
|
Setting<bool> useCgroups{
|
||||||
this, false, "use-cgroups",
|
this, false, "use-cgroups",
|
||||||
|
|
|
@ -221,6 +221,113 @@ struct ChrootLinuxDerivationBuilder : LinuxDerivationBuilder
|
||||||
return usingUserNamespace ? (!buildUser || buildUser->getUIDCount() == 1 ? 100 : 0) : buildUser->getGID();
|
return usingUserNamespace ? (!buildUser || buildUser->getUIDCount() == 1 ? 100 : 0) : buildUser->getGID();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool setSupplementaryGroups;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return parent_gid -> (mapped_gid, mapped_name)
|
||||||
|
*
|
||||||
|
* Keys are host/parent gid's (can't have duplicates obviously). Values
|
||||||
|
* are the mapped/target (gid, name) pairs. Each mapped gid (name) may
|
||||||
|
* only appear once in the result.
|
||||||
|
*/
|
||||||
|
std::map<gid_t, std::tuple<gid_t, std::string>> getSupplementaryGIDMap()
|
||||||
|
{
|
||||||
|
// Primary GID (sandboxGid) is always mapped and it would make no
|
||||||
|
// sense to assign as a supplementary group as well.
|
||||||
|
//
|
||||||
|
// It would be technically harmless to allow mapping and assigning 0,
|
||||||
|
// aside from some potential confusion. It's only disallowed here
|
||||||
|
// because the root group is declared always anyway. Allowing it here
|
||||||
|
// would result in a duplicate /etc/group entry.
|
||||||
|
//
|
||||||
|
// Allowing assigning nogroup 65534 would be very bad, especially if
|
||||||
|
// root 0 wasn't mapped, as it's the fallback group to which all
|
||||||
|
// unmapped GIDs get mapped to (including root).
|
||||||
|
std::vector<gid_t> reserved_gids = {sandboxGid(), 0, 65534};
|
||||||
|
std::vector<std::string> reserved_names = {"root", "nixbld", "nogroup"};
|
||||||
|
|
||||||
|
std::map<gid_t, std::tuple<gid_t, std::string>> gid_map;
|
||||||
|
|
||||||
|
struct group grp;
|
||||||
|
struct group * gr = nullptr;
|
||||||
|
long bufsize = sysconf(_SC_GETGR_R_SIZE_MAX);
|
||||||
|
if (bufsize == -1) bufsize = 16384;
|
||||||
|
std::vector<char> buffer;
|
||||||
|
|
||||||
|
for (const auto & group_entry : settings.supplementaryGroups.get()) {
|
||||||
|
std::string group_name = group_entry;
|
||||||
|
std::optional<gid_t> mapped_gid;
|
||||||
|
|
||||||
|
// parse "name[:gid]"
|
||||||
|
auto pos = group_entry.find(":");
|
||||||
|
if (pos != std::string::npos) {
|
||||||
|
std::string gid_str = group_entry.substr(pos + 1);
|
||||||
|
group_name = group_entry.substr(0, pos);
|
||||||
|
try {
|
||||||
|
mapped_gid = static_cast<gid_t>(std::stoul(gid_str));
|
||||||
|
} catch (...) {
|
||||||
|
throw Error("Invalid GID number in '%s'", gid_str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
buffer.resize(bufsize);
|
||||||
|
int ret = getgrnam_r(group_name.c_str(), &grp, buffer.data(), buffer.size(), &gr);
|
||||||
|
if (ret == 0) {
|
||||||
|
break;
|
||||||
|
} else if (ret == ERANGE) { // buffer too small
|
||||||
|
bufsize *= 2;
|
||||||
|
} else if (ret != 0)
|
||||||
|
throw Error("getgrnam_r failed for group '%s': %s", group_name, strerror(ret));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gr) {
|
||||||
|
debug("Supplementary group '%s' not found", group_name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// host GID sanity checks
|
||||||
|
gid_t parent_gid = gr->gr_gid;
|
||||||
|
if (parent_gid == 0)
|
||||||
|
throw Error("Group '%s': mapping the root group (GID 0) is not a good idea", group_name);
|
||||||
|
if (gid_map.contains(parent_gid))
|
||||||
|
throw Error("Group '%s': parent GID %d is already mapped", group_name, parent_gid);
|
||||||
|
|
||||||
|
/* Mapped GID sanity checks
|
||||||
|
|
||||||
|
65535 is the special invalid/overflow GID distinct from
|
||||||
|
nogroup. We don't want to allow that.
|
||||||
|
|
||||||
|
2^16 and above are not allowed because it would seem impossible
|
||||||
|
to assign them. In a quick test higher GIDs got truncated to
|
||||||
|
65534. Might have something to do with how we're forced to call
|
||||||
|
setgroups before setting up the namespace. */
|
||||||
|
if (!mapped_gid)
|
||||||
|
mapped_gid = parent_gid;
|
||||||
|
if (mapped_gid > 65534)
|
||||||
|
throw Error("Group '%s': mapped GID %d is too large (>65534)", group_name, mapped_gid.value());
|
||||||
|
if (std::find(reserved_gids.begin(), reserved_gids.end(), mapped_gid.value()) != reserved_gids.end())
|
||||||
|
throw Error("Group '%s': mapped GID %d conflicts with reserved GID", group_name, mapped_gid.value());
|
||||||
|
|
||||||
|
// mapped name sanity checks
|
||||||
|
std::string mapped_name = gr->gr_name;
|
||||||
|
if (std::find(reserved_names.begin(), reserved_names.end(), mapped_name) != reserved_names.end()) {
|
||||||
|
mapped_name += "-host";
|
||||||
|
debug("Group '%s': name conflicts with reserved name; renaming to '%s'", group_name, mapped_name);
|
||||||
|
if (std::find(reserved_names.begin(), reserved_names.end(), mapped_name) != reserved_names.end()) {
|
||||||
|
warn("Group '%s': original and alternative name '%s' both conflict with reserved names; skipping this group", group_name, mapped_name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gid_map[parent_gid] = std::make_tuple(mapped_gid.value(), std::move(mapped_name));
|
||||||
|
reserved_gids.push_back(mapped_gid.value());
|
||||||
|
reserved_names.push_back(std::get<1>(gid_map[parent_gid]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return gid_map;
|
||||||
|
}
|
||||||
|
|
||||||
bool needsHashRewrite() override
|
bool needsHashRewrite() override
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
@ -292,6 +399,8 @@ struct ChrootLinuxDerivationBuilder : LinuxDerivationBuilder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setSupplementaryGroups = settings.autoAllocateUids && getuid() == 0;
|
||||||
|
|
||||||
// Kill any processes left in the cgroup or build user.
|
// Kill any processes left in the cgroup or build user.
|
||||||
DerivationBuilderImpl::prepareUser();
|
DerivationBuilderImpl::prepareUser();
|
||||||
}
|
}
|
||||||
|
@ -343,12 +452,17 @@ struct ChrootLinuxDerivationBuilder : LinuxDerivationBuilder
|
||||||
|
|
||||||
/* Declare the build user's group so that programs get a consistent
|
/* Declare the build user's group so that programs get a consistent
|
||||||
view of the system (e.g., "id -gn"). */
|
view of the system (e.g., "id -gn"). */
|
||||||
writeFile(
|
std::ostringstream oss;
|
||||||
chrootRootDir + "/etc/group",
|
oss << fmt(
|
||||||
fmt("root:x:0:\n"
|
"root:x:0:\n"
|
||||||
"nixbld:!:%1%:\n"
|
"nogroup:x:65534:\n"
|
||||||
"nogroup:x:65534:\n",
|
"nixbld:!:%d:\n", sandboxGid());
|
||||||
sandboxGid()));
|
|
||||||
|
if (setSupplementaryGroups)
|
||||||
|
for (const auto & [parent_gid, mapped] : getSupplementaryGIDMap())
|
||||||
|
oss << fmt("%s:x:%d:nixbld\n", std::get<1>(mapped), std::get<0>(mapped));
|
||||||
|
|
||||||
|
writeFile(chrootRootDir + "/etc/group", oss.str());
|
||||||
|
|
||||||
/* Create /etc/hosts with localhost entry. */
|
/* Create /etc/hosts with localhost entry. */
|
||||||
if (derivationType.isSandboxed())
|
if (derivationType.isSandboxed())
|
||||||
|
@ -452,6 +566,13 @@ struct ChrootLinuxDerivationBuilder : LinuxDerivationBuilder
|
||||||
|
|
||||||
usingUserNamespace = userNamespacesSupported();
|
usingUserNamespace = userNamespacesSupported();
|
||||||
|
|
||||||
|
// NOTE: setting supplementary groups like this only only works in
|
||||||
|
// certain conditions (root permissions).
|
||||||
|
std::vector<gid_t> supplementaryGroups;
|
||||||
|
if (setSupplementaryGroups)
|
||||||
|
for (const auto & [parent_gid, mapped] : getSupplementaryGIDMap())
|
||||||
|
supplementaryGroups.push_back(std::get<0>(mapped));
|
||||||
|
|
||||||
Pipe sendPid;
|
Pipe sendPid;
|
||||||
sendPid.create();
|
sendPid.create();
|
||||||
|
|
||||||
|
@ -464,9 +585,9 @@ struct ChrootLinuxDerivationBuilder : LinuxDerivationBuilder
|
||||||
openSlave();
|
openSlave();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
/* Drop additional groups here because we can't do it
|
/* Drop and/or set additional groups here because we can't do it
|
||||||
after we've created the new user namespace. */
|
after we've created the new user namespace. */
|
||||||
if (setgroups(0, 0) == -1) {
|
if (setgroups(supplementaryGroups.size(), supplementaryGroups.data()) == -1) {
|
||||||
if (errno != EPERM)
|
if (errno != EPERM)
|
||||||
throw SysError("setgroups failed");
|
throw SysError("setgroups failed");
|
||||||
if (settings.requireDropSupplementaryGroups)
|
if (settings.requireDropSupplementaryGroups)
|
||||||
|
@ -522,7 +643,17 @@ struct ChrootLinuxDerivationBuilder : LinuxDerivationBuilder
|
||||||
if (!buildUser || buildUser->getUIDCount() == 1)
|
if (!buildUser || buildUser->getUIDCount() == 1)
|
||||||
writeFile("/proc/" + std::to_string(pid) + "/setgroups", "deny");
|
writeFile("/proc/" + std::to_string(pid) + "/setgroups", "deny");
|
||||||
|
|
||||||
writeFile("/proc/" + std::to_string(pid) + "/gid_map", fmt("%d %d %d", sandboxGid(), hostGid, nrIds));
|
std::ostringstream oss;
|
||||||
|
|
||||||
|
// Primary GID mapping
|
||||||
|
oss << fmt("%d %d %d", sandboxGid(), hostGid, nrIds);
|
||||||
|
|
||||||
|
if (setSupplementaryGroups)
|
||||||
|
// Supplementary GIDs one by one
|
||||||
|
for (const auto & [parent_gid, mapped] : getSupplementaryGIDMap())
|
||||||
|
oss << fmt("\n%d %d 1", std::get<0>(mapped), parent_gid);
|
||||||
|
|
||||||
|
writeFile("/proc/" + std::to_string(pid) + "/gid_map", oss.str());
|
||||||
} else {
|
} else {
|
||||||
debug("note: not using a user namespace");
|
debug("note: not using a user namespace");
|
||||||
if (!buildUser)
|
if (!buildUser)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue