Skip to content

Commit 21948de

Browse files
committed
Kill builds when we get EOF on the log FD
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.
1 parent 63e10b4 commit 21948de

File tree

6 files changed

+40
-40
lines changed

6 files changed

+40
-40
lines changed

src/libmain/shared.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ RunPager::~RunPager()
333333
if (pid != -1) {
334334
std::cout.flush();
335335
close(STDOUT_FILENO);
336-
pid.wait(true);
336+
pid.wait();
337337
}
338338
} catch (...) {
339339
ignoreException();

src/libstore/build.cc

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -646,7 +646,7 @@ HookInstance::~HookInstance()
646646
{
647647
try {
648648
toHook.writeSide = -1;
649-
pid.kill(true);
649+
if (pid != -1) pid.kill(true);
650650
} catch (...) {
651651
ignoreException();
652652
}
@@ -960,7 +960,7 @@ void DerivationGoal::killChild()
960960
child. */
961961
::kill(-pid, SIGKILL); /* ignore the result */
962962
buildUser.kill();
963-
pid.wait(true);
963+
pid.wait();
964964
} else
965965
pid.kill();
966966

@@ -1416,11 +1416,9 @@ void DerivationGoal::buildDone()
14161416

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

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

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

21142112
/* Fork a child to build the package. */
2113+
ProcessOptions options;
2114+
21152115
#if __linux__
21162116
if (useChroot) {
21172117
/* Set up private namespaces for the build:
@@ -2153,7 +2153,6 @@ void DerivationGoal::startBuilder()
21532153

21542154
userNamespaceSync.create();
21552155

2156-
ProcessOptions options;
21572156
options.allowVfork = false;
21582157

21592158
Pid helper = startProcess([&]() {
@@ -2189,7 +2188,7 @@ void DerivationGoal::startBuilder()
21892188
_exit(0);
21902189
}, options);
21912190

2192-
if (helper.wait(true) != 0)
2191+
if (helper.wait() != 0)
21932192
throw Error("unable to start build process");
21942193

21952194
userNamespaceSync.readSide = -1;
@@ -2220,7 +2219,6 @@ void DerivationGoal::startBuilder()
22202219
} else
22212220
#endif
22222221
{
2223-
ProcessOptions options;
22242222
options.allowVfork = !buildUser.enabled() && !drv->isBuiltin();
22252223
pid = startProcess([&]() {
22262224
runChild();
@@ -3702,7 +3700,8 @@ void Worker::waitForInput()
37023700

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

3705-
/* Process all available file descriptors. */
3703+
/* Process all available file descriptors. FIXME: this is
3704+
O(children * fds). */
37063705
decltype(children)::iterator i;
37073706
for (auto j = children.begin(); j != children.end(); j = i) {
37083707
i = std::next(j);

src/libutil/util.cc

Lines changed: 10 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -648,26 +648,25 @@ void Pipe::create()
648648

649649

650650
Pid::Pid()
651-
: pid(-1), separatePG(false), killSignal(SIGKILL)
652651
{
653652
}
654653

655654

656655
Pid::Pid(pid_t pid)
657-
: pid(pid), separatePG(false), killSignal(SIGKILL)
656+
: pid(pid)
658657
{
659658
}
660659

661660

662661
Pid::~Pid()
663662
{
664-
kill();
663+
if (pid != -1) kill();
665664
}
666665

667666

668667
void Pid::operator =(pid_t pid)
669668
{
670-
if (this->pid != pid) kill();
669+
if (this->pid != -1 && this->pid != pid) kill();
671670
this->pid = pid;
672671
killSignal = SIGKILL; // reset signal to default
673672
}
@@ -679,9 +678,9 @@ Pid::operator pid_t()
679678
}
680679

681680

682-
void Pid::kill(bool quiet)
681+
int Pid::kill(bool quiet)
683682
{
684-
if (pid == -1 || pid == 0) return;
683+
assert(pid != -1);
685684

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

695-
/* Wait until the child dies, disregarding the exit status. */
696-
int status;
697-
while (waitpid(pid, &status, 0) == -1) {
698-
checkInterrupt();
699-
if (errno != EINTR) {
700-
printError(
701-
(SysError(format("waiting for process %1%") % pid).msg()));
702-
break;
703-
}
704-
}
705-
706-
pid = -1;
694+
return wait();
707695
}
708696

709697

710-
int Pid::wait(bool block)
698+
int Pid::wait()
711699
{
712700
assert(pid != -1);
713701
while (1) {
714702
int status;
715-
int res = waitpid(pid, &status, block ? 0 : WNOHANG);
703+
int res = waitpid(pid, &status, 0);
716704
if (res == pid) {
717705
pid = -1;
718706
return status;
719707
}
720-
if (res == 0 && !block) return -1;
721708
if (errno != EINTR)
722709
throw SysError("cannot get child exit status");
723710
checkInterrupt();
@@ -782,7 +769,7 @@ void killUser(uid_t uid)
782769
_exit(0);
783770
}, options);
784771

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

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

895882
/* Wait for the child to finish. */
896-
int status = pid.wait(true);
883+
int status = pid.wait();
897884
if (!statusOk(status))
898885
throw ExecError(status, format("program ‘%1%’ %2%")
899886
% program % statusToString(status));

src/libutil/util.hh

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -192,17 +192,18 @@ typedef std::unique_ptr<DIR, DIRDeleter> AutoCloseDir;
192192

193193
class Pid
194194
{
195-
pid_t pid;
196-
bool separatePG;
197-
int killSignal;
195+
pid_t pid = -1;
196+
bool separatePG = false;
197+
int killSignal = SIGKILL;
198198
public:
199199
Pid();
200200
Pid(pid_t pid);
201201
~Pid();
202202
void operator =(pid_t pid);
203203
operator pid_t();
204-
void kill(bool quiet = false);
205-
int wait(bool block);
204+
int kill(bool quiet = false);
205+
int wait();
206+
206207
void setSeparatePG(bool separatePG);
207208
void setKillSignal(int signal);
208209
pid_t release();

tests/timeout.nix

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,12 @@ with import ./config.nix;
1717
'';
1818
};
1919

20+
closeLog = mkDerivation {
21+
name = "silent";
22+
buildCommand = ''
23+
exec > /dev/null 2>&1
24+
sleep 1000000000
25+
'';
26+
};
27+
2028
}

tests/timeout.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,8 @@ if nix-build timeout.nix -A silent --max-silent-time 2; then
2424
echo "build should have failed"
2525
exit 1
2626
fi
27+
28+
if nix-build timeout.nix -A closeLog; then
29+
echo "build should have failed"
30+
exit 1
31+
fi

0 commit comments

Comments
 (0)