Compositor: Sandbox the process on Linux

Add opt-in Linux sandbox support to Compositor. Ladybird and test-web
pass --enable-sandbox through when requested, and Compositor only
installs the sandbox when that flag is present.

Install the sandbox after Compositor has initialized platform, font, and
GPU state so startup probing can complete before filesystem access is
removed. Compose the runtime seccomp policy from LibSandbox building
blocks and add an explicit GPU device operations group for driver IPC
through already-open descriptors.
This commit is contained in:
Andreas Kling 2026-06-09 11:08:46 +02:00 committed by Andreas Kling
parent ba87a32626
commit a69fd61eaf
8 changed files with 113 additions and 1 deletions

View file

@ -72,6 +72,11 @@ static constexpr unsigned read_only_open_flags = O_CLOEXEC;
#define IF_DEFINED_close(if_defined, if_not_defined) if_defined
#define IF_DEFINED_dup(if_defined, if_not_defined) if_defined
#define IF_DEFINED_dup3(if_defined, if_not_defined) if_defined
#define IF_DEFINED_epoll_create1(if_defined, if_not_defined) if_defined
#define IF_DEFINED_epoll_ctl(if_defined, if_not_defined) if_defined
#define IF_DEFINED_epoll_pwait(if_defined, if_not_defined) if_defined
#define IF_DEFINED_epoll_wait(if_defined, if_not_defined) if_defined
#define IF_DEFINED_eventfd2(if_defined, if_not_defined) if_defined
#define IF_DEFINED_exit(if_defined, if_not_defined) if_defined
#define IF_DEFINED_exit_group(if_defined, if_not_defined) if_defined
#define IF_DEFINED_fcntl(if_defined, if_not_defined) if_defined
@ -161,6 +166,26 @@ static constexpr unsigned read_only_open_flags = O_CLOEXEC;
# undef IF_DEFINED_dup3
# define IF_DEFINED_dup3(if_defined, if_not_defined) if_not_defined
#endif
#ifndef __NR_epoll_create1
# undef IF_DEFINED_epoll_create1
# define IF_DEFINED_epoll_create1(if_defined, if_not_defined) if_not_defined
#endif
#ifndef __NR_epoll_ctl
# undef IF_DEFINED_epoll_ctl
# define IF_DEFINED_epoll_ctl(if_defined, if_not_defined) if_not_defined
#endif
#ifndef __NR_epoll_pwait
# undef IF_DEFINED_epoll_pwait
# define IF_DEFINED_epoll_pwait(if_defined, if_not_defined) if_not_defined
#endif
#ifndef __NR_epoll_wait
# undef IF_DEFINED_epoll_wait
# define IF_DEFINED_epoll_wait(if_defined, if_not_defined) if_not_defined
#endif
#ifndef __NR_eventfd2
# undef IF_DEFINED_eventfd2
# define IF_DEFINED_eventfd2(if_defined, if_not_defined) if_not_defined
#endif
#ifndef __NR_fcntl64
# undef IF_DEFINED_fcntl64
# define IF_DEFINED_fcntl64(if_defined, if_not_defined) if_not_defined
@ -681,6 +706,16 @@ void SeccompPolicy::allow_clocks()
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, restart_syscall);
}
void SeccompPolicy::allow_gpu_device_operations()
{
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, ioctl);
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, eventfd2);
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, epoll_create1);
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, epoll_ctl);
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, epoll_wait);
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, epoll_pwait);
}
void SeccompPolicy::allow_process_metadata()
{
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, getpid);

View file

@ -23,6 +23,7 @@ public:
void allow_threads();
void allow_signals();
void allow_clocks();
void allow_gpu_device_operations();
void allow_process_metadata();
void allow_common_runtime();
void allow_prctl();

View file

