diff options
| -rw-r--r-- | nix/libstore/build.cc | 95 | ||||
| -rw-r--r-- | tests/store.scm | 23 |
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. */ | ||
| 1693 | static 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. */ | ||
| 1697 | static 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. */ | ||
| 1714 | static gid_t guestKVMGID = 40000; | ||
| 1715 | |||
| 1716 | static 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" |
