From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp2 ([2001:41d0:2:c151::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms11 with LMTPS id OLryGvY2U2DkTQAA0tVLHw (envelope-from ) for ; Thu, 18 Mar 2021 11:18:14 +0000 Received: from aspmx2.migadu.com ([2001:41d0:2:c151::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp2 with LMTPS id wGC0FvY2U2D4IwAAB5/wlQ (envelope-from ) for ; Thu, 18 Mar 2021 11:18:14 +0000 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by aspmx2.migadu.com (Postfix) with ESMTPS id B84EF9EFC for ; Thu, 18 Mar 2021 12:18:13 +0100 (CET) Received: from localhost ([::1]:56626 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1lMqfM-0002VJ-MK for larch@yhetil.org; Thu, 18 Mar 2021 07:18:12 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:41326) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1lMqfD-0002Sh-3x for bug-guix@gnu.org; Thu, 18 Mar 2021 07:18:03 -0400 Received: from debbugs.gnu.org ([209.51.188.43]:33692) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1lMqfC-0003Ix-S3 for bug-guix@gnu.org; Thu, 18 Mar 2021 07:18:02 -0400 Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1lMqfC-0002X1-LO; Thu, 18 Mar 2021 07:18:02 -0400 X-Loop: help-debbugs@gnu.org Subject: bug#47229: Local privilege escalation via guix-daemon and =?UTF-8?Q?=E2=80=98--keep-failed=E2=80=99?= Resent-From: Ludovic =?UTF-8?Q?Court=C3=A8s?= Original-Sender: "Debbugs-submit" Resent-CC: leo@famulari.name, bug-guix@gnu.org Resent-Date: Thu, 18 Mar 2021 11:18:02 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: report 47229 X-GNU-PR-Package: guix X-GNU-PR-Keywords: To: 47229@debbugs.gnu.org X-Debbugs-Original-To: X-Debbugs-Original-Xcc: Leo Famulari Received: via spool by submit@debbugs.gnu.org id=B.16160662459670 (code B ref -1); Thu, 18 Mar 2021 11:18:02 +0000 Received: (at submit) by debbugs.gnu.org; 18 Mar 2021 11:17:25 +0000 Received: from localhost ([127.0.0.1]:45234 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1lMqea-0002Vt-EG for submit@debbugs.gnu.org; Thu, 18 Mar 2021 07:17:24 -0400 Received: from lists.gnu.org ([209.51.188.17]:56814) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1lMqeV-0002Ve-9c for submit@debbugs.gnu.org; Thu, 18 Mar 2021 07:17:23 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:41040) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1lMqeT-0001tS-Qx for bug-guix@gnu.org; Thu, 18 Mar 2021 07:17:18 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]:52987) by eggs.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1lMqeT-0002vn-Bs for bug-guix@gnu.org; Thu, 18 Mar 2021 07:17:17 -0400 Received: from [2a01:e0a:1d:7270:af76:b9b:ca24:c465] (port=51680 helo=ribbon) by fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_256_CBC_SHA1:256) (Exim 4.82) (envelope-from ) id 1lMqeS-0002A3-PW for bug-guix@gnu.org; Thu, 18 Mar 2021 07:17:17 -0400 From: Ludovic =?UTF-8?Q?Court=C3=A8s?= X-URL: http://www.fdn.fr/~lcourtes/ X-Revolutionary-Date: 28 =?UTF-8?Q?Vent=C3=B4se?= an 229 de la =?UTF-8?Q?R=C3=A9volution?= X-PGP-Key-ID: 0x090B11993D9AEBB5 X-PGP-Key: http://www.fdn.fr/~lcourtes/ludovic.asc X-PGP-Fingerprint: 3CE4 6455 8A84 FDC6 9DB4 0CFB 090B 1199 3D9A EBB5 X-OS: x86_64-pc-linux-gnu Date: Thu, 18 Mar 2021 12:17:15 +0100 Message-ID: <87lfaksock.fsf@gnu.org> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/27.1 (gnu/linux) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: bug-guix@gnu.org List-Id: Bug reports for GNU Guix List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: bug-guix-bounces+larch=yhetil.org@gnu.org Sender: "bug-Guix" X-Migadu-Flow: FLOW_IN ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=yhetil.org; s=key1; t=1616066293; h=from:from:sender:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:mime-version:mime-version: content-type:content-type:resent-cc:resent-from:resent-sender: resent-message-id:list-id:list-help:list-unsubscribe:list-subscribe: list-post; bh=q7XTjwymCHUDdt9JAfJzlFVl2l+CVv9UDEersBcl2dc=; b=kMvo2r8ZRAxIiKT2OBwA2F9BytkjPdL5UFNIfhjMpITcbNH76LL1tW61PjzAq7alIJ+yjW 5HgfxFUqoYruZmUQ+tHB3ygfXwgFlPhYdsIFPieQbeVF73DDTCpB3P/+b+i0kDwPZlP1au lrN09hRRpQpv/uuudfzcXPZtR6Bjim9ENCKTDukpkMT1tFVrxfS6byugBLNhlFKpv4Q3Ds cYcG9+BRqXqKQiUb34XoiWf7VGxWyzBJS9aA19Q4Uqak0p/Sc+I7uP+ZO4iWjhhhYoodGI ZcIMzrQQP4HK3A3UAXWzK6JKrqTx80fhhUEnX2PzRqEuHGNt6HcbRa+cF2Cejw== ARC-Seal: i=1; s=key1; d=yhetil.org; t=1616066293; a=rsa-sha256; cv=none; b=HslbA/cW/M/j6F7Em+dQGUe0GgzvCxf9GhQVmfusr9JWZIE3wWfpXHUSfcqTIdDJj3L9Ce zHF4HGS7hBiDGVSCVudESBg3fA9gw0QO2PsbkS0n17M4s7aH5v9TN7Wi9J4GGNdIMF/tL2 XGEpuv4OpBLRByZkzFTk84/w0v/D6UhHioD/0NZKtfTnNWfgSqViKbT6XT3aURxIR+TdFU JvOB5ZfWhXoFLJAzBoLEnFffB53w59UH5hJ1hP/pMRHush2UzdIINyZvHurusSQBYjMevT jBNznDyq5sEgzkpR/JuhPHo1od5t7A70cA3s36oLDk7Tzs/aPnPn6QeYkd2wdA== ARC-Authentication-Results: i=1; aspmx2.migadu.com; dkim=none; spf=pass (aspmx2.migadu.com: domain of bug-guix-bounces@gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=bug-guix-bounces@gnu.org X-Migadu-Spam-Score: -2.90 Authentication-Results: aspmx2.migadu.com; dkim=none; dmarc=pass (policy=none) header.from=gnu.org; spf=pass (aspmx2.migadu.com: domain of bug-guix-bounces@gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=bug-guix-bounces@gnu.org X-Migadu-Queue-Id: B84EF9EFC X-Spam-Score: -2.90 X-Migadu-Scanner: scn0.migadu.com X-TUID: 2WdlFMQ/g0Xg --=-=-= Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable A security vulnerability that can lead to local privilege escalation has been found in =E2=80=99guix-daemon=E2=80=99. It affects multi-user setups = in which =E2=80=99guix-daemon=E2=80=99 runs locally. It does not affect multi-user setups where =E2=80=98guix-daemon=E2=80=99 ru= ns on a separate machine and is accessed over the network, via =E2=80=98GUIX_DAEMON_SOCKET=E2=80=99, as is customary on cluster setups. M= achines where the Linux =E2=80=9Cprotected hardlink=E2=80=9D[*] feature is enabled, which= is common, are also unaffected=E2=80=94this is the case when the contents of /proc/sys/fs/protected_hardlinks are 1. [*] https://www.kernel.org/doc/Documentation/sysctl/fs.txt Vulnerability ~~~~~~~~~~~~~ The attack consists in having an unprivileged user spawn a build process, for instance with =E2=80=98guix build=E2=80=99, that makes its bui= ld directory world-writable. The user then creates a hardlink within the build directory to a root-owned file from outside of the build directory, such as =E2=80=98/etc/shadow=E2=80=99. If the user passed the =E2=80=98--keep-f= ailed=E2=80=99 option and the build eventually fails, the daemon changes ownership of the whole build tree, including the hardlink, to the user. At that point, the user has write access to the target file. Fix ~~~ The fix (patch attached) consists in adding a root-owned =E2=80=9Cwrapper= =E2=80=9D directory in which the build directory itself is located. If the user passed the =E2=80=98--keep-failed=E2=80=99 option and the build fails, the = =E2=80=98guix-daemon=E2=80=99 first changes ownership of the build directory, and then, in two stages, moves the build directory into the location where users expect to find failed builds, roughly like this: 1. chown -R USER /tmp/guix-build-foo.drv-0/top 2. mv /tmp/guix-build-foo.drv-0{,.pivot} 3. mv /tmp/guix-build-foo.drv-0.pivot/top /tmp/guix-build-foo.drv-0 In step #1, /tmp/guix-build-foo.drv-0 remains root-owned, with permissions of #o700. Thus, only root can change directory into it or into =E2=80=98top=E2=80=99. Likewise in step #2. The build tree becomes accessible to the user once step #3 has succeeded, not before. These steps are performed after the package build scripts have stopped running. Additionally, the patch at enables protected hardlinks and symlinks by default on Guix System, which will protect against this class of vulnerability from now on. Credit ~~~~~~ We are grateful to Nathan Nye of WhiteBeam Security for reporting this bug and discussing fixes with us! Timeline ~~~~~~~~ We learned about this bug on the private guix-security@gnu.org list on February 7th, and discussed and prepared fixes in the interim. Ludo=E2=80=99 & Leo Famulari. --=-=-= Content-Type: text/x-patch Content-Disposition: inline diff --git a/nix/libstore/build.cc b/nix/libstore/build.cc index 20d83fea4a..4f486f0822 100644 --- a/nix/libstore/build.cc +++ b/nix/libstore/build.cc @@ -1621,6 +1621,24 @@ void DerivationGoal::startBuilder() auto drvName = storePathToName(drvPath); tmpDir = createTempDir("", "guix-build-" + drvName, false, false, 0700); + if (useChroot) { + /* Make the build directory seen by the build process a sub-directory. + That way, "/tmp/guix-build-foo.drv-0" is root-owned, and thus its + permissions cannot be changed by the build process, while + "/tmp/guix-build-foo.drv-0/top" is owned by the build user. This + cannot be done when !useChroot because then $NIX_BUILD_TOP would + be inaccessible to the build user by its full file name. + + If the build user could make the build directory world-writable, + then an attacker could create in it a hardlink to a root-owned file + such as /etc/shadow. If 'keepFailed' is true, the daemon would + then chown that hardlink to the user, giving them write access to + that file. */ + tmpDir += "/top"; + if (mkdir(tmpDir.c_str(), 0700) == 1) + throw SysError("creating top-level build directory"); + } + /* In a sandbox, for determinism, always use the same temporary directory. */ tmpDirInSandbox = useChroot ? canonPath("/tmp", true) + "/guix-build-" + drvName + "-0" : tmpDir; @@ -2626,20 +2644,41 @@ static void _chown(const Path & path, uid_t uid, gid_t gid) void DerivationGoal::deleteTmpDir(bool force) { if (tmpDir != "") { + // When useChroot is true, tmpDir looks like + // "/tmp/guix-build-foo.drv-0/top". Its parent is root-owned. + string top; + if (useChroot) { + if (baseNameOf(tmpDir) != "top") abort(); + top = dirOf(tmpDir); + } else top = tmpDir; + if (settings.keepFailed && !force) { printMsg(lvlError, format("note: keeping build directory `%2%'") - % drvPath % tmpDir); + % drvPath % top); chmod(tmpDir.c_str(), 0755); + // Change the ownership if clientUid is set. Never change the // ownership or the group to "root" for security reasons. if (settings.clientUid != (uid_t) -1 && settings.clientUid != 0) { _chown(tmpDir, settings.clientUid, settings.clientGid != 0 ? settings.clientGid : -1); + + if (top != tmpDir) { + // Rename tmpDir to its parent, with an intermediate step. + string pivot = top + ".pivot"; + if (rename(top.c_str(), pivot.c_str()) == -1) + throw SysError("pivoting failed build tree"); + if (rename((pivot + "/top").c_str(), top.c_str()) == -1) + throw SysError("renaming failed build tree"); + rmdir(pivot.c_str()); + } } } - else + else { deletePath(tmpDir); + if (top != tmpDir) rmdir(dirOf(tmpDir).c_str()); + } tmpDir = ""; } } --=-=-=--