mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-22 07:30:19 +00:00
Escalator: Add new method to privilege escalate within GUI
This commit is contained in:
parent
a143d666db
commit
b4456ecdbb
Notes:
sideshowbarker
2024-07-17 05:58:36 +09:00
Author: https://github.com/ne0ndrag0n Commit: https://github.com/SerenityOS/serenity/commit/b4456ecdbb Pull-request: https://github.com/SerenityOS/serenity/pull/15446 Reviewed-by: https://github.com/ADKaster ✅ Reviewed-by: https://github.com/linusg Reviewed-by: https://github.com/timschumi ✅
9 changed files with 287 additions and 0 deletions
BIN
Base/res/icons/16x16/app-escalator.png
Normal file
BIN
Base/res/icons/16x16/app-escalator.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 229 B |
BIN
Base/res/icons/32x32/app-escalator.png
Normal file
BIN
Base/res/icons/32x32/app-escalator.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 409 B |
|
@ -97,6 +97,10 @@ if [ -f mnt/bin/pls ]; then
|
|||
chown 0:$wheel_gid mnt/bin/pls
|
||||
chmod 4750 mnt/bin/pls
|
||||
fi
|
||||
if [ -f mnt/bin/Escalator ]; then
|
||||
chown 0:$wheel_gid mnt/bin/Escalator
|
||||
chmod 4750 mnt/bin/Escalator
|
||||
fi
|
||||
if [ -f mnt/bin/utmpupdate ]; then
|
||||
chown 0:$utmp_gid mnt/bin/utmpupdate
|
||||
chmod 2755 mnt/bin/utmpupdate
|
||||
|
|
|
@ -12,6 +12,7 @@ add_subdirectory(ClockSettings)
|
|||
add_subdirectory(CrashReporter)
|
||||
add_subdirectory(Debugger)
|
||||
add_subdirectory(DisplaySettings)
|
||||
add_subdirectory(Escalator)
|
||||
add_subdirectory(FileManager)
|
||||
add_subdirectory(FontEditor)
|
||||
add_subdirectory(GamesSettings)
|
||||
|
|
19
Userland/Applications/Escalator/CMakeLists.txt
Normal file
19
Userland/Applications/Escalator/CMakeLists.txt
Normal file
|
@ -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)
|
49
Userland/Applications/Escalator/Escalator.gml
Normal file
49
Userland/Applications/Escalator/Escalator.gml
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
114
Userland/Applications/Escalator/EscalatorWindow.cpp
Normal file
114
Userland/Applications/Escalator/EscalatorWindow.cpp
Normal file
|
@ -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();
|
||||
}
|
47
Userland/Applications/Escalator/EscalatorWindow.h
Normal file
47
Userland/Applications/Escalator/EscalatorWindow.h
Normal file
|
@ -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;
|
||||
};
|
53
Userland/Applications/Escalator/main.cpp
Normal file
53
Userland/Applications/Escalator/main.cpp
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue