diff options
author | ne0ndrag0n <ne0ndrag0n@users.noreply.github.com> | 2022-10-01 00:40:28 -0400 |
---|---|---|
committer | Linus Groh <mail@linusgroh.de> | 2022-10-14 11:23:06 +0200 |
commit | b4456ecdbbe6aa9e0187c06458acc51e50ced512 (patch) | |
tree | 59ffb4fe862d246cb72879863e1f7b19b96c2602 /Userland/Applications/Escalator | |
parent | a143d666db14369f26da3d8590443083b382d8fe (diff) | |
download | serenity-b4456ecdbbe6aa9e0187c06458acc51e50ced512.zip |
Escalator: Add new method to privilege escalate within GUI
Diffstat (limited to 'Userland/Applications/Escalator')
-rw-r--r-- | Userland/Applications/Escalator/CMakeLists.txt | 19 | ||||
-rw-r--r-- | Userland/Applications/Escalator/Escalator.gml | 49 | ||||
-rw-r--r-- | Userland/Applications/Escalator/EscalatorWindow.cpp | 114 | ||||
-rw-r--r-- | Userland/Applications/Escalator/EscalatorWindow.h | 47 | ||||
-rw-r--r-- | Userland/Applications/Escalator/main.cpp | 53 |
5 files changed, 282 insertions, 0 deletions
diff --git a/Userland/Applications/Escalator/CMakeLists.txt b/Userland/Applications/Escalator/CMakeLists.txt new file mode 100644 index 0000000000..e8a1749311 --- /dev/null +++ b/Userland/Applications/Escalator/CMakeLists.txt @@ -0,0 +1,19 @@ +serenity_component( + Escalator + REQUIRED + TARGETS Escalator +) + +compile_gml(Escalator.gml EscalatorGML.h escalator_gml) + +set(SOURCES + main.cpp + EscalatorWindow.cpp +) + +set(GENERATED_SOURCES + EscalatorGML.h +) + +serenity_app(Escalator ICON app-escalator) +target_link_libraries(Escalator LibCore LibDesktop LibGUI LibMain) diff --git a/Userland/Applications/Escalator/Escalator.gml b/Userland/Applications/Escalator/Escalator.gml new file mode 100644 index 0000000000..08ec1fb3ed --- /dev/null +++ b/Userland/Applications/Escalator/Escalator.gml @@ -0,0 +1,49 @@ +@GUI::Widget { + fill_with_background_color: true + layout: @GUI::VerticalBoxLayout { + margins: [4] + } + + @GUI::Widget { + max_height: 32 + layout: @GUI::HorizontalBoxLayout { + spacing: 16 + } + + @GUI::ImageWidget { + name: "icon" + } + + @GUI::Label { + name: "description" + text_alignment: "CenterLeft" + } + } + + @GUI::Widget { + layout: @GUI::HorizontalBoxLayout { + margins: [4] + } + + @GUI::PasswordBox { + name: "password" + } + } + + @GUI::Widget { + layout: @GUI::HorizontalBoxLayout {} + fixed_height: 22 + + @GUI::Layout::Spacer {} + + @GUI::DialogButton { + name: "ok_button" + text: "OK" + } + + @GUI::DialogButton { + name: "cancel_button" + text: "Cancel" + } + } +} diff --git a/Userland/Applications/Escalator/EscalatorWindow.cpp b/Userland/Applications/Escalator/EscalatorWindow.cpp new file mode 100644 index 0000000000..9b2927fe63 --- /dev/null +++ b/Userland/Applications/Escalator/EscalatorWindow.cpp @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2022, Ashley N. <dev-serenity@ne0ndrag0n.com> + * Copyright (c) 2022, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "EscalatorWindow.h" +#include <AK/Assertions.h> +#include <Applications/Escalator/EscalatorGML.h> +#include <LibCore/SecretString.h> +#include <LibCore/System.h> +#include <LibGUI/FileIconProvider.h> +#include <LibGUI/Icon.h> +#include <LibGUI/Label.h> +#include <LibGUI/MessageBox.h> +#include <LibGUI/Widget.h> +#include <unistd.h> + +EscalatorWindow::EscalatorWindow(StringView executable, Vector<StringView> arguments, EscalatorWindow::Options const& options) + : m_arguments(arguments) + , m_executable(executable) + , m_current_user(options.current_user) + , m_preserve_env(options.preserve_env) +{ + auto app_icon = GUI::FileIconProvider::icon_for_executable(m_executable); + + set_title("Run as Root"); + set_icon(app_icon.bitmap_for_size(16)); + resize(345, 100); + set_resizable(false); + set_minimizable(false); + + auto& main_widget = set_main_widget<GUI::Widget>(); + main_widget.load_from_gml(escalator_gml); + + RefPtr<GUI::Label> app_label = *main_widget.find_descendant_of_type_named<GUI::Label>("description"); + + String prompt; + if (options.description.is_empty()) + prompt = String::formatted("{} requires root access. Please enter password for user \"{}\".", m_arguments[0], m_current_user.username()); + else + prompt = options.description; + + app_label->set_text(prompt); + + m_icon_image_widget = *main_widget.find_descendant_of_type_named<GUI::ImageWidget>("icon"); + m_icon_image_widget->set_bitmap(app_icon.bitmap_for_size(32)); + + m_ok_button = *main_widget.find_descendant_of_type_named<GUI::DialogButton>("ok_button"); + m_ok_button->on_click = [this](auto) { + auto result = check_password(); + if (result.is_error()) { + GUI::MessageBox::show_error(this, String::formatted("Failed to execute command: {}", result.error())); + close(); + } + }; + m_ok_button->set_default(true); + + m_cancel_button = *main_widget.find_descendant_of_type_named<GUI::DialogButton>("cancel_button"); + m_cancel_button->on_click = [this](auto) { + close(); + }; + + m_password_input = *main_widget.find_descendant_of_type_named<GUI::PasswordBox>("password"); +} + +ErrorOr<void> EscalatorWindow::check_password() +{ + String password = m_password_input->text(); + if (password.is_empty()) { + GUI::MessageBox::show_error(this, "Please enter a password."sv); + return {}; + } + + // FIXME: PasswordBox really should store its input directly as a SecretString. + Core::SecretString password_secret = Core::SecretString::take_ownership(password.to_byte_buffer()); + if (!m_current_user.authenticate(password_secret)) { + GUI::MessageBox::show_error(this, "Incorrect or disabled password."sv); + m_password_input->select_all(); + return {}; + } + + // Caller will close Escalator if error is returned. + TRY(execute_command()); + VERIFY_NOT_REACHED(); +} + +ErrorOr<void> EscalatorWindow::execute_command() +{ + // Translate environ to format for Core::System::exec. + Vector<StringView> 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); + } + + // 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(); +} diff --git a/Userland/Applications/Escalator/EscalatorWindow.h b/Userland/Applications/Escalator/EscalatorWindow.h new file mode 100644 index 0000000000..21c51e7cc5 --- /dev/null +++ b/Userland/Applications/Escalator/EscalatorWindow.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2022, Ashley N. <dev-serenity@ne0ndrag0n.com> + * Copyright (c) 2022, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/String.h> +#include <AK/StringView.h> +#include <AK/Vector.h> +#include <LibCore/Account.h> +#include <LibCore/Object.h> +#include <LibGUI/Button.h> +#include <LibGUI/ImageWidget.h> +#include <LibGUI/TextBox.h> +#include <LibGUI/Window.h> + +class EscalatorWindow final : public GUI::Window { + C_OBJECT(EscalatorWindow) +public: + struct Options { + StringView description; + Core::Account current_user; + bool preserve_env { false }; + }; + + virtual ~EscalatorWindow() override = default; + + ErrorOr<void> execute_command(); + +private: + EscalatorWindow(StringView executable, Vector<StringView> arguments, Options const& options); + + ErrorOr<void> check_password(); + + Vector<StringView> m_arguments; + StringView m_executable; + Core::Account m_current_user; + bool m_preserve_env { false }; + + RefPtr<GUI::ImageWidget> m_icon_image_widget; + RefPtr<GUI::Button> m_ok_button; + RefPtr<GUI::Button> m_cancel_button; + RefPtr<GUI::PasswordBox> m_password_input; +}; diff --git a/Userland/Applications/Escalator/main.cpp b/Userland/Applications/Escalator/main.cpp new file mode 100644 index 0000000000..0570487e67 --- /dev/null +++ b/Userland/Applications/Escalator/main.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2022, Ashley N. <dev-serenity@ne0ndrag0n.com> + * Copyright (c) 2022, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "EscalatorWindow.h" +#include <AK/String.h> +#include <LibCore/Account.h> +#include <LibCore/ArgsParser.h> +#include <LibCore/File.h> +#include <LibCore/System.h> +#include <LibGUI/Application.h> +#include <LibGUI/Desktop.h> +#include <LibGUI/MessageBox.h> +#include <LibMain/Main.h> + +ErrorOr<int> serenity_main(Main::Arguments arguments) +{ + Vector<StringView> command; + Core::ArgsParser args_parser; + StringView description; + bool preserve_env = 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_positional_argument(command, "Command to run at elevated privilege level", "command"); + args_parser.parse(arguments); + + TRY(Core::System::pledge("stdio recvfd sendfd thread cpath rpath wpath unix proc exec id")); + + auto app = TRY(GUI::Application::try_create(arguments)); + + auto executable_path = Core::File::resolve_executable_from_environment(command[0]); + if (!executable_path.has_value()) { + GUI::MessageBox::show_error(nullptr, String::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 })); + + if (current_user.uid() != 0) { + window->show(); + return app->exec(); + } else { + // Run directly as root if already root uid. + TRY(window->execute_command()); + return 0; + } +} |