@ -161,9 +161,12 @@ ErrorOr<NonnullRefPtr<ImageDecoderClient::Client>> launch_image_decoder_process(
ErrorOr<NonnullRefPtr<WebView::CompositorClient>> launch_compositor_process()
{
auto const& browser_options = WebView::Application::browser_options();
auto const& web_content_options = WebView::Application::web_content_options();
Vector<ByteString> arguments;
if (browser_options.enable_sandbox == EnableSandbox::Yes)
arguments.append("--enable-sandbox"sv);
if (web_content_options.force_cpu_painting == WebView::ForceCPUPainting::Yes)
arguments.append("--force-cpu-painting"sv);
if (web_content_options.force_fontconfig == WebView::ForceFontconfig::Yes)

View file

@ -20,10 +20,16 @@ ladybird_generated_sources(compositorservice)
add_executable(Compositor main.cpp)
if (LINUX)
target_sources(Compositor PRIVATE SandboxLinux.cpp)
else()
target_sources(Compositor PRIVATE SandboxUnimplemented.cpp)
endif()
target_include_directories(compositorservice PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/../..)
target_include_directories(compositorservice PRIVATE ${LADYBIRD_SOURCE_DIR}/Services/)
target_link_libraries(Compositor PRIVATE compositorservice LibCore LibMain LibWebView)
target_link_libraries(Compositor PRIVATE compositorservice LibCore LibMain LibSandbox LibWebView)
target_link_libraries(compositorservice PRIVATE LibCore LibGfx LibIPC LibMedia LibSync LibWeb)
if (APPLE)

View file

@ -0,0 +1,15 @@
/*
* Copyright (c) 2026-present, the Ladybird developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Error.h>
namespace Compositor {
[[nodiscard]] ErrorOr<void> apply_sandbox();
}

View file

@ -0,0 +1,30 @@
/*
* Copyright (c) 2026-present, the Ladybird developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Compositor/Sandbox.h>
#include <LibSandbox/Sandbox.h>
#include <LibSandbox/Seccomp.h>
namespace Compositor {
ErrorOr<void> apply_sandbox()
{
TRY(Sandbox::install_no_new_privileges());
TRY(Sandbox::configure_runtime());
TRY(Sandbox::restrict_filesystem_with_landlock());
Sandbox::SeccompPolicy policy;
policy.deny_readonly_filesystem_probes();
policy.allow_file_descriptor_operations();
policy.allow_ipc();
policy.allow_gpu_device_operations();
policy.allow_common_runtime();
TRY(policy.install());
return {};
}
}

View file

@ -0,0 +1,16 @@
/*
* Copyright (c) 2026-present, the Ladybird developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Compositor/Sandbox.h>
namespace Compositor {
ErrorOr<void> apply_sandbox()
{
return {};
}
}

View file

@ -5,6 +5,7 @@
*/
#include <Compositor/ConnectionFromClient.h>
#include <Compositor/Sandbox.h>
#include <LibCore/ArgsParser.h>
#include <LibCore/EventLoop.h>
#include <LibCore/Process.h>
@ -24,6 +25,7 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
bool force_cpu_painting = false;
bool force_fontconfig = false;
bool disable_async_scrolling = false;
bool enable_sandbox = false;
Core::ArgsParser args_parser;
args_parser.add_option(mach_server_name, "Mach server name", "mach-server-name", 0, "mach_server_name");
@ -31,6 +33,7 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
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(disable_async_scrolling, "Disable async scrolling", "disable-async-scrolling");
args_parser.add_option(enable_sandbox, "Enable process sandboxing", "enable-sandbox");
args_parser.parse(arguments);
if (wait_for_debugger)
@ -45,6 +48,9 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
Gfx::SkiaBackendContext::initialize_gpu_backend();
auto skia_backend_context = Gfx::SkiaBackendContext::the_main_thread_context();
if (enable_sandbox)
TRY(Compositor::apply_sandbox());
auto& event_loop = Core::EventLoop::initialize_for_current_thread();
auto client = TRY(IPC::take_over_accepted_client_from_system_server<Compositor::ConnectionFromClient>(
mach_server_name, move(skia_backend_context), !disable_async_scrolling));