diff --git a/Libraries/LibSandbox/Sandbox.cpp b/Libraries/LibSandbox/Sandbox.cpp index acb8178e0e..702b0510e9 100644 --- a/Libraries/LibSandbox/Sandbox.cpp +++ b/Libraries/LibSandbox/Sandbox.cpp @@ -131,6 +131,8 @@ ErrorOr restrict_filesystem_with_landlock(ReadonlySpan 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 diff --git a/Libraries/LibSandbox/Sandbox.h b/Libraries/LibSandbox/Sandbox.h index f3741c2edf..960ba204a0 100644 --- a/Libraries/LibSandbox/Sandbox.h +++ b/Libraries/LibSandbox/Sandbox.h @@ -17,6 +17,7 @@ namespace Sandbox { struct LandlockPath { enum class Access { ReadOnly, + ReadAndExecute, ReadWrite, }; diff --git a/Libraries/LibSandbox/Seccomp.cpp b/Libraries/LibSandbox/Seccomp.cpp index 238b865668..78519ea169 100644 --- a/Libraries/LibSandbox/Seccomp.cpp +++ b/Libraries/LibSandbox/Seccomp.cpp @@ -10,10 +10,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -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() diff --git a/Libraries/LibSandbox/Seccomp.h b/Libraries/LibSandbox/Seccomp.h index 76bb62d0cf..8e123d00cd 100644 --- a/Libraries/LibSandbox/Seccomp.h +++ b/Libraries/LibSandbox/Seccomp.h @@ -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(); diff --git a/Libraries/LibWebView/HelperProcess.cpp b/Libraries/LibWebView/HelperProcess.cpp index ba3b8a9a9a..c25818a490 100644 --- a/Libraries/LibWebView/HelperProcess.cpp +++ b/Libraries/LibWebView/HelperProcess.cpp @@ -122,6 +122,8 @@ ErrorOr> 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> launch_compositor_process() ErrorOr> 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 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) diff --git a/Services/RendererSandbox.h b/Services/RendererSandbox.h new file mode 100644 index 0000000000..4e7e901341 --- /dev/null +++ b/Services/RendererSandbox.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2026-present, the Ladybird developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace RendererSandbox { + +[[nodiscard]] ErrorOr apply_sandbox(Optional config_path); + +} diff --git a/Services/RendererSandboxLinux.cpp b/Services/RendererSandboxLinux.cpp new file mode 100644 index 0000000000..61188f8204 --- /dev/null +++ b/Services/RendererSandboxLinux.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2026-present, the Ladybird developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace RendererSandbox { + +ErrorOr apply_sandbox(Optional 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 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 {}; +} + +} diff --git a/Services/RendererSandboxUnimplemented.cpp b/Services/RendererSandboxUnimplemented.cpp new file mode 100644 index 0000000000..e26bf955ca --- /dev/null +++ b/Services/RendererSandboxUnimplemented.cpp @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2026-present, the Ladybird developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +namespace RendererSandbox { + +ErrorOr apply_sandbox(Optional) +{ + return {}; +} + +} diff --git a/Services/WebContent/CMakeLists.txt b/Services/WebContent/CMakeLists.txt index f353ca14be..06d8ae6bc3 100644 --- a/Services/WebContent/CMakeLists.txt +++ b/Services/WebContent/CMakeLists.txt @@ -38,7 +38,13 @@ target_compile_options(webcontentservice PRIVATE $<$:-Wexit-time-destructors>) if(WIN32) diff --git a/Services/WebContent/main.cpp b/Services/WebContent/main.cpp index ec436d09a6..2762f8faa7 100644 --- a/Services/WebContent/main.cpp +++ b/Services/WebContent/main.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -150,6 +151,7 @@ ErrorOr 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 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 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)); diff --git a/Services/WebWorker/CMakeLists.txt b/Services/WebWorker/CMakeLists.txt index 6b24cd1c23..418e004b71 100644 --- a/Services/WebWorker/CMakeLists.txt +++ b/Services/WebWorker/CMakeLists.txt @@ -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 $) diff --git a/Services/WebWorker/main.cpp b/Services/WebWorker/main.cpp index 062b9f61d7..adb62ad6c9 100644 --- a/Services/WebWorker/main.cpp +++ b/Services/WebWorker/main.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -56,6 +57,7 @@ ErrorOr 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 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 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(mach_server_name)); auto& heap = Web::Bindings::main_thread_vm().heap();