Skip to content

Commit 75e6dd4

Browse files
committed
[lldb/Host] Enable inheriting "non-inheritable" FDs
Currently we're creating inheritable (`~FD_CLOEXEC`) file descriptors in the (few) cases where we need to pass an FD to a subprocess. The problem with these is that, in a multithreaded application such as lldb, there's essentially no way to prevent them from being leaked into processes other than the intended one. A safer (though still not completely safe) approach is to mark the descriptors as FD_CLOEXEC and only clear this flag in the subprocess. We currently have something that almost does that, which is the ability to add a `DuplicateFileAction` to our `ProcessLaunchInfo` struct (the duplicated file descriptor will be created with the flag cleared). The problem with *that* is that this approach is completely incompatible with Windows. Windows equivalents of file descriptors are `HANDLE`s, but these do not have user controlled values -- applications are expected to work with whatever HANDLE values are assigned by the OS. In unix terms, there is no equivalent to the `dup2` syscall (only `dup`). To find a way out of this conundrum, and create a miniscule API surface that works uniformly across platforms, this PR proposes to extend the `DuplicateFileAction` API to support duplicating a file descriptor onto itself. Currently, this operation does nothing (it leaves the FD_CLOEXEC flag set), because that's how `dup2(fd, fd)` behaves, but I think it's not completely unreasonable to say that this operation should clear the FD_CLOEXEC flag, just like it would do if one was using different fd values. This would enable us to pass a windows HANDLE as itself through the ProcessLaunchInfo API. This PR implements the unix portion of this idea. Macos and non-macos launchers are updated to clear FD_CLOEXEC flag when duplicating a file descriptor onto itself, and I've created a test which enables passing a FD_CLOEXEC file descritor to the subprocess. For the windows portion, please see the follow-up PR.
1 parent bf7af2d commit 75e6dd4

File tree

3 files changed

+73
-4
lines changed

3 files changed

+73
-4
lines changed

lldb/source/Host/macosx/objcxx/Host.mm

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1100,7 +1100,7 @@ static bool AddPosixSpawnFileAction(void *_file_actions, const FileAction *info,
11001100
else if (info->GetActionArgument() == -1)
11011101
error = Status::FromErrorString(
11021102
"invalid duplicate fd for posix_spawn_file_actions_adddup2(...)");
1103-
else {
1103+
else if (info->GetFD() != info->GetActionArgument()) {
11041104
error =
11051105
Status(::posix_spawn_file_actions_adddup2(file_actions, info->GetFD(),
11061106
info->GetActionArgument()),
@@ -1110,6 +1110,15 @@ static bool AddPosixSpawnFileAction(void *_file_actions, const FileAction *info,
11101110
"error: {0}, posix_spawn_file_actions_adddup2 "
11111111
"(action={1}, fd={2}, dup_fd={3})",
11121112
error, file_actions, info->GetFD(), info->GetActionArgument());
1113+
} else {
1114+
error =
1115+
Status(::posix_spawn_file_actions_addinherit_np(file_actions, info->GetFD()),
1116+
eErrorTypePOSIX);
1117+
if (error.Fail())
1118+
LLDB_LOG(log,
1119+
"error: {0}, posix_spawn_file_actions_addinherit_np "
1120+
"(action={1}, fd={2})",
1121+
error, file_actions, info->GetFD());
11131122
}
11141123
break;
11151124

lldb/source/Host/posix/ProcessLauncherPosixFork.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "llvm/Support/Errno.h"
1818

1919
#include <climits>
20+
#include <fcntl.h>
2021
#include <sys/ptrace.h>
2122
#include <sys/wait.h>
2223
#include <unistd.h>
@@ -121,8 +122,14 @@ struct ForkLaunchInfo {
121122
ExitWithError(error_fd, "close");
122123
break;
123124
case FileAction::eFileActionDuplicate:
124-
if (dup2(action.fd, action.arg) == -1)
125-
ExitWithError(error_fd, "dup2");
125+
if (action.fd != action.arg) {
126+
if (dup2(action.fd, action.arg) == -1)
127+
ExitWithError(error_fd, "dup2");
128+
} else {
129+
if (fcntl(action.fd, F_SETFD,
130+
fcntl(action.fd, F_GETFD) & ~FD_CLOEXEC) == -1)
131+
ExitWithError(error_fd, "fcntl");
132+
}
126133
break;
127134
case FileAction::eFileActionOpen:
128135
DupDescriptor(error_fd, action.path.c_str(), action.fd, action.arg);

lldb/unittests/Host/HostTest.cpp

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,24 @@
77
//===----------------------------------------------------------------------===//
88

99
#include "lldb/Host/Host.h"
10+
#include "TestingSupport/SubsystemRAII.h"
11+
#include "lldb/Host/FileSystem.h"
12+
#include "lldb/Host/Pipe.h"
13+
#include "lldb/Host/ProcessLaunchInfo.h"
1014
#include "lldb/Utility/ProcessInfo.h"
15+
#include "llvm/ADT/Twine.h"
16+
#include "llvm/Support/CommandLine.h"
17+
#include "llvm/Testing/Support/Error.h"
1118
#include "gtest/gtest.h"
1219

20+
// From TestMain.cpp.
21+
extern const char *TestMainArgv0;
22+
1323
using namespace lldb_private;
1424
using namespace llvm;
1525

26+
static cl::opt<uint64_t> test_arg("test-arg");
27+
1628
TEST(Host, WaitStatusFormat) {
1729
EXPECT_EQ("W01", formatv("{0:g}", WaitStatus{WaitStatus::Exit, 1}).str());
1830
EXPECT_EQ("X02", formatv("{0:g}", WaitStatus{WaitStatus::Signal, 2}).str());
@@ -45,4 +57,45 @@ TEST(Host, ProcessInstanceInfoCumulativeSystemTimeIsValid) {
4557
EXPECT_TRUE(info.CumulativeSystemTimeIsValid());
4658
info.SetCumulativeSystemTime(ProcessInstanceInfo::timespec{1, 0});
4759
EXPECT_TRUE(info.CumulativeSystemTimeIsValid());
48-
}
60+
}
61+
62+
#ifdef LLVM_ON_UNIX
63+
TEST(Host, LaunchProcessDuplicatesHandle) {
64+
static constexpr llvm::StringLiteral test_msg("Hello subprocess!");
65+
66+
SubsystemRAII<FileSystem> subsystems;
67+
68+
if (test_arg) {
69+
Pipe pipe(LLDB_INVALID_PIPE, test_arg);
70+
size_t bytes_written;
71+
if (pipe.WriteWithTimeout(test_msg.data(), test_msg.size(),
72+
std::chrono::microseconds(0), bytes_written)
73+
.Success() &&
74+
bytes_written == test_msg.size())
75+
exit(0);
76+
exit(1);
77+
}
78+
Pipe pipe;
79+
ASSERT_THAT_ERROR(pipe.CreateNew(/*child_process_inherit=*/false).takeError(),
80+
llvm::Succeeded());
81+
ProcessLaunchInfo info;
82+
info.SetExecutableFile(FileSpec(TestMainArgv0),
83+
/*add_exe_file_as_first_arg=*/true);
84+
info.GetArguments().AppendArgument(
85+
"--gtest_filter=Host.LaunchProcessDuplicatesHandle");
86+
info.GetArguments().AppendArgument(
87+
("--test-arg=" + llvm::Twine::utohexstr(pipe.GetWritePipe())).str());
88+
info.AppendDuplicateFileAction(pipe.GetWritePipe(), pipe.GetWritePipe());
89+
info.SetMonitorProcessCallback(&ProcessLaunchInfo::NoOpMonitorCallback);
90+
ASSERT_THAT_ERROR(Host::LaunchProcess(info).takeError(), llvm::Succeeded());
91+
pipe.CloseWriteFileDescriptor();
92+
93+
char msg[100];
94+
size_t bytes_read;
95+
ASSERT_THAT_ERROR(pipe.ReadWithTimeout(msg, sizeof(msg),
96+
std::chrono::seconds(10), bytes_read)
97+
.takeError(),
98+
llvm::Succeeded());
99+
ASSERT_EQ(llvm::StringRef(msg, bytes_read), test_msg);
100+
}
101+
#endif

0 commit comments

Comments
 (0)