From b4456ecdbbe6aa9e0187c06458acc51e50ced512 Mon Sep 17 00:00:00 2001 From: ne0ndrag0n Date: Sat, 1 Oct 2022 00:40:28 -0400 Subject: [PATCH] Escalator: Add new method to privilege escalate within GUI --- Base/res/icons/16x16/app-escalator.png | Bin 0 -> 229 bytes Base/res/icons/32x32/app-escalator.png | Bin 0 -> 409 bytes Meta/build-root-filesystem.sh | 4 + Userland/Applications/CMakeLists.txt | 1 + .../Applications/Escalator/CMakeLists.txt | 19 +++ Userland/Applications/Escalator/Escalator.gml | 49 ++++++++ .../Escalator/EscalatorWindow.cpp | 114 ++++++++++++++++++ .../Applications/Escalator/EscalatorWindow.h | 47 ++++++++ Userland/Applications/Escalator/main.cpp | 53 ++++++++ 9 files changed, 287 insertions(+) create mode 100644 Base/res/icons/16x16/app-escalator.png create mode 100644 Base/res/icons/32x32/app-escalator.png create mode 100644 Userland/Applications/Escalator/CMakeLists.txt create mode 100644 Userland/Applications/Escalator/Escalator.gml create mode 100644 Userland/Applications/Escalator/EscalatorWindow.cpp create mode 100644 Userland/Applications/Escalator/EscalatorWindow.h create mode 100644 Userland/Applications/Escalator/main.cpp diff --git a/Base/res/icons/16x16/app-escalator.png b/Base/res/icons/16x16/app-escalator.png new file mode 100644 index 0000000000000000000000000000000000000000..d238ca0bcefa0b8d72709163a72d8285c28cdf5d GIT binary patch literal 229 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4rT@h1`S>QU*3Q$dj_zvqX2Y_+(x|L(TSS9ac)`qX`RMtlr! z!(4sl&6D|=nN>xn^)YT*?iK(1*s%~E!xOP>%S3{&)E<5DoYUKv(Wvx@vbe+jj@lKX cs_&m)*50SU^zN{`emlsyp00i_>zopr07%1C$N&HU literal 0 HcmV?d00001 diff --git a/Base/res/icons/32x32/app-escalator.png b/Base/res/icons/32x32/app-escalator.png new file mode 100644 index 0000000000000000000000000000000000000000..e387ca93952b3cb32da384206d8a28fd55b1372f GIT binary patch literal 409 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4rT@h2A3sW#~2tG`~rMJTp7S1GBPqcIyx&W ztGv8?`t<2DQyAvYpTA|xmO~8;Cz=_~o;`bSD%ay#f-mRty`IJNWvRf|wJKjXnSR|H z^lgXD_XBD_&#C{qq4EE6gJi5tJp%&+V@Z%-FoVOh8)*y-3_m?x978NlZ=L*lQnLb& zo99x)ur;jHz3=~bHD{bT?b;>#-G#>rpULcRa9sLtvEFz6Kfj9K7iU+qeLkC)&*owK z<5v%e4WPvT7pA#ZNYDV*9VuG12nji}3e* zqSLO{oV?>Yn=!L&_UD7xjz^D&V(7b6*G z&h+#!JnH|vv-gO$?*c2f^~#UTo9(!7F7P|FZ}z^1zbv8kjrObJWfuz-zFxVn7Zk*v Lu6{1-oD!M + * Copyright (c) 2022, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "EscalatorWindow.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +EscalatorWindow::EscalatorWindow(StringView executable, Vector 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(); + main_widget.load_from_gml(escalator_gml); + + RefPtr app_label = *main_widget.find_descendant_of_type_named("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("icon"); + m_icon_image_widget->set_bitmap(app_icon.bitmap_for_size(32)); + + m_ok_button = *main_widget.find_descendant_of_type_named("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("cancel_button"); + m_cancel_button->on_click = [this](auto) { + close(); + }; + + m_password_input = *main_widget.find_descendant_of_type_named("password"); +} + +ErrorOr 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 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); + } + + // 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. + * Copyright (c) 2022, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 execute_command(); + +private: + EscalatorWindow(StringView executable, Vector arguments, Options const& options); + + ErrorOr check_password(); + + Vector m_arguments; + StringView m_executable; + Core::Account m_current_user; + bool m_preserve_env { false }; + + RefPtr m_icon_image_widget; + RefPtr m_ok_button; + RefPtr m_cancel_button; + RefPtr 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. + * Copyright (c) 2022, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "EscalatorWindow.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +ErrorOr serenity_main(Main::Arguments arguments) +{ + Vector 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; + } +}