WebContent: Sandbox renderer processes on Linux

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.
This commit is contained in:
Andreas Kling 2026-06-09 13:26:47 +02:00 committed by Andreas Kling
parent ea1ecdad5a
commit 0c7fda8735
12 changed files with 316 additions and 2 deletions

View file

@ -131,6 +131,8 @@ ErrorOr<void> restrict_filesystem_with_landlock(ReadonlySpan<LandlockPath> paths
landlock_path_beneath_attr path_beneath {};
path_beneath.allowed_access = LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR;
if (landlock_path.access == LandlockPath::Access::ReadAndExecute)
path_beneath.allowed_access |= LANDLOCK_ACCESS_FS_EXECUTE;
if (landlock_path.access == LandlockPath::Access::ReadWrite) {
path_beneath.allowed_access |= LANDLOCK_ACCESS_FS_WRITE_FILE
| LANDLOCK_ACCESS_FS_REMOVE_DIR

View file

@ -17,6 +17,7 @@ namespace Sandbox {
struct LandlockPath {
enum class Access {
ReadOnly,
ReadAndExecute,
ReadWrite,
};

View file

@ -10,10 +10,12 @@
#include <linux/audit.h>
#include <linux/sched.h>
#include <linux/seccomp.h>
#include <signal.h>
#include <stddef.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/ucontext.h>
#include <unistd.h>
@ -40,6 +42,7 @@ static constexpr u32 thread_clone_allowed_flags = thread_clone_required_flags
| CLONE_CHILD_CLEARTID
| CLONE_DETACHED
| CLONE_CHILD_SETTID;
static constexpr u32 vfork_clone_flags = CLONE_VM | CLONE_VFORK | SIGCHLD;
#ifdef O_LARGEFILE
static constexpr unsigned read_only_open_flags = O_CLOEXEC | O_LARGEFILE;
@ -81,6 +84,8 @@ static constexpr unsigned read_only_open_flags = O_CLOEXEC;
#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_execve(if_defined, if_not_defined) if_defined
#define IF_DEFINED_execveat(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
@ -115,6 +120,8 @@ static constexpr unsigned read_only_open_flags = O_CLOEXEC;
#define IF_DEFINED_madvise(if_defined, if_not_defined) if_defined
#define IF_DEFINED_membarrier(if_defined, if_not_defined) if_defined
#define IF_DEFINED_memfd_create(if_defined, if_not_defined) if_defined
#define IF_DEFINED_mkdir(if_defined, if_not_defined) if_defined
#define IF_DEFINED_mkdirat(if_defined, if_not_defined) if_defined
#define IF_DEFINED_mmap(if_defined, if_not_defined) if_defined
#define IF_DEFINED_mmap2(if_defined, if_not_defined) if_defined
#define IF_DEFINED_mprotect(if_defined, if_not_defined) if_defined
@ -142,6 +149,7 @@ static constexpr unsigned read_only_open_flags = O_CLOEXEC;
#define IF_DEFINED_recvmmsg(if_defined, if_not_defined) if_defined
#define IF_DEFINED_recvmsg(if_defined, if_not_defined) if_defined
#define IF_DEFINED_restart_syscall(if_defined, if_not_defined) if_defined
#define IF_DEFINED_rmdir(if_defined, if_not_defined) if_defined
#define IF_DEFINED_rseq(if_defined, if_not_defined) if_defined
#define IF_DEFINED_rt_sigaction(if_defined, if_not_defined) if_defined
#define IF_DEFINED_rt_sigprocmask(if_defined, if_not_defined) if_defined
@ -166,6 +174,8 @@ static constexpr unsigned read_only_open_flags = O_CLOEXEC;
#define IF_DEFINED_unlinkat(if_defined, if_not_defined) if_defined
#define IF_DEFINED_umask(if_defined, if_not_defined) if_defined
#define IF_DEFINED_uname(if_defined, if_not_defined) if_defined
#define IF_DEFINED_wait4(if_defined, if_not_defined) if_defined
#define IF_DEFINED_waitid(if_defined, if_not_defined) if_defined
#define IF_DEFINED_write(if_defined, if_not_defined) if_defined
#ifndef __NR_accept
@ -228,6 +238,14 @@ static constexpr unsigned read_only_open_flags = O_CLOEXEC;
# undef IF_DEFINED_eventfd2
# define IF_DEFINED_eventfd2(if_defined, if_not_defined) if_not_defined
#endif
#ifndef __NR_execve
# undef IF_DEFINED_execve
# define IF_DEFINED_execve(if_defined, if_not_defined) if_not_defined
#endif
#ifndef __NR_execveat
# undef IF_DEFINED_execveat
# define IF_DEFINED_execveat(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
@ -284,6 +302,14 @@ static constexpr unsigned read_only_open_flags = O_CLOEXEC;
# undef IF_DEFINED_memfd_create
# define IF_DEFINED_memfd_create(if_defined, if_not_defined) if_not_defined
#endif
#ifndef __NR_mkdir
# undef IF_DEFINED_mkdir
# define IF_DEFINED_mkdir(if_defined, if_not_defined) if_not_defined
#endif
#ifndef __NR_mkdirat
# undef IF_DEFINED_mkdirat
# define IF_DEFINED_mkdirat(if_defined, if_not_defined) if_not_defined
#endif
#ifndef __NR_mmap2
# undef IF_DEFINED_mmap2
# define IF_DEFINED_mmap2(if_defined, if_not_defined) if_not_defined
@ -332,6 +358,10 @@ static constexpr unsigned read_only_open_flags = O_CLOEXEC;
# undef IF_DEFINED_renameat2
# define IF_DEFINED_renameat2(if_defined, if_not_defined) if_not_defined
#endif
#ifndef __NR_rmdir
# undef IF_DEFINED_rmdir
# define IF_DEFINED_rmdir(if_defined, if_not_defined) if_not_defined
#endif
#ifndef __NR_ppoll
# undef IF_DEFINED_ppoll
# define IF_DEFINED_ppoll(if_defined, if_not_defined) if_not_defined
@ -400,6 +430,14 @@ static constexpr unsigned read_only_open_flags = O_CLOEXEC;
# undef IF_DEFINED_umask
# define IF_DEFINED_umask(if_defined, if_not_defined) if_not_defined
#endif
#ifndef __NR_wait4
# undef IF_DEFINED_wait4
# define IF_DEFINED_wait4(if_defined, if_not_defined) if_not_defined
#endif
#ifndef __NR_waitid
# undef IF_DEFINED_waitid
# define IF_DEFINED_waitid(if_defined, if_not_defined) if_not_defined
#endif
static size_t safe_string_length(char const* string)
{
@ -736,6 +774,9 @@ void SeccompPolicy::allow_filesystem_writes()
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, rename);
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, renameat);
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, renameat2);
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, mkdir);
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, mkdirat);
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, rmdir);
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, fsync);
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, fdatasync);
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, flock);
@ -806,6 +847,31 @@ void SeccompPolicy::allow_file_descriptor_operations()
append(SECCOMP_LOAD_SYSCALL_NR);
append(BPF_STMT(BPF_ALU | BPF_ADD | BPF_K, 0));
append(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_fcntl, 0, 5));
append(SECCOMP_LOAD_ARGUMENT(1));
append(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, F_DUPFD_CLOEXEC, 0, 1));
append(SECCOMP_ALLOW);
append(SECCOMP_LOAD_SYSCALL_NR);
append(BPF_STMT(BPF_ALU | BPF_ADD | BPF_K, 0));
#ifdef F_ADD_SEALS
append(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_fcntl, 0, 5));
append(SECCOMP_LOAD_ARGUMENT(1));
append(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, F_ADD_SEALS, 0, 1));
append(SECCOMP_ALLOW);
append(SECCOMP_LOAD_SYSCALL_NR);
append(BPF_STMT(BPF_ALU | BPF_ADD | BPF_K, 0));
#endif
#ifdef F_GET_SEALS
append(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_fcntl, 0, 5));
append(SECCOMP_LOAD_ARGUMENT(1));
append(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, F_GET_SEALS, 0, 1));
append(SECCOMP_ALLOW);
append(SECCOMP_LOAD_SYSCALL_NR);
append(BPF_STMT(BPF_ALU | BPF_ADD | BPF_K, 0));
#endif
append(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_ioctl, 0, 5));
append(SECCOMP_LOAD_ARGUMENT(1));
append(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, FIONBIO, 0, 1));
@ -828,6 +894,23 @@ void SeccompPolicy::allow_file_descriptor_operations()
append(BPF_STMT(BPF_ALU | BPF_ADD | BPF_K, 0));
}
void SeccompPolicy::allow_process_creation()
{
#ifdef __NR_clone
append(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_clone, 0, 5));
append(SECCOMP_LOAD_ARGUMENT(0));
append(BPF_STMT(BPF_ALU | BPF_AND | BPF_K, ~vfork_clone_flags));
append(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0, 0, 1));
append(SECCOMP_ALLOW);
append(SECCOMP_LOAD_SYSCALL_NR);
#endif
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, execve);
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, execveat);
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, wait4);
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, waitid);
}
void SeccompPolicy::allow_ipc()
{
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, eventfd2);
@ -841,6 +924,14 @@ void SeccompPolicy::allow_ipc()
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, sendto);
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, sendmmsg);
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, socketpair);
#ifdef __NR_socket
append(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_socket, 0, 3));
append(SECCOMP_LOAD_ARGUMENT(0));
append(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, AF_UNIX, 0, 1));
append(SECCOMP_ALLOW);
append(SECCOMP_LOAD_SYSCALL_NR);
#endif
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, connect);
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, getsockopt);
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, setsockopt);
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, getsockname);
@ -889,6 +980,41 @@ void SeccompPolicy::allow_memory_without_executable_mappings()
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, brk);
}
void SeccompPolicy::allow_executable_memory_mappings()
{
append(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_mmap, 0, 8));
append(SECCOMP_LOAD_ARGUMENT(2));
append(BPF_STMT(BPF_ALU | BPF_AND | BPF_K, PROT_EXEC));
append(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0, 4, 0));
append(SECCOMP_LOAD_ARGUMENT(2));
append(BPF_STMT(BPF_ALU | BPF_AND | BPF_K, PROT_WRITE));
append(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0, 0, 1));
append(SECCOMP_ALLOW);
append(SECCOMP_LOAD_SYSCALL_NR);
#ifdef __NR_mmap2
append(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_mmap2, 0, 8));
append(SECCOMP_LOAD_ARGUMENT(2));
append(BPF_STMT(BPF_ALU | BPF_AND | BPF_K, PROT_EXEC));
append(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0, 4, 0));
append(SECCOMP_LOAD_ARGUMENT(2));
append(BPF_STMT(BPF_ALU | BPF_AND | BPF_K, PROT_WRITE));
append(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0, 0, 1));
append(SECCOMP_ALLOW);
append(SECCOMP_LOAD_SYSCALL_NR);
#endif
append(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_mprotect, 0, 8));
append(SECCOMP_LOAD_ARGUMENT(2));
append(BPF_STMT(BPF_ALU | BPF_AND | BPF_K, PROT_EXEC));
append(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0, 4, 0));
append(SECCOMP_LOAD_ARGUMENT(2));
append(BPF_STMT(BPF_ALU | BPF_AND | BPF_K, PROT_WRITE));
append(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0, 0, 1));
append(SECCOMP_ALLOW);
append(SECCOMP_LOAD_SYSCALL_NR);
}
void SeccompPolicy::allow_threads()
{
#ifdef __NR_clone
@ -961,6 +1087,39 @@ void SeccompPolicy::allow_process_metadata()
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, sysinfo);
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, uname);
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, umask);
#ifdef __NR_sched_getscheduler
append(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_sched_getscheduler, 0, 1));
append(SECCOMP_ALLOW);
#endif
#ifdef __NR_sched_get_priority_max
append(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_sched_get_priority_max, 0, 1));
append(SECCOMP_ALLOW);
#endif
#ifdef __NR_sched_get_priority_min
append(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_sched_get_priority_min, 0, 1));
append(SECCOMP_ALLOW);
#endif
#ifdef __NR_getpriority
append(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_getpriority, 0, 1));
append(SECCOMP_ALLOW);
#endif
#ifdef __NR_setpriority
append(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_setpriority, 0, 1));
append(SECCOMP_ERRNO(EPERM));
#endif
#ifdef __NR_sched_setscheduler
append(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_sched_setscheduler, 0, 1));
append(SECCOMP_ERRNO(EPERM));
#endif
#ifdef __NR_sched_setparam
append(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_sched_setparam, 0, 1));
append(SECCOMP_ERRNO(EPERM));
#endif
#ifdef __NR_sched_setaffinity
append(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_sched_setaffinity, 0, 1));
append(SECCOMP_ERRNO(EPERM));
#endif
}
void SeccompPolicy::allow_common_runtime()

