summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--nix/libstore/build.cc95
-rw-r--r--tests/store.scm23
2 files changed, 112 insertions, 6 deletions
diff --git a/nix/libstore/build.cc b/nix/libstore/build.cc
index a48214a9c0a..f455343c189 100644
--- a/nix/libstore/build.cc
+++ b/nix/libstore/build.cc
@@ -25,6 +25,7 @@
25#include <sys/utsname.h> 25#include <sys/utsname.h>
26#include <fcntl.h> 26#include <fcntl.h>
27#include <unistd.h> 27#include <unistd.h>
28#include <grp.h>
28#include <errno.h> 29#include <errno.h>
29#include <stdio.h> 30#include <stdio.h>
30#include <cstring> 31#include <cstring>
@@ -1646,15 +1647,81 @@ static void initializeUserNamespace(pid_t child,
1646 uid_t hostUID = getuid(), 1647 uid_t hostUID = getuid(),
1647 gid_t hostGID = getgid(), 1648 gid_t hostGID = getgid(),
1648 uid_t guestUID = guestUID, 1649 uid_t guestUID = guestUID,
1649 gid_t guestGID = guestGID) 1650 gid_t guestGID = guestGID,
1651 const std::vector<std::pair<gid_t, gid_t>> extraGIDs = {},
1652 bool haveCapSetGID = false)
1650{ 1653{
1651 writeFile("/proc/" + std::to_string(child) + "/uid_map", 1654 writeFile("/proc/" + std::to_string(child) + "/uid_map",
1652 (format("%d %d 1") % guestUID % hostUID).str()); 1655 (format("%d %d 1") % guestUID % hostUID).str());
1653 1656
1654 writeFile("/proc/" + std::to_string(child) + "/setgroups", "deny"); 1657 if (!haveCapSetGID && !extraGIDs.empty()) {
1658 try {
1659 Strings args = {
1660 std::to_string(child),
1661 std::to_string(guestGID), std::to_string(hostGID), "1"
1662 };
1663 for (auto &pair: extraGIDs) {
1664 args.push_back(std::to_string(pair.second));
1665 args.push_back(std::to_string(pair.first));
1666 args.push_back("1");
1667 }
1668
1669 runProgram("newgidmap", true, args);
1670 printMsg(lvlChatty,
1671 format("mapped %1% extra GIDs into namespace of PID %2%")
1672 % extraGIDs.size() % child);
1673
1674 return;
1675 } catch (const ExecError &e) {
1676 ignoreException();
1677 }
1678 }
1679
1680 if (!haveCapSetGID)
1681 writeFile("/proc/" + std::to_string(child) + "/setgroups", "deny");
1655 1682
1656 writeFile("/proc/" + std::to_string(child) + "/gid_map", 1683 auto content = (format("%d %d 1\n") % guestGID % hostGID).str();
1657 (format("%d %d 1") % guestGID % hostGID).str()); 1684 if (haveCapSetGID) {
1685 for (auto &mapping: extraGIDs) {
1686 content += (format("%d %d 1\n") % mapping.second % mapping.first).str();
1687 }
1688 }
1689 writeFile("/proc/" + std::to_string(child) + "/gid_map", content);
1690}
1691
1692/* Maximum number of supplementary groups per user account. */
1693static const size_t maxGroups = 64;
1694
1695/* Return the ID of the "kvm" group or -1 if it does not exist or is not part
1696 of the current user's supplementary groups. */
1697static gid_t kvmGID()
1698{
1699 struct group *kvm = getgrnam("kvm");
1700 if (kvm == NULL) return -1;
1701
1702 gid_t groups[maxGroups];
1703 int count = getgroups(maxGroups, groups);
1704 if (count < 0) return -1;
1705
1706 for (int i = 0; i < count; i++) {
1707 if (groups[i] == kvm->gr_gid) return kvm->gr_gid;
1708 }
1709
1710 return -1;
1711}
1712
1713/* GID of the "kvm" group in guest user namespaces. */
1714static gid_t guestKVMGID = 40000;
1715
1716static std::vector<std::pair<gid_t, gid_t>> kvmGIDMapping()
1717{
1718 gid_t kvm = kvmGID();
1719 if (kvm == (gid_t) -1)
1720 return {};
1721 else {
1722 std::pair<gid_t, gid_t> mapping(kvm, guestKVMGID);
1723 return { mapping };
1724 }
1658} 1725}
1659 1726
1660#if CHROOT_ENABLED 1727#if CHROOT_ENABLED
@@ -2931,13 +2998,29 @@ void DerivationGoal::startBuilder()
2931 enableRouteLocalnetAction); 2998 enableRouteLocalnetAction);
2932 } 2999 }
2933 3000
3001 if ((ctx.cloneFlags & CLONE_NEWUSER) != 0) {
3002 /* Have the 'lockMounts' phase re-map supplementary GIDs such as
3003 that of the "kvm" group. */
3004 ctx.lockMountsMapAll = true;
3005 }
3006
2934 pid = cloneChild(ctx); 3007 pid = cloneChild(ctx);
2935 3008
2936 if(childSetupSocket >= 0) childSetupSocket.close(); 3009 if(childSetupSocket >= 0) childSetupSocket.close();
2937 3010
2938 if ((ctx.cloneFlags & CLONE_NEWUSER) != 0) { 3011 if ((ctx.cloneFlags & CLONE_NEWUSER) != 0) {
2939 /* Initialize the UID/GID mapping of the builder. */ 3012 /* Initialize the UID/GID mapping of the child process.
2940 initializeUserNamespace(pid); 3013
3014 Try hard to map the "kvm" GID inside the user namespace ("kvm"
3015 is usually the only supplementary group of the 'guix-daemon'
3016 privilege separation user) so that package test suites that
3017 expect to be able to chown to supplementary groups can do so
3018 (without that mapping, attempts to chown to the supplementary
3019 group fail with EINVAL). */
3020 auto extraGIDs = kvmGIDMapping();
3021 initializeUserNamespace(pid,
3022 getuid(), getgid(),
3023 guestUID, guestGID, extraGIDs);
2941 writeFull(parentSetupSocket, (unsigned char*)"go\n", 3); 3024 writeFull(parentSetupSocket, (unsigned char*)"go\n", 3);
2942 } 3025 }
2943 3026
diff --git a/tests/store.scm b/tests/store.scm
index 16dcbf2396d..82fb7a96cea 100644
--- a/tests/store.scm
+++ b/tests/store.scm
@@ -476,6 +476,29 @@
476 (build-derivations %store (list d)) 476 (build-derivations %store (list d))
477 (call-with-input-file (derivation->output-path d) read))) 477 (call-with-input-file (derivation->output-path d) read)))
478 478
479(unless (and (unprivileged-user-namespace-supported?)
480 (false-if-exception
481 (= (stat:gid (stat "/dev/kvm"))
482 (group:gid (getgrnam "kvm"))))
483 (= 1 (status:exit-val (system* "newgidmap"))))
484 (test-skip 1))
485(test-assert "kvm GID is mapped"
486 ;; Ensure that the "kvm" GID is mapped into the build user namespace such
487 ;; that chown'ing a file to that GID works as expected. See
488 ;; <https://issues.guix.gnu.org/77862>.
489 (let ((d (build-expression->derivation
490 %store "chown-to-supplementary-group"
491 `(let ((st (stat "/dev/kvm")))
492 ',(gettimeofday)
493 (pk 'supplementary-groups (getgroups))
494 (pk 'kvm-group (stat:gid st))
495 (unless (member (stat:gid st) (vector->list (getgroups)))
496 (error "supplementary groups lack 'kvm' GID"))
497 (mkdir "test")
498 (chown "test" (getuid) (stat:gid st))
499 (mkdir %output)))))
500 (build-derivations %store (list d))))
501
479(unless (unprivileged-user-namespace-supported?) 502(unless (unprivileged-user-namespace-supported?)
480 (test-skip 1)) 503 (test-skip 1))
481(test-equal "inputs are read-only" 504(test-equal "inputs are read-only"