Skip to content

Commit

Permalink
Open slave pseudoterminal before CLONE_NEWUSER
Browse files Browse the repository at this point in the history
Otherwise, when running as root and user namespaces are enabled,
opening the slave fails with EPERM.

Fixes "opening pseudoterminal slave: Permission denied" followed by a
hang (https://hydra.nixos.org/build/213104244), and "error: getting
sandbox mount namespace: No such file or directory" (#8072), which
happens when the child fails very quickly and consequently reading
/proc/<child>/ns fails.

(cherry picked from commit 16db8dc)
  • Loading branch information
edolstra authored and Ericson2314 committed Feb 12, 2025
1 parent 99c690c commit 91d8838
Showing 1 changed file with 29 additions and 17 deletions.
46 changes: 29 additions & 17 deletions src/libstore/build.cc
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@ static void commonChildInit(int stderrFd)
throw SysError(format("creating a new session"));

/* Dup the write side of the logger pipe into stderr. */
if (dup2(stderrFd, STDERR_FILENO) == -1)
if (stderrFd != -1 && dup2(stderrFd, STDERR_FILENO) == -1)
throw SysError("cannot pipe standard error into log file");

/* Dup stderr to stdout. */
Expand Down Expand Up @@ -2264,6 +2264,27 @@ void DerivationGoal::startBuilder()
if (unlockpt(builderOut.get()))
throw SysError("unlocking pseudoterminal");

/* Open the slave side of the pseudoterminal and use it as stderr. */
auto openSlave = [&]()
{
AutoCloseFD builderOut = open(slaveName.c_str(), O_RDWR | O_NOCTTY);
if (!builderOut)
throw SysError("opening pseudoterminal slave");

// Put the pt into raw mode to prevent \n -> \r\n translation.
struct termios term;
if (tcgetattr(builderOut.get(), &term))
throw SysError("getting pseudoterminal attributes");

cfmakeraw(&term);

if (tcsetattr(builderOut.get(), TCSANOW, &term))
throw SysError("putting pseudoterminal into raw mode");

if (dup2(builderOut.get(), STDERR_FILENO) == -1)
throw SysError("cannot pipe standard error into log file");
};

result.startTime = time(0);

/* Fork a child to build the package. */
Expand Down Expand Up @@ -2318,6 +2339,11 @@ void DerivationGoal::startBuilder()
Pid helper = startProcess([&]() {
sendPid.readSide.close();

/* We need to open the slave early, before
CLONE_NEWUSER. Otherwise we get EPERM when running as
root. */
openSlave();

/* Drop additional groups here because we can't do it
after we've created the new user namespace. FIXME:
this means that if we're not root in the parent
Expand Down Expand Up @@ -2410,6 +2436,7 @@ void DerivationGoal::startBuilder()
fallback:
options.allowVfork = !buildUser && !drv->isBuiltin();
pid = startProcess([&]() {
openSlave();
runChild();
}, options);
}
Expand Down Expand Up @@ -2723,22 +2750,7 @@ void DerivationGoal::runChild()

try { /* child */

/* Open the slave side of the pseudoterminal. */
AutoCloseFD builderOut = open(slaveName.c_str(), O_RDWR | O_NOCTTY);
if (!builderOut)
throw SysError("opening pseudoterminal slave");

// Put the pt into raw mode to prevent \n -> \r\n translation.
struct termios term;
if (tcgetattr(builderOut.get(), &term))
throw SysError("getting pseudoterminal attributes");

cfmakeraw(&term);

if (tcsetattr(builderOut.get(), TCSANOW, &term))
throw SysError("putting pseudoterminal into raw mode");

commonChildInit(builderOut.get());
commonChildInit(-1);

try {
setupSeccomp();
Expand Down

0 comments on commit 91d8838

Please sign in to comment.