libutil: give Pid proper resource semantics

copy-constructing or assigning from pid_t can easily lead to duplicate
Pid instances for the same process if a pid_t was used carelessly, and
Pid itself was copy-constructible. both could cause surprising results
such as killing processes twice (which could become very problemantic,
but luckily modern systems don't reuse PIDs all that quickly), or more
than one piece of the code believing it owns a process when neither do

Change-Id: Ifea7445f84200b34c1a1d0acc2cdffe0f01e20c6
This commit is contained in:
eldritch horrors 2024-04-05 21:02:04 +02:00
parent b43a2e84c4
commit 3d155fc509
8 changed files with 37 additions and 40 deletions

View file

@ -354,7 +354,7 @@ RunPager::RunPager()
Pipe toPager; Pipe toPager;
toPager.create(); toPager.create();
pid = startProcess([&]() { pid = Pid{startProcess([&]() {
if (dup2(toPager.readSide.get(), STDIN_FILENO) == -1) if (dup2(toPager.readSide.get(), STDIN_FILENO) == -1)
throw SysError("dupping stdin"); throw SysError("dupping stdin");
if (!getenv("LESS")) if (!getenv("LESS"))
@ -366,7 +366,7 @@ RunPager::RunPager()
execlp("less", "less", nullptr); execlp("less", "less", nullptr);
execlp("more", "more", nullptr); execlp("more", "more", nullptr);
throw SysError("executing '%1%'", pager); throw SysError("executing '%1%'", pager);
}); })};
pid.setKillSignal(SIGINT); pid.setKillSignal(SIGINT);
std_out = fcntl(STDOUT_FILENO, F_DUPFD_CLOEXEC, 0); std_out = fcntl(STDOUT_FILENO, F_DUPFD_CLOEXEC, 0);

View file

@ -35,7 +35,7 @@ HookInstance::HookInstance()
builderOut.create(); builderOut.create();
/* Fork the hook. */ /* Fork the hook. */
pid = startProcess([&]() { pid = Pid{startProcess([&]() {
if (dup2(fromHook.writeSide.get(), STDERR_FILENO) == -1) if (dup2(fromHook.writeSide.get(), STDERR_FILENO) == -1)
throw SysError("cannot pipe standard error into log file"); throw SysError("cannot pipe standard error into log file");
@ -60,7 +60,7 @@ HookInstance::HookInstance()
execv(buildHook.c_str(), stringsToCharPtrs(args).data()); execv(buildHook.c_str(), stringsToCharPtrs(args).data());
throw SysError("executing '%s'", buildHook); throw SysError("executing '%s'", buildHook);
}); })};
pid.setSeparatePG(true); pid.setSeparatePG(true);
fromHook.writeSide.reset(); fromHook.writeSide.reset();

View file

@ -906,7 +906,7 @@ void LocalDerivationGoal::startBuilder()
Pipe sendPid; Pipe sendPid;
sendPid.create(); sendPid.create();
Pid helper = startProcess([&]() { Pid helper{startProcess([&]() {
sendPid.readSide.close(); sendPid.readSide.close();
/* We need to open the slave early, before /* We need to open the slave early, before
@ -934,7 +934,7 @@ void LocalDerivationGoal::startBuilder()
writeFull(sendPid.writeSide.get(), fmt("%d\n", child)); writeFull(sendPid.writeSide.get(), fmt("%d\n", child));
_exit(0); _exit(0);
}); })};
sendPid.writeSide.close(); sendPid.writeSide.close();
@ -951,7 +951,7 @@ void LocalDerivationGoal::startBuilder()
auto ss = tokenizeString<std::vector<std::string>>(readLine(sendPid.readSide.get())); auto ss = tokenizeString<std::vector<std::string>>(readLine(sendPid.readSide.get()));
assert(ss.size() == 1); assert(ss.size() == 1);
pid = string2Int<pid_t>(ss[0]).value(); pid = Pid{string2Int<pid_t>(ss[0]).value()};
if (usingUserNamespace) { if (usingUserNamespace) {
/* Set the UID/GID mapping of the builder's user namespace /* Set the UID/GID mapping of the builder's user namespace
@ -1010,10 +1010,10 @@ void LocalDerivationGoal::startBuilder()
} else } else
#endif #endif
{ {
pid = startProcess([&]() { pid = Pid{startProcess([&]() {
openSlave(); openSlave();
runChild(); runChild();
}); })};
} }
/* parent */ /* parent */

View file

@ -70,7 +70,7 @@ std::unique_ptr<SSHMaster::Connection> SSHMaster::startCommand(const std::string
} }
Finally cleanup = [&]() { logger->resume(); }; Finally cleanup = [&]() { logger->resume(); };
conn->sshPid = startProcess([&]() { conn->sshPid = Pid{startProcess([&]() {
restoreProcessContext(); restoreProcessContext();
close(in.writeSide.get()); close(in.writeSide.get());
@ -99,7 +99,7 @@ std::unique_ptr<SSHMaster::Connection> SSHMaster::startCommand(const std::string
// could not exec ssh/bash // could not exec ssh/bash
throw SysError("unable to execute '%s'", args.front()); throw SysError("unable to execute '%s'", args.front());
}, options); }, options)};
in.readSide.reset(); in.readSide.reset();
@ -147,7 +147,7 @@ Path SSHMaster::startMaster()
if (isMasterRunning()) if (isMasterRunning())
return state->socketPath; return state->socketPath;
state->sshMaster = startProcess([&]() { state->sshMaster = Pid{startProcess([&]() {
restoreProcessContext(); restoreProcessContext();
close(out.readSide.get()); close(out.readSide.get());
@ -160,7 +160,7 @@ Path SSHMaster::startMaster()
execvp(args.begin()->c_str(), stringsToCharPtrs(args).data()); execvp(args.begin()->c_str(), stringsToCharPtrs(args).data());
throw SysError("unable to execute '%s'", args.front()); throw SysError("unable to execute '%s'", args.front());
}, options); }, options)};
out.writeSide.reset(); out.writeSide.reset();

View file

@ -94,12 +94,7 @@ bool userNamespacesSupported()
static auto res = [&]() -> bool static auto res = [&]() -> bool
{ {
try { try {
Pid pid = startProcess([&]() Pid pid{startProcess([&]() { _exit(0); }, {.cloneFlags = CLONE_NEWUSER})};
{
_exit(0);
}, {
.cloneFlags = CLONE_NEWUSER
});
auto r = pid.wait(); auto r = pid.wait();
assert(!r); assert(!r);
@ -120,8 +115,7 @@ bool mountAndPidNamespacesSupported()
{ {
try { try {
Pid pid = startProcess([&]() Pid pid{startProcess([&]() {
{
/* Make sure we don't remount the parent's /proc. */ /* Make sure we don't remount the parent's /proc. */
if (mount(0, "/", 0, MS_PRIVATE | MS_REC, 0) == -1) if (mount(0, "/", 0, MS_PRIVATE | MS_REC, 0) == -1)
_exit(1); _exit(1);
@ -136,7 +130,7 @@ bool mountAndPidNamespacesSupported()
_exit(0); _exit(0);
}, { }, {
.cloneFlags = CLONE_NEWNS | CLONE_NEWPID | (userNamespacesSupported() ? CLONE_NEWUSER : 0) .cloneFlags = CLONE_NEWNS | CLONE_NEWPID | (userNamespacesSupported() ? CLONE_NEWUSER : 0)
}); })};
if (pid.wait()) { if (pid.wait()) {
debug("PID namespaces do not work on this system: cannot remount /proc"); debug("PID namespaces do not work on this system: cannot remount /proc");

View file

@ -35,9 +35,19 @@ Pid::Pid()
} }
Pid::Pid(pid_t pid) Pid::Pid(Pid && other) : pid(other.pid), separatePG(other.separatePG), killSignal(other.killSignal)
: pid(pid)
{ {
other.pid = -1;
}
Pid & Pid::operator=(Pid && other)
{
Pid tmp(std::move(other));
std::swap(pid, tmp.pid);
std::swap(separatePG, tmp.separatePG);
std::swap(killSignal, tmp.killSignal);
return *this;
} }
@ -47,14 +57,6 @@ Pid::~Pid() noexcept(false)
} }
void Pid::operator =(pid_t pid)
{
if (this->pid != -1 && this->pid != pid) kill();
this->pid = pid;
killSignal = SIGKILL; // reset signal to default
}
int Pid::kill() int Pid::kill()
{ {
assert(pid != -1); assert(pid != -1);
@ -125,7 +127,7 @@ void killUser(uid_t uid)
users to which the current process can send signals. So we users to which the current process can send signals. So we
fork a process, switch to uid, and send a mass kill. */ fork a process, switch to uid, and send a mass kill. */
Pid pid = startProcess([&]() { Pid pid{startProcess([&]() {
if (setuid(uid) == -1) if (setuid(uid) == -1)
throw SysError("setting uid"); throw SysError("setting uid");
@ -147,7 +149,7 @@ void killUser(uid_t uid)
} }
_exit(0); _exit(0);
}); })};
int status = pid.wait(); int status = pid.wait();
if (status != 0) if (status != 0)
@ -288,7 +290,7 @@ void runProgram2(const RunOptions & options)
} }
/* Fork. */ /* Fork. */
Pid pid = startProcess([&]() { Pid pid{startProcess([&]() {
if (options.environment) if (options.environment)
replaceEnv(*options.environment); replaceEnv(*options.environment);
if (options.standardOut && dup2(out.writeSide.get(), STDOUT_FILENO) == -1) if (options.standardOut && dup2(out.writeSide.get(), STDOUT_FILENO) == -1)
@ -322,7 +324,7 @@ void runProgram2(const RunOptions & options)
execv(options.program.c_str(), stringsToCharPtrs(args_).data()); execv(options.program.c_str(), stringsToCharPtrs(args_).data());
throw SysError("executing '%1%'", options.program); throw SysError("executing '%1%'", options.program);
}, processOptions); }, processOptions)};
out.writeSide.close(); out.writeSide.close();

View file

@ -26,9 +26,10 @@ class Pid
int killSignal = SIGKILL; int killSignal = SIGKILL;
public: public:
Pid(); Pid();
Pid(pid_t pid); explicit Pid(pid_t pid): pid(pid) {}
Pid(Pid && other);
Pid & operator=(Pid && other);
~Pid() noexcept(false); ~Pid() noexcept(false);
void operator =(pid_t pid);
explicit operator bool() const { return pid != -1; } explicit operator bool() const { return pid != -1; }
int kill(); int kill();
int wait(); int wait();

View file

@ -65,7 +65,7 @@ static void bindConnectProcHelper(
if (path.size() + 1 >= sizeof(addr.sun_path)) { if (path.size() + 1 >= sizeof(addr.sun_path)) {
Pipe pipe; Pipe pipe;
pipe.create(); pipe.create();
Pid pid = startProcess([&] { Pid pid{startProcess([&] {
try { try {
pipe.readSide.close(); pipe.readSide.close();
Path dir = dirOf(path); Path dir = dirOf(path);
@ -83,7 +83,7 @@ static void bindConnectProcHelper(
} catch (...) { } catch (...) {
writeFull(pipe.writeSide.get(), "-1\n"); writeFull(pipe.writeSide.get(), "-1\n");
} }
}); })};
pipe.writeSide.close(); pipe.writeSide.close();
auto errNo = string2Int<int>(chomp(drainFD(pipe.readSide.get()))); auto errNo = string2Int<int>(chomp(drainFD(pipe.readSide.get())));
if (!errNo || *errNo == -1) if (!errNo || *errNo == -1)