LibWebView: Tie helper processes to browser lifetime

Add an opt-in Core::Process spawn mode that asks supported kernels to
terminate the child when its parent exits. On Linux, use a fork/exec
path for this mode so the child can install PR_SET_PDEATHSIG before
execing the helper process. The existing posix_spawn path remains in
place for other process launches.

Opt LibWebView helper processes into this mode so WebContent,
WebWorker, and other browser children do not survive a crashed or
exited UI process.
This commit is contained in:
Andreas Kling 2026-05-26 19:00:40 +02:00 committed by Andreas Kling
parent 42f89bb679
commit 8e85f5a0c4
3 changed files with 109 additions and 4 deletions

View file

@ -16,8 +16,10 @@
#include <LibCore/Process.h>
#include <LibCore/System.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <spawn.h>
#include <sys/wait.h>
#include <unistd.h>
#if defined(AK_OS_SERENITY)
@ -31,6 +33,9 @@ extern "C" {
# include <hurd.h>
}
#endif
#if defined(AK_OS_LINUX)
# include <sys/prctl.h>
#endif
#if defined(AK_OS_FREEBSD)
# include <sys/user.h>
#endif
@ -82,8 +87,109 @@ Process Process::current()
return p;
}
#if defined(AK_OS_LINUX)
static Optional<int> run_file_actions_in_child(Vector<ProcessSpawnOptions::FileActionType> const& file_actions)
{
for (auto const& file_action : file_actions) {
auto error = file_action.visit(
[&](FileAction::OpenFile const& action) {
auto fd = open(
action.path.characters(),
File::open_mode_to_options(action.mode | Core::File::OpenMode::KeepOnExec),
action.permissions);
if (fd < 0)
return Optional<int> { errno };
if (fd != action.fd) {
if (dup2(fd, action.fd) < 0) {
auto saved_errno = errno;
close(fd);
return Optional<int> { saved_errno };
}
close(fd);
}
return Optional<int> {};
},
[&](FileAction::CloseFile const& action) {
close(action.fd);
return Optional<int> {};
},
[&](FileAction::DupFd const& action) {
if (dup2(action.write_fd, action.fd) < 0)
return Optional<int> { errno };
return Optional<int> {};
});
if (error.has_value())
return error;
}
return {};
}
static ErrorOr<pid_t> fork_and_exec_with_parent_death_signal(ProcessSpawnOptions const& options, Span<char const*> arguments)
{
auto error_pipe = TRY(System::pipe2(O_CLOEXEC));
auto parent_pid = getpid();
auto pid = fork();
if (pid < 0) {
auto saved_errno = errno;
MUST(System::close(error_pipe[0]));
MUST(System::close(error_pipe[1]));
return Error::from_syscall("fork"sv, saved_errno);
}
if (pid == 0) {
close(error_pipe[0]);
auto report_errno_and_exit = [&](int error) {
auto bytes_written = write(error_pipe[1], &error, sizeof(error));
(void)bytes_written;
_exit(127);
};
if (prctl(PR_SET_PDEATHSIG, SIGKILL) < 0)
report_errno_and_exit(errno);
if (getppid() != parent_pid)
_exit(127);
if (auto error = run_file_actions_in_child(options.file_actions); error.has_value())
report_errno_and_exit(error.value());
if (options.search_for_executable_in_path)
execvp(options.executable.characters(), const_cast<char* const*>(arguments.data()));
else
execve(options.executable.characters(), const_cast<char* const*>(arguments.data()), Environment::raw_environ());
report_errno_and_exit(errno);
}
MUST(System::close(error_pipe[1]));
int child_errno = 0;
auto bytes_read = TRY(System::read(error_pipe[0], { &child_errno, sizeof(child_errno) }));
MUST(System::close(error_pipe[0]));
if (bytes_read > 0) {
int status = 0;
(void)waitpid(pid, &status, 0);
return Error::from_errno(child_errno);
}
return pid;
}
#endif
ErrorOr<Process> Process::spawn(ProcessSpawnOptions const& options)
{
ArgvList argv_list(options.executable, options.arguments.size());
for (auto const& argument : options.arguments)
argv_list.append(argument.characters());
#if defined(AK_OS_LINUX)
if (options.die_with_parent)
return Process { TRY(fork_and_exec_with_parent_death_signal(options, argv_list.get())) };
#endif
#define CHECK(invocation) \
if (int returned_errno = (invocation)) \
return Error::from_errno(returned_errno);
@ -117,10 +223,6 @@ ErrorOr<Process> Process::spawn(ProcessSpawnOptions const& options)
#undef CHECK
ArgvList argv_list(options.executable, options.arguments.size());
for (auto const& argument : options.arguments)
argv_list.append(argument.characters());
pid_t pid;
if (options.search_for_executable_in_path) {
pid = TRY(System::posix_spawnp(options.executable.view(), &spawn_actions, nullptr, const_cast<char**>(argv_list.get().data()), Core::Environment::raw_environ()));

View file

@ -41,6 +41,8 @@ struct ProcessSpawnOptions {
StringView name {};
ByteString executable {};
bool search_for_executable_in_path { false };
// On supported platforms, ask the kernel to terminate this process when its parent dies.
bool die_with_parent { false };
Vector<ByteString> const& arguments {};
using FileActionType = Variant<FileAction::OpenFile, FileAction::CloseFile, FileAction::DupFd>;

View file

@ -47,6 +47,7 @@ ErrorOr<Process::ProcessAndIPCTransport> Process::spawn_and_connect_to_process(C
Array<int, 2> stderr_pipe {};
Core::ProcessSpawnOptions spawn_options = options;
spawn_options.die_with_parent = true;
if (capture_output) {
stdout_pipe = TRY(Core::System::pipe2(O_CLOEXEC));