From 27297d28173c6d1812e379bd213644cfd8da3545 Mon Sep 17 00:00:00 2001 From: Fabian Dellwing Date: Sun, 7 May 2023 20:47:40 +0200 Subject: [PATCH] Escalator: Major rework and enable forwarding of stdin/stdout - Escalator now uses `posix_spawn` instead of `exec` - Escalator is now able to forward stdin and stdout - A few general changes to improve code quality --- .../Escalator/EscalatorWindow.cpp | 72 +++++++++++++------ .../Applications/Escalator/EscalatorWindow.h | 5 ++ Userland/Applications/Escalator/main.cpp | 9 ++- 3 files changed, 64 insertions(+), 22 deletions(-) diff --git a/Userland/Applications/Escalator/EscalatorWindow.cpp b/Userland/Applications/Escalator/EscalatorWindow.cpp index f8cbd0d97f..56d19d5993 100644 --- a/Userland/Applications/Escalator/EscalatorWindow.cpp +++ b/Userland/Applications/Escalator/EscalatorWindow.cpp @@ -1,6 +1,7 @@ /* * Copyright (c) 2022, Ashley N. * Copyright (c) 2022, the SerenityOS developers. + * Copyright (c) 2023, Fabian Dellwing * * SPDX-License-Identifier: BSD-2-Clause */ @@ -8,6 +9,7 @@ #include "EscalatorWindow.h" #include #include +#include #include #include #include @@ -15,13 +17,16 @@ #include #include #include +#include #include EscalatorWindow::EscalatorWindow(StringView executable, Vector arguments, EscalatorWindow::Options const& options) - : m_arguments(arguments) + : m_arguments(move(arguments)) , m_executable(executable) , m_current_user(options.current_user) , m_preserve_env(options.preserve_env) + , m_forward_stdin(options.forward_stdin) + , m_forward_stdout(options.forward_stdout) { auto app_icon = GUI::FileIconProvider::icon_for_executable(m_executable); @@ -52,8 +57,8 @@ EscalatorWindow::EscalatorWindow(StringView executable, Vector argum auto result = check_password(); if (result.is_error()) { GUI::MessageBox::show_error(this, DeprecatedString::formatted("Failed to execute command: {}", result.error())); - close(); } + close(); }; m_ok_button->set_default(true); @@ -83,32 +88,59 @@ ErrorOr EscalatorWindow::check_password() // Caller will close Escalator if error is returned. TRY(execute_command()); - VERIFY_NOT_REACHED(); + return {}; } ErrorOr EscalatorWindow::execute_command() { - // Translate environ to format for Core::System::exec. - Vector exec_environment; - for (size_t i = 0; environ[i]; ++i) { - StringView env_view { environ[i], strlen(environ[i]) }; - auto maybe_needle = env_view.find('='); - - if (!maybe_needle.has_value()) - continue; - - if (!m_preserve_env && env_view.substring_view(0, maybe_needle.value()) != "TERM"sv) - continue; - - exec_environment.append(env_view); - } + char const* envp[] = { nullptr }; + Vector argv; + for (auto& arg : m_arguments) + argv.append(arg.characters_without_null_termination()); + argv.append(nullptr); // Escalate process privilege to root user. TRY(Core::System::seteuid(0)); auto root_user = TRY(Core::Account::from_uid(0)); TRY(root_user.login()); - TRY(Core::System::pledge("stdio sendfd rpath exec")); - TRY(Core::System::exec(m_executable, m_arguments, Core::System::SearchInPath::No, exec_environment)); - VERIFY_NOT_REACHED(); + if (m_forward_stdin || m_forward_stdout) { + auto in_pipefds = TRY(Core::System::pipe2(O_CLOEXEC)); + auto out_pipefds = TRY(Core::System::pipe2(O_CLOEXEC)); + ScopeGuard guard_fds { [&] { + ::close(in_pipefds[1]); + ::close(out_pipefds[0]); + } }; + { + posix_spawn_file_actions_t file_actions; + posix_spawn_file_actions_init(&file_actions); + posix_spawn_file_actions_adddup2(&file_actions, in_pipefds[0], STDIN_FILENO); + posix_spawn_file_actions_adddup2(&file_actions, out_pipefds[1], STDOUT_FILENO); + + ScopeGuard guard_fds_and_file_actions { [&]() { + posix_spawn_file_actions_destroy(&file_actions); + ::close(in_pipefds[0]); + ::close(out_pipefds[1]); + } }; + + TRY(Core::System::pledge("stdio sendfd rpath proc exec")); + (void)TRY(Core::System::posix_spawn(m_executable, &file_actions, nullptr, const_cast(argv.data()), m_preserve_env ? environ : const_cast(envp))); + + if (m_forward_stdin) { + auto in_outfile = TRY(Core::File::adopt_fd(in_pipefds[1], Core::File::OpenMode::Write, Core::File::ShouldCloseFileDescriptor::No)); + auto in_infile = TRY(Core::File::standard_input()); + TRY(in_outfile->write_until_depleted(TRY(in_infile->read_until_eof()))); + } + if (m_forward_stdout) { + auto out_outfile = TRY(Core::File::standard_output()); + auto out_infile = TRY(Core::File::adopt_fd(out_pipefds[0], Core::File::OpenMode::Read, Core::File::ShouldCloseFileDescriptor::No)); + TRY(out_outfile->write_until_depleted(TRY(out_infile->read_until_eof()))); + } + } + } else { + TRY(Core::System::pledge("stdio sendfd rpath proc exec")); + (void)TRY(Core::System::posix_spawn(m_executable, nullptr, nullptr, const_cast(argv.data()), m_preserve_env ? environ : const_cast(envp))); + } + + return {}; } diff --git a/Userland/Applications/Escalator/EscalatorWindow.h b/Userland/Applications/Escalator/EscalatorWindow.h index 0294093645..53b877c6e2 100644 --- a/Userland/Applications/Escalator/EscalatorWindow.h +++ b/Userland/Applications/Escalator/EscalatorWindow.h @@ -1,6 +1,7 @@ /* * Copyright (c) 2022, Ashley N. * Copyright (c) 2022, the SerenityOS developers. + * Copyright (c) 2023, Fabian Dellwing * * SPDX-License-Identifier: BSD-2-Clause */ @@ -23,6 +24,8 @@ public: StringView description; Core::Account current_user; bool preserve_env { false }; + bool forward_stdin { false }; + bool forward_stdout { false }; }; virtual ~EscalatorWindow() override = default; @@ -38,6 +41,8 @@ private: StringView m_executable; Core::Account m_current_user; bool m_preserve_env { false }; + bool m_forward_stdin { false }; + bool m_forward_stdout { false }; RefPtr m_icon_image_widget; RefPtr m_ok_button; diff --git a/Userland/Applications/Escalator/main.cpp b/Userland/Applications/Escalator/main.cpp index 806bba33b4..c98b6b8681 100644 --- a/Userland/Applications/Escalator/main.cpp +++ b/Userland/Applications/Escalator/main.cpp @@ -1,6 +1,7 @@ /* * Copyright (c) 2022, Ashley N. * Copyright (c) 2022, the SerenityOS developers. + * Copyright (c) 2023, Fabian Dellwing * * SPDX-License-Identifier: BSD-2-Clause */ @@ -22,10 +23,14 @@ ErrorOr serenity_main(Main::Arguments arguments) Core::ArgsParser args_parser; StringView description; bool preserve_env = false; + bool forward_stdin = false; + bool forward_stdout = false; args_parser.set_general_help("Escalate privilege to root for a given command using a GUI prompt."); args_parser.set_stop_on_first_non_option(true); args_parser.add_option(description, "Custom prompt to use for dialog", "prompt", 'P', "prompt"); args_parser.add_option(preserve_env, "Preserve user environment when running command", "preserve-env", 'E'); + args_parser.add_option(forward_stdin, "Forward stdin to targets stdin", "forward-stdin", 'I'); + args_parser.add_option(forward_stdout, "Forward targets stdout to stdout", "forward-stdout", 'O'); args_parser.add_positional_argument(command, "Command to run at elevated privilege level", "command"); args_parser.parse(arguments); @@ -33,14 +38,14 @@ ErrorOr serenity_main(Main::Arguments arguments) auto app = TRY(GUI::Application::create(arguments)); - auto executable_path = FileSystem::resolve_executable_from_environment(command[0]); + auto executable_path = FileSystem::resolve_executable_from_environment(command[0], AT_EACCESS); if (executable_path.is_error()) { GUI::MessageBox::show_error(nullptr, DeprecatedString::formatted("Could not execute command {}: Command not found.", command[0])); return 127; } auto current_user = TRY(Core::Account::self()); - auto window = TRY(EscalatorWindow::try_create(executable_path.value(), command, EscalatorWindow::Options { description, current_user, preserve_env })); + auto window = TRY(EscalatorWindow::try_create(executable_path.value(), command, EscalatorWindow::Options { description, current_user, preserve_env, forward_stdin, forward_stdout })); if (current_user.uid() != 0) { window->show();