Skip to content

Commit

Permalink
Kill builds when we get EOF on the log FD
Browse files Browse the repository at this point in the history
This closes a long-time bug that allowed builds to hang Nix
indefinitely (regardless of timeouts) simply by doing

  exec > /dev/null 2>&1; while true; do true; done

Now, on EOF, we just send SIGKILL to the child to make sure it's
really gone.
  • Loading branch information
edolstra committed Jan 19, 2017
1 parent 63e10b4 commit 21948de
Show file tree
Hide file tree
Showing 6 changed files with 40 additions and 40 deletions.
2 changes: 1 addition & 1 deletion src/libmain/shared.cc
Expand Up @@ -333,7 +333,7 @@ RunPager::~RunPager()
if (pid != -1) { if (pid != -1) {
std::cout.flush(); std::cout.flush();
close(STDOUT_FILENO); close(STDOUT_FILENO);
pid.wait(true); pid.wait();
} }
} catch (...) { } catch (...) {
ignoreException(); ignoreException();
Expand Down
21 changes: 10 additions & 11 deletions src/libstore/build.cc
Expand Up @@ -646,7 +646,7 @@ HookInstance::~HookInstance()
{ {
try { try {
toHook.writeSide = -1; toHook.writeSide = -1;
pid.kill(true); if (pid != -1) pid.kill(true);
} catch (...) { } catch (...) {
ignoreException(); ignoreException();
} }
Expand Down Expand Up @@ -960,7 +960,7 @@ void DerivationGoal::killChild()
child. */ child. */
::kill(-pid, SIGKILL); /* ignore the result */ ::kill(-pid, SIGKILL); /* ignore the result */
buildUser.kill(); buildUser.kill();
pid.wait(true); pid.wait();
} else } else
pid.kill(); pid.kill();


Expand Down Expand Up @@ -1416,11 +1416,9 @@ void DerivationGoal::buildDone()


/* Since we got an EOF on the logger pipe, the builder is presumed /* Since we got an EOF on the logger pipe, the builder is presumed
to have terminated. In fact, the builder could also have to have terminated. In fact, the builder could also have
simply have closed its end of the pipe --- just don't do that simply have closed its end of the pipe, so just to be sure,
:-) */ kill it. */
/* !!! this could block! security problem! solution: kill the int status = hook ? hook->pid.kill(true) : pid.kill(true);
child */
int status = hook ? hook->pid.wait(true) : pid.wait(true);


debug(format("builder process for ‘%1%’ finished") % drvPath); debug(format("builder process for ‘%1%’ finished") % drvPath);


Expand Down Expand Up @@ -2112,6 +2110,8 @@ void DerivationGoal::startBuilder()
result.startTime = time(0); result.startTime = time(0);


/* Fork a child to build the package. */ /* Fork a child to build the package. */
ProcessOptions options;

#if __linux__ #if __linux__
if (useChroot) { if (useChroot) {
/* Set up private namespaces for the build: /* Set up private namespaces for the build:
Expand Down Expand Up @@ -2153,7 +2153,6 @@ void DerivationGoal::startBuilder()


userNamespaceSync.create(); userNamespaceSync.create();


ProcessOptions options;
options.allowVfork = false; options.allowVfork = false;


Pid helper = startProcess([&]() { Pid helper = startProcess([&]() {
Expand Down Expand Up @@ -2189,7 +2188,7 @@ void DerivationGoal::startBuilder()
_exit(0); _exit(0);
}, options); }, options);


if (helper.wait(true) != 0) if (helper.wait() != 0)
throw Error("unable to start build process"); throw Error("unable to start build process");


userNamespaceSync.readSide = -1; userNamespaceSync.readSide = -1;
Expand Down Expand Up @@ -2220,7 +2219,6 @@ void DerivationGoal::startBuilder()
} else } else
#endif #endif
{ {
ProcessOptions options;
options.allowVfork = !buildUser.enabled() && !drv->isBuiltin(); options.allowVfork = !buildUser.enabled() && !drv->isBuiltin();
pid = startProcess([&]() { pid = startProcess([&]() {
runChild(); runChild();
Expand Down Expand Up @@ -3702,7 +3700,8 @@ void Worker::waitForInput()


auto after = steady_time_point::clock::now(); auto after = steady_time_point::clock::now();


/* Process all available file descriptors. */ /* Process all available file descriptors. FIXME: this is
O(children * fds). */
decltype(children)::iterator i; decltype(children)::iterator i;
for (auto j = children.begin(); j != children.end(); j = i) { for (auto j = children.begin(); j != children.end(); j = i) {
i = std::next(j); i = std::next(j);
Expand Down
33 changes: 10 additions & 23 deletions src/libutil/util.cc
Expand Up @@ -648,26 +648,25 @@ void Pipe::create()




Pid::Pid() Pid::Pid()
: pid(-1), separatePG(false), killSignal(SIGKILL)
{ {
} }




Pid::Pid(pid_t pid) Pid::Pid(pid_t pid)
: pid(pid), separatePG(false), killSignal(SIGKILL) : pid(pid)
{ {
} }




Pid::~Pid() Pid::~Pid()
{ {
kill(); if (pid != -1) kill();
} }




void Pid::operator =(pid_t pid) void Pid::operator =(pid_t pid)
{ {
if (this->pid != pid) kill(); if (this->pid != -1 && this->pid != pid) kill();
this->pid = pid; this->pid = pid;
killSignal = SIGKILL; // reset signal to default killSignal = SIGKILL; // reset signal to default
} }
Expand All @@ -679,9 +678,9 @@ Pid::operator pid_t()
} }




void Pid::kill(bool quiet) int Pid::kill(bool quiet)
{ {
if (pid == -1 || pid == 0) return; assert(pid != -1);


if (!quiet) if (!quiet)
printError(format("killing process %1%") % pid); printError(format("killing process %1%") % pid);
Expand All @@ -692,32 +691,20 @@ void Pid::kill(bool quiet)
if (::kill(separatePG ? -pid : pid, killSignal) != 0) if (::kill(separatePG ? -pid : pid, killSignal) != 0)
printError((SysError(format("killing process %1%") % pid).msg())); printError((SysError(format("killing process %1%") % pid).msg()));


/* Wait until the child dies, disregarding the exit status. */ return wait();
int status;
while (waitpid(pid, &status, 0) == -1) {
checkInterrupt();
if (errno != EINTR) {
printError(
(SysError(format("waiting for process %1%") % pid).msg()));
break;
}
}

pid = -1;
} }




int Pid::wait(bool block) int Pid::wait()
{ {
assert(pid != -1); assert(pid != -1);
while (1) { while (1) {
int status; int status;
int res = waitpid(pid, &status, block ? 0 : WNOHANG); int res = waitpid(pid, &status, 0);
if (res == pid) { if (res == pid) {
pid = -1; pid = -1;
return status; return status;
} }
if (res == 0 && !block) return -1;
if (errno != EINTR) if (errno != EINTR)
throw SysError("cannot get child exit status"); throw SysError("cannot get child exit status");
checkInterrupt(); checkInterrupt();
Expand Down Expand Up @@ -782,7 +769,7 @@ void killUser(uid_t uid)
_exit(0); _exit(0);
}, options); }, options);


int status = pid.wait(true); int status = pid.wait();
if (status != 0) if (status != 0)
throw Error(format("cannot kill processes for uid ‘%1%’: %2%") % uid % statusToString(status)); throw Error(format("cannot kill processes for uid ‘%1%’: %2%") % uid % statusToString(status));


Expand Down Expand Up @@ -893,7 +880,7 @@ string runProgram(Path program, bool searchPath, const Strings & args,
string result = drainFD(out.readSide.get()); string result = drainFD(out.readSide.get());


/* Wait for the child to finish. */ /* Wait for the child to finish. */
int status = pid.wait(true); int status = pid.wait();
if (!statusOk(status)) if (!statusOk(status))
throw ExecError(status, format("program ‘%1%’ %2%") throw ExecError(status, format("program ‘%1%’ %2%")
% program % statusToString(status)); % program % statusToString(status));
Expand Down
11 changes: 6 additions & 5 deletions src/libutil/util.hh
Expand Up @@ -192,17 +192,18 @@ typedef std::unique_ptr<DIR, DIRDeleter> AutoCloseDir;


class Pid class Pid
{ {
pid_t pid; pid_t pid = -1;
bool separatePG; bool separatePG = false;
int killSignal; int killSignal = SIGKILL;
public: public:
Pid(); Pid();
Pid(pid_t pid); Pid(pid_t pid);
~Pid(); ~Pid();
void operator =(pid_t pid); void operator =(pid_t pid);
operator pid_t(); operator pid_t();
void kill(bool quiet = false); int kill(bool quiet = false);
int wait(bool block); int wait();

void setSeparatePG(bool separatePG); void setSeparatePG(bool separatePG);
void setKillSignal(int signal); void setKillSignal(int signal);
pid_t release(); pid_t release();
Expand Down
8 changes: 8 additions & 0 deletions tests/timeout.nix
Expand Up @@ -17,4 +17,12 @@ with import ./config.nix;
''; '';
}; };


closeLog = mkDerivation {
name = "silent";
buildCommand = ''
exec > /dev/null 2>&1
sleep 1000000000
'';
};

} }
5 changes: 5 additions & 0 deletions tests/timeout.sh
Expand Up @@ -24,3 +24,8 @@ if nix-build timeout.nix -A silent --max-silent-time 2; then
echo "build should have failed" echo "build should have failed"
exit 1 exit 1
fi fi

if nix-build timeout.nix -A closeLog; then
echo "build should have failed"
exit 1
fi

0 comments on commit 21948de

Please sign in to comment.