View file

@ -21,9 +21,11 @@ public:
void allow_filesystem_metadata_queries();
void allow_filesystem_writes();
void allow_file_descriptor_operations();
void allow_process_creation();
void allow_ipc();
void allow_network();
void allow_memory_without_executable_mappings();
void allow_executable_memory_mappings();
void allow_threads();
void allow_signals();
void allow_clocks();

View file

@ -122,6 +122,8 @@ ErrorOr<NonnullRefPtr<WebView::WebContentClient>> launch_web_content_process(u64
arguments.append("--disable-async-scrolling"sv);
if (web_content_options.file_scheme_urls_have_tuple_origins == FileSchemeUrlsHaveTupleOrigins::Yes)
arguments.append("--tuple-file-origins"sv);
if (browser_options.enable_sandbox == EnableSandbox::Yes)
arguments.append("--enable-sandbox"sv);
if (auto const maybe_echo_server_port = web_content_options.echo_server_port; maybe_echo_server_port.has_value()) {
arguments.append("--echo-server-port"sv);
@ -183,10 +185,13 @@ ErrorOr<NonnullRefPtr<WebView::CompositorClient>> launch_compositor_process()
ErrorOr<NonnullRefPtr<WebWorkerClient>> launch_web_worker_process(Web::Bindings::AgentType type, Web::HTML::WorkerAgentId agent_id)
{
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.expose_experimental_interfaces == WebView::ExposeExperimentalInterfaces::Yes)
arguments.append("--expose-experimental-interfaces"sv);
if (web_content_options.enable_http_memory_cache == WebView::EnableMemoryHTTPCache::Yes)

View file

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

View file

@ -0,0 +1,88 @@
/*
* Copyright (c) 2026-present, the Ladybird developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/LexicalPath.h>
#include <LibCore/Directory.h>
#include <LibCore/Environment.h>
#include <LibCore/StandardPaths.h>
#include <LibCore/System.h>
#include <LibGfx/Font/FontDatabase.h>
#include <LibSandbox/Sandbox.h>
#include <LibSandbox/Seccomp.h>
#include <LibWebView/Utilities.h>
#include <Services/RendererSandbox.h>
namespace RendererSandbox {
ErrorOr<void> apply_sandbox(Optional<StringView> config_path)
{
TRY(Sandbox::install_no_new_privileges());
TRY(Sandbox::configure_runtime());
auto executable_path = TRY(Core::System::current_executable_path());
auto build_root = LexicalPath::dirname(LexicalPath::dirname(executable_path));
Vector<Sandbox::LandlockPath> paths;
TRY(Sandbox::add_landlock_path_if_exists(paths, WebView::s_ladybird_resource_root, Sandbox::LandlockPath::Access::ReadOnly));
if (config_path.has_value())
TRY(Sandbox::add_landlock_path_if_exists(paths, *config_path, Sandbox::LandlockPath::Access::ReadOnly));
// cpptrace opens loaded ELF objects when symbolizing in-process stack traces.
TRY(Sandbox::add_landlock_path_if_exists(paths, executable_path, Sandbox::LandlockPath::Access::ReadOnly));
TRY(Sandbox::add_landlock_path_if_exists(paths, LexicalPath::join(build_root, "lib"sv).string(), Sandbox::LandlockPath::Access::ReadOnly));
TRY(Sandbox::add_landlock_path_if_exists(paths, "/proc/self"sv, Sandbox::LandlockPath::Access::ReadOnly));
TRY(Sandbox::add_landlock_path_if_exists(paths, "/lib"sv, Sandbox::LandlockPath::Access::ReadOnly));
TRY(Sandbox::add_landlock_path_if_exists(paths, "/lib64"sv, Sandbox::LandlockPath::Access::ReadOnly));
TRY(Sandbox::add_landlock_path_if_exists(paths, "/usr/lib"sv, Sandbox::LandlockPath::Access::ReadOnly));
TRY(Sandbox::add_landlock_path_if_exists(paths, "/usr/local/lib"sv, Sandbox::LandlockPath::Access::ReadOnly));
TRY(Sandbox::add_landlock_path_if_exists(paths, "/etc/glvnd"sv, Sandbox::LandlockPath::Access::ReadOnly));
TRY(Sandbox::add_landlock_path_if_exists(paths, "/usr/share/glvnd"sv, Sandbox::LandlockPath::Access::ReadOnly));
TRY(Sandbox::add_landlock_path_if_exists(paths, "/usr/share/drirc.d"sv, Sandbox::LandlockPath::Access::ReadOnly));
TRY(Sandbox::add_landlock_path_if_exists(paths, "/usr/share/vulkan"sv, Sandbox::LandlockPath::Access::ReadOnly));
TRY(Sandbox::add_landlock_path_if_exists(paths, "/dev/dri"sv, Sandbox::LandlockPath::Access::ReadWrite));
TRY(Sandbox::add_landlock_path_if_exists(paths, "/sys"sv, Sandbox::LandlockPath::Access::ReadOnly));
if (auto library_path = Core::Environment::get("LD_LIBRARY_PATH"sv); library_path.has_value()) {
for (auto path : library_path->split_view(':'))
TRY(Sandbox::add_landlock_path_if_exists(paths, path, Sandbox::LandlockPath::Access::ReadOnly));
}
for (auto const& path : TRY(Gfx::FontDatabase::font_directories()))
TRY(Sandbox::add_landlock_path_if_exists(paths, path, Sandbox::LandlockPath::Access::ReadOnly));
if (auto cranelift_compiler_path = Core::Environment::get("LADYBIRD_CRANELIFT_COMPILER"sv); cranelift_compiler_path.has_value()) {
TRY(Sandbox::add_landlock_path_if_exists(paths, *cranelift_compiler_path, Sandbox::LandlockPath::Access::ReadAndExecute));
} else {
auto default_cranelift_compiler_path = LexicalPath::join(build_root, "bin/cranelift-compiler"sv).string();
TRY(Sandbox::add_landlock_path_if_exists(paths, default_cranelift_compiler_path, Sandbox::LandlockPath::Access::ReadAndExecute));
}
auto mesa_shader_cache_path = Core::Environment::get("MESA_SHADER_CACHE_DIR"sv)
.map([](auto path) { return path.to_byte_string(); })
.value_or_lazy_evaluated([] { return ByteString::formatted("{}/mesa_shader_cache", Core::StandardPaths::cache_directory()); });
TRY(Core::Directory::create(mesa_shader_cache_path, Core::Directory::CreateDirectories::Yes));
TRY(Sandbox::add_landlock_path_if_exists(paths, mesa_shader_cache_path, Sandbox::LandlockPath::Access::ReadWrite));
auto pulse_runtime_path = LexicalPath::join(TRY(Core::StandardPaths::runtime_directory()), "pulse"sv).string();
TRY(Core::Directory::create(pulse_runtime_path, Core::Directory::CreateDirectories::Yes, 0700));
TRY(Sandbox::add_landlock_path_if_exists(paths, pulse_runtime_path, Sandbox::LandlockPath::Access::ReadWrite));
TRY(Sandbox::add_landlock_path_if_exists(paths, LexicalPath::join(Core::StandardPaths::config_directory(), "pulse"sv).string(), Sandbox::LandlockPath::Access::ReadOnly));
TRY(Sandbox::restrict_filesystem_with_landlock(paths.span()));
Sandbox::SeccompPolicy policy;
policy.allow_readonly_file_opens();
policy.allow_filesystem_metadata_queries();
policy.allow_filesystem_writes();
policy.allow_file_descriptor_operations();
policy.allow_process_creation();
policy.allow_ipc();
policy.allow_gpu_device_operations();
policy.allow_common_runtime();
policy.allow_executable_memory_mappings();
TRY(policy.install());
return {};
}
}

View file

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

View file

@ -38,7 +38,13 @@ target_compile_options(webcontentservice PRIVATE $<$<COMPILE_LANG_AND_ID:CXX,Cla
add_executable(WebContent main.cpp)
target_link_libraries(WebContent PRIVATE webcontentservice LibURL)
if (LINUX)
target_sources(WebContent PRIVATE ../RendererSandboxLinux.cpp)
else()
target_sources(WebContent PRIVATE ../RendererSandboxUnimplemented.cpp)
endif()
target_link_libraries(WebContent PRIVATE webcontentservice LibSandbox LibURL)
target_compile_options(WebContent PRIVATE $<$<COMPILE_LANG_AND_ID:CXX,Clang,AppleClang>:-Wexit-time-destructors>)
if(WIN32)

View file

@ -37,6 +37,7 @@
#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>
@ -150,6 +151,7 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
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 {};
@ -172,6 +174,7 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
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");
@ -256,6 +259,9 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
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));

View file

@ -18,8 +18,14 @@ target_link_libraries(webworkerservice PRIVATE OpenSSL::Crypto OpenSSL::SSL)
add_executable(WebWorker main.cpp)
if (LINUX)
target_sources(WebWorker PRIVATE ../RendererSandboxLinux.cpp)
else()
target_sources(WebWorker PRIVATE ../RendererSandboxUnimplemented.cpp)
endif()
target_include_directories(WebWorker PRIVATE ${LADYBIRD_SOURCE_DIR})
target_link_libraries(WebWorker PRIVATE webworkerservice OpenSSL::Crypto OpenSSL::SSL)
target_link_libraries(WebWorker PRIVATE webworkerservice LibSandbox OpenSSL::Crypto OpenSSL::SSL)
if(WIN32)
target_include_directories(WebWorker PRIVATE $<BUILD_INTERFACE:${PTHREAD_INCLUDE_DIR}>)

View file

@ -25,6 +25,7 @@
#include <LibWeb/Platform/FontPlugin.h>
#include <LibWebView/Plugins/ImageCodecPlugin.h>
#include <LibWebView/Utilities.h>
#include <Services/RendererSandbox.h>
#include <WebWorker/ConnectionFromClient.h>
#include <openssl/thread.h>
@ -56,6 +57,7 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
bool enable_http_memory_cache = false;
bool wait_for_debugger = false;
bool file_origins_are_tuple_origins = false;
bool enable_sandbox = false;
Core::ArgsParser args_parser;
args_parser.add_option(serenity_resource_root, "Absolute path to directory for serenity resources", "serenity-resource-root", 'r', "serenity-resource-root");
@ -66,6 +68,7 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
args_parser.add_option(worker_type_string, "Type of WebWorker to start (dedicated, shared, or service)", "type", 't', "type");
args_parser.add_option(mach_server_name, "Mach server name", "mach-server-name", 0, "mach_server_name");
args_parser.add_option(file_origins_are_tuple_origins, "Treat file:// URLs as having tuple origins", "tuple-file-origins");
args_parser.add_option(enable_sandbox, "Enable process sandboxing", "enable-sandbox");
args_parser.parse(arguments);
@ -94,6 +97,9 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
Web::Bindings::initialize_main_thread_vm(worker_type);
if (enable_sandbox)
TRY(RendererSandbox::apply_sandbox({}));
auto client = TRY(IPC::take_over_accepted_client_from_system_server<WebWorker::ConnectionFromClient>(mach_server_name));
auto& heap = Web::Bindings::main_thread_vm().heap();