diff options
author | Jason Carey <jcarey@argv.me> | 2016-11-03 18:03:06 -0400 |
---|---|---|
committer | Jason Carey <jcarey@argv.me> | 2016-11-07 11:09:07 -0500 |
commit | ac465d21d85dfde7d55764cb714d8e235c22383f (patch) | |
tree | ed2325f89812f2de5a72a41c460ccd4a9b294a26 /src/mongo/shell/shell_utils_launcher.cpp | |
parent | 04751ab04478fab9a1f190d117d3afe4f90a0d7c (diff) | |
download | mongo-ac465d21d85dfde7d55764cb714d8e235c22383f.tar.gz |
SERVER-26910 lock around fork() in shell launcher
The shell process management functions fork()/exec() in a multi-threaded
program without sufficient guards to avoid leaking fds into children.
This causes deadlocks around the program registry (as we rely on file
descriptor closure to break us out of dedicated child threads which read
child output).
The fix is to use a global lock to guard us from before the call to
pipe() until after we close write side of the pipe in the parent.
Diffstat (limited to 'src/mongo/shell/shell_utils_launcher.cpp')
-rw-r--r-- | src/mongo/shell/shell_utils_launcher.cpp | 150 |
1 files changed, 83 insertions, 67 deletions
diff --git a/src/mongo/shell/shell_utils_launcher.cpp b/src/mongo/shell/shell_utils_launcher.cpp index 629e48e5106..55ee501c10a 100644 --- a/src/mongo/shell/shell_utils_launcher.cpp +++ b/src/mongo/shell/shell_utils_launcher.cpp @@ -134,9 +134,7 @@ void safeClose(int fd) { } } -#ifdef _WIN32 stdx::mutex _createProcessMtx; -#endif } // namespace ProgramOutputMultiplexer programOutputLogger; @@ -374,37 +372,58 @@ ProgramRunner::ProgramRunner(const BSONObj& args, const BSONObj& env) { void ProgramRunner::start() { int pipeEnds[2]; - int status = pipe(pipeEnds); - if (status != 0) { - const auto ewd = errnoWithDescription(); - error() << "failed to create pipe: " << ewd; - fassertFailed(16701); - } + + { + // NOTE(JCAREY): + // + // We take this lock from before our call to pipe until after we close the write side (in + // the parent) to avoid leaking fds from threads racing around fork(). I.e. + // + // Thread A: calls pipe() + // Thread B: calls fork() + // A: sets cloexec on read and write sides + // B: has a forked child with open fds + // A: spawns a child thread to read it's child process's stdout + // A: A's child process exits + // A: wait's on A's reader thread in de-register + // A: deadlocks forever (because the child reader thread stays in read() because of the open + // fd in B) + // + // Holding the lock for the duration of those events prevents the leaks and thus the + // associated deadlocks. + stdx::lock_guard<stdx::mutex> lk(_createProcessMtx); + int status = pipe(pipeEnds); + if (status != 0) { + const auto ewd = errnoWithDescription(); + error() << "failed to create pipe: " << ewd; + fassertFailed(16701); + } #ifndef _WIN32 - // The calls to fcntl to set CLOEXEC ensure that processes started by the process we are about - // to fork do *not* inherit the file descriptors for the pipe. If grandchild processes could - // inherit the FD for the pipe, than the pipe wouldn't close on child process exit. On windows, - // instead the handle inherit flag is turned off after the call to CreateProcess. - status = fcntl(pipeEnds[0], F_SETFD, FD_CLOEXEC); - if (status != 0) { - const auto ewd = errnoWithDescription(); - error() << "failed to set FD_CLOEXEC on pipe end 0: " << ewd; - fassertFailed(40308); - } - status = fcntl(pipeEnds[1], F_SETFD, FD_CLOEXEC); - if (status != 0) { - const auto ewd = errnoWithDescription(); - error() << "failed to set FD_CLOEXEC on pipe end 1: " << ewd; - fassertFailed(40317); - } + // The calls to fcntl to set CLOEXEC ensure that processes started by the process we are + // about to fork do *not* inherit the file descriptors for the pipe. If grandchild processes + // could inherit the FD for the pipe, than the pipe wouldn't close on child process exit. On + // windows, instead the handle inherit flag is turned off after the call to CreateProcess. + status = fcntl(pipeEnds[0], F_SETFD, FD_CLOEXEC); + if (status != 0) { + const auto ewd = errnoWithDescription(); + error() << "failed to set FD_CLOEXEC on pipe end 0: " << ewd; + fassertFailed(40308); + } + status = fcntl(pipeEnds[1], F_SETFD, FD_CLOEXEC); + if (status != 0) { + const auto ewd = errnoWithDescription(); + error() << "failed to set FD_CLOEXEC on pipe end 1: " << ewd; + fassertFailed(40317); + } #endif - fflush(0); + fflush(0); - launchProcess(pipeEnds[1]); // sets _pid + launchProcess(pipeEnds[1]); // sets _pid - // Close the write end of the pipe. - safeClose(pipeEnds[1]); + // Close the write end of the pipe. + safeClose(pipeEnds[1]); + } if (_port >= 0) { registry.registerProgram(_pid, _port); @@ -551,47 +570,44 @@ void ProgramRunner::launchProcess(int child_stdout) { } std::memset(lpEnvironment.get() + environmentOffset, 0, sizeof(wchar_t)); - { - stdx::lock_guard<stdx::mutex> lk(_createProcessMtx); - HANDLE h = reinterpret_cast<HANDLE>(_get_osfhandle(child_stdout)); - invariant(h != INVALID_HANDLE_VALUE); - invariant(SetHandleInformation(h, HANDLE_FLAG_INHERIT, 1)); - - STARTUPINFO si; - ZeroMemory(&si, sizeof(si)); - si.cb = sizeof(si); - si.hStdError = h; - si.hStdOutput = h; - si.dwFlags |= STARTF_USESTDHANDLES; - - PROCESS_INFORMATION pi; - ZeroMemory(&pi, sizeof(pi)); - - DWORD dwCreationFlags = 0; - dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT; - - bool success = CreateProcessW(nullptr, - const_cast<LPWSTR>(args.c_str()), - nullptr, - nullptr, - true, - dwCreationFlags, - lpEnvironment.get(), - nullptr, - &si, - &pi) != 0; - if (!success) { - const auto ewd = errnoWithDescription(); - ss << "couldn't start process " << _argv[0] << "; " << ewd; - uasserted(14042, ss.str()); - } + HANDLE h = reinterpret_cast<HANDLE>(_get_osfhandle(child_stdout)); + invariant(h != INVALID_HANDLE_VALUE); + invariant(SetHandleInformation(h, HANDLE_FLAG_INHERIT, 1)); + + STARTUPINFO si; + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + si.hStdError = h; + si.hStdOutput = h; + si.dwFlags |= STARTF_USESTDHANDLES; + + PROCESS_INFORMATION pi; + ZeroMemory(&pi, sizeof(pi)); + + DWORD dwCreationFlags = 0; + dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT; + + bool success = CreateProcessW(nullptr, + const_cast<LPWSTR>(args.c_str()), + nullptr, + nullptr, + true, + dwCreationFlags, + lpEnvironment.get(), + nullptr, + &si, + &pi) != 0; + if (!success) { + const auto ewd = errnoWithDescription(); + ss << "couldn't start process " << _argv[0] << "; " << ewd; + uasserted(14042, ss.str()); + } - CloseHandle(pi.hThread); - invariant(SetHandleInformation(h, HANDLE_FLAG_INHERIT, 0)); + CloseHandle(pi.hThread); + invariant(SetHandleInformation(h, HANDLE_FLAG_INHERIT, 0)); - _pid = ProcessId::fromNative(pi.dwProcessId); - registry.insertHandleForPid(_pid, pi.hProcess); - } + _pid = ProcessId::fromNative(pi.dwProcessId); + registry.insertHandleForPid(_pid, pi.hProcess); #else std::string execErrMsg = str::stream() << "Unable to start program " << _argv[0]; |