Add opt-in Linux renderer sandbox support to WebContent and WebWorker. Ladybird and test-web pass --enable-sandbox through when requested, and the renderer services only install the shared sandbox when that flag is present. Share one renderer policy for both services. Allow resource, font, shared library, WebGL, Wasm, audio, and local IPC paths needed at runtime, while keeping renderer filesystem writes mediated by Landlock. Allow Mesa and PulseAudio to probe their standard runtime state without escaping the renderer sandbox. Return EPERM for scheduler and priority changes so library initialization can fall back instead of crashing on a seccomp violation.
339 lines
14 KiB
C++
339 lines
14 KiB
C++
/*
|
|
* Copyright (c) 2020-2024, Andreas Kling <andreas@ladybird.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/LexicalPath.h>
|
|
#include <LibCore/ArgsParser.h>
|
|
#include <LibCore/EventLoop.h>
|
|
#include <LibCore/LocalServer.h>
|
|
#include <LibCore/Process.h>
|
|
#include <LibCore/Resource.h>
|
|
#include <LibCore/System.h>
|
|
#include <LibCore/TimeZone.h>
|
|
#include <LibCrypto/OpenSSLForward.h>
|
|
#include <LibGfx/Font/FontDatabase.h>
|
|
#include <LibGfx/Font/PathFontProvider.h>
|
|
#include <LibGfx/SkiaBackendContext.h>
|
|
#include <LibIPC/ConnectionFromClient.h>
|
|
#include <LibIPC/TransportHandle.h>
|
|
#include <LibMain/Main.h>
|
|
#include <LibRequests/RequestClient.h>
|
|
#include <LibUnicode/TimeZone.h>
|
|
#include <LibWeb/Bindings/MainThreadVM.h>
|
|
#include <LibWeb/DOM/Document.h>
|
|
#include <LibWeb/Fetch/Fetching/Fetching.h>
|
|
#include <LibWeb/HTML/UniversalGlobalScope.h>
|
|
#include <LibWeb/HTML/Window.h>
|
|
#include <LibWeb/Internals/Internals.h>
|
|
#include <LibWeb/Loader/ContentBlocker.h>
|
|
#include <LibWeb/Loader/GeneratedPagesLoader.h>
|
|
#include <LibWeb/Loader/ResourceLoader.h>
|
|
#include <LibWeb/Painting/PaintableBox.h>
|
|
#include <LibWeb/Platform/EventLoopPlugin.h>
|
|
#include <LibWeb/Platform/FontPlugin.h>
|
|
#include <LibWeb/WebIDL/Tracing.h>
|
|
#include <LibWebView/Plugins/ImageCodecPlugin.h>
|
|
#include <LibWebView/SiteIsolation.h>
|
|
#include <LibWebView/Utilities.h>
|
|
#include <Services/RendererSandbox.h>
|
|
#include <WebContent/ConnectionFromClient.h>
|
|
#include <WebContent/PageClient.h>
|
|
#include <WebContent/WebContentCompositorHost.h>
|
|
#include <WebContent/WebDriverConnection.h>
|
|
|
|
#include <openssl/thread.h>
|
|
|
|
#if defined(AK_OS_MACOS)
|
|
# include <LibIPC/Transport.h>
|
|
# include <LibIPC/TransportBootstrapMach.h>
|
|
#else
|
|
# include <LibIPC/SingleServer.h>
|
|
#endif
|
|
|
|
#if defined(AK_OS_WINDOWS)
|
|
# include <objbase.h>
|
|
#endif
|
|
|
|
#include <SDL3/SDL_init.h>
|
|
|
|
#if !defined(AK_OS_WINDOWS)
|
|
# include <signal.h>
|
|
static void crash_signal_handler(int signo)
|
|
{
|
|
char const* name;
|
|
switch (signo) {
|
|
case SIGSEGV:
|
|
name = "SIGSEGV";
|
|
break;
|
|
case SIGBUS:
|
|
name = "SIGBUS";
|
|
break;
|
|
case SIGFPE:
|
|
name = "SIGFPE";
|
|
break;
|
|
case SIGABRT:
|
|
name = "SIGABRT";
|
|
break;
|
|
case SIGILL:
|
|
name = "SIGILL";
|
|
break;
|
|
default:
|
|
name = "unknown";
|
|
break;
|
|
}
|
|
warnln("\n\033[31;1mCRASH\033[0m: Received signal {} ({})", name, signo);
|
|
dump_backtrace(2, 100);
|
|
Core::Process::terminate_immediately(128 + signo);
|
|
}
|
|
|
|
static void install_crash_signal_handlers()
|
|
{
|
|
struct sigaction sa;
|
|
sa.sa_handler = crash_signal_handler;
|
|
sigemptyset(&sa.sa_mask);
|
|
sa.sa_flags = SA_RESETHAND;
|
|
sigaction(SIGSEGV, &sa, nullptr);
|
|
sigaction(SIGBUS, &sa, nullptr);
|
|
sigaction(SIGFPE, &sa, nullptr);
|
|
sigaction(SIGABRT, &sa, nullptr);
|
|
sigaction(SIGILL, &sa, nullptr);
|
|
}
|
|
#endif
|
|
|
|
static ErrorOr<void> load_content_blockers(StringView config_path);
|
|
|
|
static ErrorOr<void> connect_to_resource_loader(GC::Heap& heap, IPC::TransportHandle const& handle);
|
|
static ErrorOr<void> connect_to_image_decoder(IPC::TransportHandle const& handle);
|
|
|
|
ErrorOr<int> ladybird_main(Main::Arguments arguments)
|
|
{
|
|
AK::set_rich_debug_enabled(true);
|
|
|
|
#if !defined(AK_OS_WINDOWS)
|
|
install_crash_signal_handlers();
|
|
#endif
|
|
|
|
#if defined(AK_OS_WINDOWS)
|
|
// NOTE: We need this here otherwise SDL inits COM in the APARTMENTTHREADED model which we don't want as we need to
|
|
// make calls across threads which would otherwise have a high overhead. It is safe for all the objects we use.
|
|
HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED);
|
|
VERIFY(SUCCEEDED(hr));
|
|
ScopeGuard uninitialize_com = []() { CoUninitialize(); };
|
|
#endif
|
|
// SDL is used for the Gamepad API.
|
|
if (!SDL_Init(SDL_INIT_GAMEPAD)) {
|
|
dbgln("Failed to initialize SDL3: {}", SDL_GetError());
|
|
return -1;
|
|
}
|
|
|
|
auto& event_loop = Core::EventLoop::initialize_for_current_thread();
|
|
|
|
WebView::platform_init();
|
|
|
|
Web::Platform::EventLoopPlugin::install(*new Web::Platform::EventLoopPlugin);
|
|
|
|
auto config_path = ByteString::formatted("{}/ladybird/default-config", WebView::s_ladybird_resource_root);
|
|
StringView mach_server_name {};
|
|
Vector<ByteString> certificates;
|
|
bool enable_test_mode = false;
|
|
bool expose_experimental_interfaces = false;
|
|
bool expose_internals_object = false;
|
|
bool wait_for_debugger = false;
|
|
bool log_all_js_exceptions = false;
|
|
bool disable_site_isolation = false;
|
|
bool enable_idl_tracing = false;
|
|
bool enable_http_memory_cache = false;
|
|
bool force_cpu_painting = false;
|
|
bool force_fontconfig = false;
|
|
bool collect_garbage_on_every_allocation = false;
|
|
bool is_headless = false;
|
|
bool disable_scrollbar_painting = false;
|
|
bool disable_async_scrolling = false;
|
|
bool enable_sandbox = false;
|
|
StringView echo_server_port_string_view {};
|
|
StringView default_time_zone {};
|
|
StringView style_invalidation_counter_dump_interval {};
|
|
bool file_origins_are_tuple_origins = false;
|
|
|
|
Core::ArgsParser args_parser;
|
|
args_parser.add_option(config_path, "Ladybird configuration path", "config-path", 0, "config_path");
|
|
args_parser.add_option(enable_test_mode, "Enable test mode", "test-mode");
|
|
args_parser.add_option(expose_experimental_interfaces, "Expose experimental IDL interfaces", "expose-experimental-interfaces");
|
|
args_parser.add_option(expose_internals_object, "Expose internals object", "expose-internals-object");
|
|
args_parser.add_option(certificates, "Path to a certificate file", "certificate", 'C', "certificate");
|
|
args_parser.add_option(wait_for_debugger, "Wait for debugger", "wait-for-debugger");
|
|
args_parser.add_option(mach_server_name, "Mach server name", "mach-server-name", 0, "mach_server_name");
|
|
args_parser.add_option(log_all_js_exceptions, "Log all JavaScript exceptions", "log-all-js-exceptions");
|
|
args_parser.add_option(disable_site_isolation, "Disable site isolation", "disable-site-isolation");
|
|
args_parser.add_option(enable_idl_tracing, "Enable IDL tracing", "enable-idl-tracing");
|
|
args_parser.add_option(enable_http_memory_cache, "Enable HTTP cache", "enable-http-memory-cache");
|
|
args_parser.add_option(force_cpu_painting, "Force CPU painting", "force-cpu-painting");
|
|
args_parser.add_option(force_fontconfig, "Force using fontconfig for font loading", "force-fontconfig");
|
|
args_parser.add_option(collect_garbage_on_every_allocation, "Collect garbage after every JS heap allocation", "collect-garbage-on-every-allocation");
|
|
args_parser.add_option(disable_scrollbar_painting, "Don't paint horizontal or vertical viewport scrollbars", "disable-scrollbar-painting");
|
|
args_parser.add_option(disable_async_scrolling, "Disable async scrolling", "disable-async-scrolling");
|
|
args_parser.add_option(enable_sandbox, "Enable process sandboxing", "enable-sandbox");
|
|
args_parser.add_option(echo_server_port_string_view, "Echo server port used in test internals", "echo-server-port", 0, "echo_server_port");
|
|
args_parser.add_option(is_headless, "Report that the browser is running in headless mode", "headless");
|
|
args_parser.add_option(default_time_zone, "Default time zone", "default-time-zone", 0, "time-zone-id");
|
|
args_parser.add_option(style_invalidation_counter_dump_interval, "Dump style invalidation counters after every N style invalidations", "dump-style-invalidation-counters", 0, "N");
|
|
args_parser.add_option(file_origins_are_tuple_origins, "Treat file:// URLs as having tuple origins", "tuple-file-origins");
|
|
|
|
args_parser.parse(arguments);
|
|
|
|
if (wait_for_debugger) {
|
|
Core::Process::wait_for_debugger_and_break();
|
|
}
|
|
|
|
if (!default_time_zone.is_empty()) {
|
|
if (auto result = Core::TimeZone::set_current_time_zone(default_time_zone); result.is_error())
|
|
dbgln("Failed to set default time zone: {}", result.error());
|
|
}
|
|
|
|
if (!style_invalidation_counter_dump_interval.is_empty()) {
|
|
auto interval = style_invalidation_counter_dump_interval.to_number<u64>();
|
|
if (!interval.has_value() || *interval == 0)
|
|
VERIFY_NOT_REACHED();
|
|
Web::DOM::Document::set_style_invalidation_counter_dump_interval(*interval);
|
|
}
|
|
|
|
if (file_origins_are_tuple_origins)
|
|
URL::set_file_scheme_urls_have_tuple_origins();
|
|
|
|
auto& font_provider = static_cast<Gfx::PathFontProvider&>(Gfx::FontDatabase::the().install_system_font_provider(make<Gfx::PathFontProvider>()));
|
|
if (force_fontconfig) {
|
|
font_provider.set_name_but_fixme_should_create_custom_system_font_provider("FontConfig"_string);
|
|
}
|
|
font_provider.load_all_fonts_from_uri("resource://fonts"sv);
|
|
|
|
// Always use the CPU backend for tests, as the GPU backend is not deterministic
|
|
if (force_cpu_painting) {
|
|
WebContent::PageClient::set_use_skia_painter(WebContent::PageClient::UseSkiaPainter::CPUBackend);
|
|
} else {
|
|
Gfx::SkiaBackendContext::initialize_gpu_backend();
|
|
WebContent::PageClient::set_use_skia_painter(WebContent::PageClient::UseSkiaPainter::GPUBackendIfAvailable);
|
|
}
|
|
|
|
WebContent::PageClient::set_is_headless(is_headless);
|
|
|
|
if (disable_site_isolation)
|
|
WebView::disable_site_isolation();
|
|
|
|
if (enable_http_memory_cache)
|
|
Web::Fetch::Fetching::set_http_memory_cache_enabled(true);
|
|
|
|
Web::Painting::set_paint_viewport_scrollbars(!disable_scrollbar_painting);
|
|
WebContent::PageClient::set_async_scrolling_enabled(!disable_async_scrolling);
|
|
|
|
if (!echo_server_port_string_view.is_empty()) {
|
|
if (auto maybe_echo_server_port = echo_server_port_string_view.to_number<u16>(); maybe_echo_server_port.has_value())
|
|
Web::Internals::Internals::set_echo_server_port(maybe_echo_server_port.value());
|
|
else
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
|
|
OPENSSL_TRY(OSSL_set_max_threads(nullptr, Core::System::hardware_concurrency()));
|
|
|
|
Web::HTML::Window::set_enable_test_mode(enable_test_mode);
|
|
Web::HTML::Window::set_internals_object_exposed(expose_internals_object);
|
|
Web::HTML::UniversalGlobalScopeMixin::set_experimental_interfaces_exposed(expose_experimental_interfaces);
|
|
|
|
Web::Platform::FontPlugin::install(*new Web::Platform::FontPlugin(enable_test_mode, &font_provider));
|
|
|
|
Web::Bindings::initialize_main_thread_vm(Web::Bindings::AgentType::SimilarOriginWindow);
|
|
|
|
if (collect_garbage_on_every_allocation)
|
|
Web::Bindings::main_thread_vm().heap().set_should_collect_on_every_allocation(true);
|
|
|
|
if (log_all_js_exceptions) {
|
|
JS::set_log_all_js_exceptions(true);
|
|
}
|
|
|
|
if (enable_idl_tracing) {
|
|
Web::WebIDL::set_enable_idl_tracing(true);
|
|
}
|
|
|
|
auto maybe_content_blocker_error = load_content_blockers(config_path);
|
|
if (maybe_content_blocker_error.is_error())
|
|
dbgln("Failed to load content blockers: {}", maybe_content_blocker_error.error());
|
|
|
|
if (enable_sandbox)
|
|
TRY(RendererSandbox::apply_sandbox(config_path));
|
|
|
|
#if defined(AK_OS_MACOS)
|
|
auto browser_port = TRY(Core::MachPort::look_up_from_bootstrap_server(ByteString { mach_server_name }));
|
|
auto transport_ports = TRY(IPC::bootstrap_transport_from_server_port(browser_port));
|
|
auto webcontent_client = WebContent::ConnectionFromClient::construct(
|
|
make<IPC::Transport>(move(transport_ports.receive_right), move(transport_ports.send_right)));
|
|
#else
|
|
auto webcontent_client = TRY(IPC::take_over_accepted_client_from_system_server<WebContent::ConnectionFromClient>(mach_server_name));
|
|
#endif
|
|
|
|
auto& heap = Web::Bindings::main_thread_vm().heap();
|
|
webcontent_client->on_request_server_connection = [&heap](auto const& handle) {
|
|
if (auto result = connect_to_resource_loader(heap, handle); result.is_error())
|
|
dbgln("Failed to connect to resource loader: {}", result.error());
|
|
};
|
|
webcontent_client->on_image_decoder_connection = [](auto const& handle) {
|
|
if (auto result = connect_to_image_decoder(handle); result.is_error())
|
|
dbgln("Failed to connect to image decoder: {}", result.error());
|
|
};
|
|
|
|
return event_loop.exec();
|
|
}
|
|
|
|
static ErrorOr<void> load_content_blockers(StringView config_path)
|
|
{
|
|
auto buffer = TRY(ByteBuffer::create_uninitialized(4096));
|
|
|
|
auto file = TRY(Core::File::open(ByteString::formatted("{}/BrowserContentBlockers.txt", config_path), Core::File::OpenMode::Read));
|
|
auto content_blocker_list = TRY(Core::InputBufferedFile::create(move(file)));
|
|
|
|
Vector<String> patterns;
|
|
|
|
while (TRY(content_blocker_list->can_read_line())) {
|
|
auto line = TRY(content_blocker_list->read_line(buffer));
|
|
if (line.is_empty())
|
|
continue;
|
|
|
|
auto pattern = TRY(String::from_utf8(line));
|
|
patterns.append(move(pattern));
|
|
}
|
|
|
|
auto& content_blocker = Web::ContentBlocker::the();
|
|
TRY(content_blocker.set_patterns(patterns));
|
|
|
|
return {};
|
|
}
|
|
|
|
ErrorOr<void> connect_to_resource_loader(GC::Heap& heap, IPC::TransportHandle const& handle)
|
|
{
|
|
auto transport = TRY(handle.create_transport());
|
|
auto request_client = TRY(try_make_ref_counted<Requests::RequestClient>(move(transport)));
|
|
#ifdef AK_OS_WINDOWS
|
|
auto response = request_client->send_sync<Messages::RequestServer::InitTransport>(Core::System::getpid());
|
|
request_client->transport().set_peer_pid(response->peer_pid());
|
|
#endif
|
|
if (Web::ResourceLoader::is_initialized())
|
|
Web::ResourceLoader::the().set_client(move(request_client));
|
|
else
|
|
Web::ResourceLoader::initialize(heap, move(request_client));
|
|
return {};
|
|
}
|
|
|
|
ErrorOr<void> connect_to_image_decoder(IPC::TransportHandle const& handle)
|
|
{
|
|
auto transport = TRY(handle.create_transport());
|
|
auto new_client = TRY(try_make_ref_counted<ImageDecoderClient::Client>(move(transport)));
|
|
#ifdef AK_OS_WINDOWS
|
|
auto response = new_client->send_sync<Messages::ImageDecoderServer::InitTransport>(Core::System::getpid());
|
|
new_client->transport().set_peer_pid(response->peer_pid());
|
|
#endif
|
|
if (Web::Platform::ImageCodecPlugin::is_initialized())
|
|
static_cast<WebView::ImageCodecPlugin&>(Web::Platform::ImageCodecPlugin::the()).set_client(move(new_client));
|
|
else
|
|
Web::Platform::ImageCodecPlugin::install(*new WebView::ImageCodecPlugin(move(new_client)));
|
|
return {};
|
|
}
|