[Lldb-commits] [lldb] [lldb/Host] Enable inheriting "non-inheritable" FDs (PR #126935)
Pavel Labath via lldb-commits
lldb-commits at lists.llvm.org
Wed Feb 12 08:22:19 PST 2025
https://github.com/labath created https://github.com/llvm/llvm-project/pull/126935
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.
>From 75e6dd4f81a9fe4bd303ba4e3b0301b69d47b523 Mon Sep 17 00:00:00 2001
From: Pavel Labath <pavel at labath.sk>
Date: Sun, 20 Oct 2024 02:55:02 +0200
Subject: [PATCH] [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.
---
lldb/source/Host/macosx/objcxx/Host.mm | 11 +++-
.../Host/posix/ProcessLauncherPosixFork.cpp | 11 +++-
lldb/unittests/Host/HostTest.cpp | 55 ++++++++++++++++++-
3 files changed, 73 insertions(+), 4 deletions(-)
diff --git a/lldb/source/Host/macosx/objcxx/Host.mm b/lldb/source/Host/macosx/objcxx/Host.mm
index 5b3d04a6b587b..38edac45dedbd 100644
--- a/lldb/source/Host/macosx/objcxx/Host.mm
+++ b/lldb/source/Host/macosx/objcxx/Host.mm
@@ -1100,7 +1100,7 @@ static bool AddPosixSpawnFileAction(void *_file_actions, const FileAction *info,
else if (info->GetActionArgument() == -1)
error = Status::FromErrorString(
"invalid duplicate fd for posix_spawn_file_actions_adddup2(...)");
- else {
+ else if (info->GetFD() != info->GetActionArgument()) {
error =
Status(::posix_spawn_file_actions_adddup2(file_actions, info->GetFD(),
info->GetActionArgument()),
@@ -1110,6 +1110,15 @@ static bool AddPosixSpawnFileAction(void *_file_actions, const FileAction *info,
"error: {0}, posix_spawn_file_actions_adddup2 "
"(action={1}, fd={2}, dup_fd={3})",
error, file_actions, info->GetFD(), info->GetActionArgument());
+ } else {
+ error =
+ Status(::posix_spawn_file_actions_addinherit_np(file_actions, info->GetFD()),
+ eErrorTypePOSIX);
+ if (error.Fail())
+ LLDB_LOG(log,
+ "error: {0}, posix_spawn_file_actions_addinherit_np "
+ "(action={1}, fd={2})",
+ error, file_actions, info->GetFD());
}
break;
diff --git a/lldb/source/Host/posix/ProcessLauncherPosixFork.cpp b/lldb/source/Host/posix/ProcessLauncherPosixFork.cpp
index 3e956290c3055..c3f1896172c41 100644
--- a/lldb/source/Host/posix/ProcessLauncherPosixFork.cpp
+++ b/lldb/source/Host/posix/ProcessLauncherPosixFork.cpp
@@ -17,6 +17,7 @@
#include "llvm/Support/Errno.h"
#include <climits>
+#include <fcntl.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <unistd.h>
@@ -121,8 +122,14 @@ struct ForkLaunchInfo {
ExitWithError(error_fd, "close");
break;
case FileAction::eFileActionDuplicate:
- if (dup2(action.fd, action.arg) == -1)
- ExitWithError(error_fd, "dup2");
+ if (action.fd != action.arg) {
+ if (dup2(action.fd, action.arg) == -1)
+ ExitWithError(error_fd, "dup2");
+ } else {
+ if (fcntl(action.fd, F_SETFD,
+ fcntl(action.fd, F_GETFD) & ~FD_CLOEXEC) == -1)
+ ExitWithError(error_fd, "fcntl");
+ }
break;
case FileAction::eFileActionOpen:
DupDescriptor(error_fd, action.path.c_str(), action.fd, action.arg);
diff --git a/lldb/unittests/Host/HostTest.cpp b/lldb/unittests/Host/HostTest.cpp
index a1d8a3b7f485a..b9ad3e9ae1e41 100644
--- a/lldb/unittests/Host/HostTest.cpp
+++ b/lldb/unittests/Host/HostTest.cpp
@@ -7,12 +7,24 @@
//===----------------------------------------------------------------------===//
#include "lldb/Host/Host.h"
+#include "TestingSupport/SubsystemRAII.h"
+#include "lldb/Host/FileSystem.h"
+#include "lldb/Host/Pipe.h"
+#include "lldb/Host/ProcessLaunchInfo.h"
#include "lldb/Utility/ProcessInfo.h"
+#include "llvm/ADT/Twine.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Testing/Support/Error.h"
#include "gtest/gtest.h"
+// From TestMain.cpp.
+extern const char *TestMainArgv0;
+
using namespace lldb_private;
using namespace llvm;
+static cl::opt<uint64_t> test_arg("test-arg");
+
TEST(Host, WaitStatusFormat) {
EXPECT_EQ("W01", formatv("{0:g}", WaitStatus{WaitStatus::Exit, 1}).str());
EXPECT_EQ("X02", formatv("{0:g}", WaitStatus{WaitStatus::Signal, 2}).str());
@@ -45,4 +57,45 @@ TEST(Host, ProcessInstanceInfoCumulativeSystemTimeIsValid) {
EXPECT_TRUE(info.CumulativeSystemTimeIsValid());
info.SetCumulativeSystemTime(ProcessInstanceInfo::timespec{1, 0});
EXPECT_TRUE(info.CumulativeSystemTimeIsValid());
-}
\ No newline at end of file
+}
+
+#ifdef LLVM_ON_UNIX
+TEST(Host, LaunchProcessDuplicatesHandle) {
+ static constexpr llvm::StringLiteral test_msg("Hello subprocess!");
+
+ SubsystemRAII<FileSystem> subsystems;
+
+ if (test_arg) {
+ Pipe pipe(LLDB_INVALID_PIPE, test_arg);
+ size_t bytes_written;
+ if (pipe.WriteWithTimeout(test_msg.data(), test_msg.size(),
+ std::chrono::microseconds(0), bytes_written)
+ .Success() &&
+ bytes_written == test_msg.size())
+ exit(0);
+ exit(1);
+ }
+ Pipe pipe;
+ ASSERT_THAT_ERROR(pipe.CreateNew(/*child_process_inherit=*/false).takeError(),
+ llvm::Succeeded());
+ ProcessLaunchInfo info;
+ info.SetExecutableFile(FileSpec(TestMainArgv0),
+ /*add_exe_file_as_first_arg=*/true);
+ info.GetArguments().AppendArgument(
+ "--gtest_filter=Host.LaunchProcessDuplicatesHandle");
+ info.GetArguments().AppendArgument(
+ ("--test-arg=" + llvm::Twine::utohexstr(pipe.GetWritePipe())).str());
+ info.AppendDuplicateFileAction(pipe.GetWritePipe(), pipe.GetWritePipe());
+ info.SetMonitorProcessCallback(&ProcessLaunchInfo::NoOpMonitorCallback);
+ ASSERT_THAT_ERROR(Host::LaunchProcess(info).takeError(), llvm::Succeeded());
+ pipe.CloseWriteFileDescriptor();
+
+ char msg[100];
+ size_t bytes_read;
+ ASSERT_THAT_ERROR(pipe.ReadWithTimeout(msg, sizeof(msg),
+ std::chrono::seconds(10), bytes_read)
+ .takeError(),
+ llvm::Succeeded());
+ ASSERT_EQ(llvm::StringRef(msg, bytes_read), test_msg);
+}
+#endif
More information about the lldb-commits
mailing list