diff --git a/Libraries/LibSandbox/Sandbox.cpp b/Libraries/LibSandbox/Sandbox.cpp index 702b0510e9..0dff64dcb5 100644 --- a/Libraries/LibSandbox/Sandbox.cpp +++ b/Libraries/LibSandbox/Sandbox.cpp @@ -19,6 +19,22 @@ # include #endif +#if defined(AK_OS_MACOS) +# include +# include +# include +# include +# include +# include +# include +# include +# include + +extern "C" { +int sandbox_init_with_parameters(char const* profile, u64 flags, char const* const parameters[], char** errorbuf); +} +#endif + #if defined(__GLIBC__) # include #endif @@ -43,9 +59,9 @@ ErrorOr configure_runtime() return {}; } +#if defined(AK_OS_LINUX) ErrorOr add_landlock_path_if_exists(Vector& paths, StringView path, LandlockPath::Access access) { -#if defined(AK_OS_LINUX) auto path_bytes = path.to_byte_string(); struct stat statbuf; @@ -64,17 +80,342 @@ ErrorOr add_landlock_path_if_exists(Vector& paths, StringVie } TRY(paths.try_append({ move(path_bytes), access })); -#else - (void)paths; - (void)path; - (void)access; + return {}; +} #endif + +#if defined(AK_OS_MACOS) +ErrorOr add_seatbelt_path_if_exists(Vector& paths, StringView path, SeatbeltPath::Access access) +{ + auto path_bytes = path.to_byte_string(); + + struct stat statbuf; + if (stat(path_bytes.characters(), &statbuf) < 0) { + if (errno == ENOENT) + return {}; + return Error::from_syscall("stat"sv, errno); + } + + char resolved_path[PATH_MAX]; + if (realpath(path_bytes.characters(), resolved_path) == nullptr) + return Error::from_syscall("realpath"sv, errno); + path_bytes = resolved_path; + + auto is_directory = S_ISDIR(statbuf.st_mode); + + for (auto const& existing_path : paths) { + if (existing_path.access == access && existing_path.path == path_bytes) + return {}; + } + + TRY(paths.try_append({ move(path_bytes), access, is_directory })); return {}; } +static void append_sandbox_string_literal(StringBuilder& builder, StringView string) +{ + builder.append('"'); + for (auto ch : string.bytes()) { + if (ch == '"' || ch == '\\') + builder.append('\\'); + builder.append(static_cast(ch)); + } + builder.append('"'); +} + +static void append_sandbox_path_filter(StringBuilder& builder, SeatbeltPath const& path) +{ + builder.append(path.is_directory ? "(subpath "sv : "(literal "sv); + append_sandbox_string_literal(builder, path.path); + builder.append(')'); +} + +static bool seatbelt_path_allows_access(SeatbeltPath::Access path_access, SeatbeltPath::Access requested_access) +{ + if (requested_access == SeatbeltPath::Access::ReadOnly) + return true; + return path_access == requested_access; +} + +static ErrorOr append_allowed_paths(StringBuilder& builder, StringView operation, ReadonlySpan paths, SeatbeltPath::Access access) +{ + bool emitted_header = false; + for (auto const& path : paths) { + if (!seatbelt_path_allows_access(path.access, access)) + continue; + + if (!emitted_header) { + builder.append("(allow "sv); + builder.append(operation); + emitted_header = true; + } + builder.append(' '); + append_sandbox_path_filter(builder, path); + } + + if (emitted_header) + builder.append(")\n"sv); + + return {}; +} + +static ErrorOr append_allowed_path_extensions(StringBuilder& builder, ReadonlySpan paths, SeatbeltPath::Access access) +{ + auto extension_class = access == SeatbeltPath::Access::ReadWrite ? "com.apple.app-sandbox.read-write"sv : "com.apple.app-sandbox.read"sv; + + bool emitted_header = false; + for (auto const& path : paths) { + if (!seatbelt_path_allows_access(path.access, access)) + continue; + + if (!emitted_header) { + builder.append("(allow file-issue-extension"sv); + emitted_header = true; + } + builder.append(" (require-all (extension-class "sv); + append_sandbox_string_literal(builder, extension_class); + builder.append(") "sv); + append_sandbox_path_filter(builder, path); + builder.append(')'); + } + + if (emitted_header) + builder.append(")\n"sv); + + return {}; +} + +static ErrorOr append_allowed_executables(StringBuilder& builder, ReadonlySpan executable_paths) +{ + if (executable_paths.is_empty()) + return {}; + + builder.append("(allow process-exec"sv); + for (auto const& path : executable_paths) { + builder.append(" (literal "sv); + append_sandbox_string_literal(builder, path); + builder.append(')'); + } + builder.append(")\n"sv); + + return {}; +} + +static void sandbox_violation_signal_handler(int) +{ + char const message[] = "Sandbox violation: terminating process\n"; + [[maybe_unused]] auto nwritten = write(STDERR_FILENO, message, sizeof(message) - 1); + _exit(128 + SIGSYS); +} + +static ErrorOr install_sandbox_violation_signal_handler() +{ + struct sigaction action {}; + action.sa_handler = sandbox_violation_signal_handler; + sigemptyset(&action.sa_mask); + action.sa_flags = SA_RESETHAND; + if (sigaction(SIGSYS, &action, nullptr) < 0) + return Error::from_syscall("sigaction(SIGSYS)"sv, errno); + return {}; +} + +ErrorOr apply_macos_sandbox(ReadonlySpan paths, NetworkAccess network_access, ReadonlySpan executable_paths) +{ + TRY(install_sandbox_violation_signal_handler()); + + StringBuilder profile; + TRY(profile.try_append(R"~~~( +(version 1) +(deny default + (with send-signal SIGSYS) + (with message "Ladybird macOS sandbox default deny")) + +(allow process-info*) +(allow signal (target self)) +(allow sysctl-read) +(allow system*) +(allow ipc*) +(allow mach*) +(allow iokit-open-user-client + (iokit-user-client-class "IOSurfaceRootUserClient")) +(allow user-preference-read + (preference-domain "kCFPreferencesAnyApplication") + (preference-domain "org.ladybird.ladybird")) + +(allow network-outbound + (literal "/private/var/run/syslog")) + +(deny syscall-unix + (with send-signal SIGKILL) + (with message "Ladybird macOS sandbox syscall deny")) + +(allow syscall-unix + (syscall-group-bsdthread) + (syscall-group-close) + (syscall-group-fcntl) + (syscall-group-getfsstat) + (syscall-group-kevent) + (syscall-group-kqueue) + (syscall-group-mkdir) + (syscall-group-open) + (syscall-group-open-dprotected) + (syscall-group-pthread) + (syscall-group-pthread-cv) + (syscall-group-pthread-locks) + (syscall-group-read) + (syscall-group-recv) + (syscall-group-rlimit) + (syscall-group-select) + (syscall-group-send) + (syscall-group-signal) + (syscall-group-sockopt) + (syscall-group-stat) + (syscall-group-statfs) + (syscall-group-ulock) + (syscall-group-write) + (syscall-number + SYS___disable_threadsignal + SYS___channel_open + SYS___mac_syscall + SYS___semwait_signal + SYS___semwait_signal_nocancel + SYS_abort_with_payload + SYS_access + SYS_change_fdguard_np + SYS_connect + SYS_crossarch_trap + SYS_csops_audittoken + SYS_csrctl + SYS_dup + SYS_exit + SYS_faccessat + SYS_fileport_makefd + SYS_fileport_makeport + SYS_fgetattrlist + SYS_fgetxattr + SYS_flock + SYS_fsgetpath + SYS_fsync + SYS_ftruncate + SYS_getaudit_addr + SYS_getattrlist + SYS_getattrlistbulk + SYS_getdirentries64 + SYS_getentropy + SYS_getegid + SYS_geteuid + SYS_getgid + SYS_gethostuuid + SYS_getpeername + SYS_getpid + SYS_getrusage + SYS_getsockname + SYS_gettid + SYS_gettimeofday + SYS_getuid + SYS_getxattr + SYS_ioctl + SYS_issetugid + SYS_kdebug_trace + SYS_kdebug_trace64 + SYS_kdebug_trace_string + SYS_kdebug_typefilter + SYS_listxattr + SYS_lseek + SYS_madvise + SYS_mlock + SYS_mmap + SYS_mprotect + SYS_mremap_encrypted + SYS_msync + SYS_munlock + SYS_munmap + SYS_necp_client_action + SYS_necp_open + SYS_open + SYS_open_nocancel + SYS_openat + SYS_os_fault_with_payload + SYS_pathconf + SYS_pipe + SYS_poll + SYS_posix_spawn + SYS_proc_info + SYS_readlink + SYS_rename + SYS_rmdir + SYS_sendfile + SYS_shm_open + SYS_shared_region_check_np + SYS_shared_region_map_and_slide_2_np + SYS_socket + SYS_socketpair + SYS_sysctl + SYS_sysctlbyname + SYS_thread_selfid + SYS_umask + SYS_wait4 + SYS_work_interval_ctl + SYS_workq_kernreturn + SYS_workq_open)) + +(allow file-read-metadata) +(allow file-read* + (literal "/") + (literal "/dev/dtracehelper") + (literal "/dev/null") + (literal "/dev/random") + (literal "/dev/urandom") + (literal "/private/etc/localtime") + (subpath "/private/etc/ssl") + (subpath "/System") + (subpath "/Library/Preferences/Logging") + (subpath "/private/var/db/timezone") + (subpath "/usr/lib") + (subpath "/usr/share")) + +(allow file-map-executable + (subpath "/System") + (subpath "/usr/lib")) + +(allow file-write-data file-ioctl + (literal "/dev/dtracehelper")) +)~~~"sv)); + + if (network_access == NetworkAccess::Allowed) + TRY(profile.try_append("(allow network*)\n"sv)); + + TRY(append_allowed_paths(profile, "file-read*"sv, paths, SeatbeltPath::Access::ReadOnly)); + TRY(append_allowed_paths(profile, "file-map-executable"sv, paths, SeatbeltPath::Access::ReadAndExecute)); + TRY(append_allowed_paths(profile, "file-write*"sv, paths, SeatbeltPath::Access::ReadWrite)); + TRY(append_allowed_path_extensions(profile, paths, SeatbeltPath::Access::ReadOnly)); + TRY(append_allowed_path_extensions(profile, paths, SeatbeltPath::Access::ReadWrite)); + TRY(append_allowed_executables(profile, executable_paths)); + + auto profile_string = profile.to_byte_string(); + + char* errorbuf = nullptr; +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdeprecated-declarations" + auto result = sandbox_init_with_parameters(profile_string.characters(), 0, nullptr, &errorbuf); +# pragma clang diagnostic pop + if (result < 0) { + if (errorbuf) { +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdeprecated-declarations" + sandbox_free_error(errorbuf); +# pragma clang diagnostic pop + } + return Error::from_string_literal("sandbox_init_with_parameters failed"); + } + return {}; +} +#endif + +#if defined(AK_OS_LINUX) ErrorOr restrict_filesystem_with_landlock(ReadonlySpan paths) { -#if defined(AK_OS_LINUX) && defined(__NR_landlock_create_ruleset) && defined(__NR_landlock_add_rule) && defined(__NR_landlock_restrict_self) +# if defined(__NR_landlock_create_ruleset) && defined(__NR_landlock_add_rule) && defined(__NR_landlock_restrict_self) auto landlock_abi = syscall(__NR_landlock_create_ruleset, nullptr, 0, LANDLOCK_CREATE_RULESET_VERSION); if (landlock_abi < 0) { if (errno == ENOSYS || errno == EOPNOTSUPP || errno == EINVAL) @@ -99,19 +440,19 @@ ErrorOr restrict_filesystem_with_landlock(ReadonlySpan paths | LANDLOCK_ACCESS_FS_MAKE_BLOCK | LANDLOCK_ACCESS_FS_MAKE_SYM; -# ifdef LANDLOCK_ACCESS_FS_REFER +# ifdef LANDLOCK_ACCESS_FS_REFER if (landlock_abi >= 2) ruleset_attributes.handled_access_fs |= LANDLOCK_ACCESS_FS_REFER; -# endif -# ifdef LANDLOCK_ACCESS_FS_TRUNCATE +# endif +# ifdef LANDLOCK_ACCESS_FS_TRUNCATE if (landlock_abi >= 3) ruleset_attributes.handled_access_fs |= LANDLOCK_ACCESS_FS_TRUNCATE; -# endif -# if defined(LANDLOCK_ACCESS_NET_BIND_TCP) && defined(LANDLOCK_ACCESS_NET_CONNECT_TCP) +# endif +# if defined(LANDLOCK_ACCESS_NET_BIND_TCP) && defined(LANDLOCK_ACCESS_NET_CONNECT_TCP) auto ruleset_attributes_size = offsetof(landlock_ruleset_attr, handled_access_net); -# else +# else auto ruleset_attributes_size = sizeof(ruleset_attributes); -# endif +# endif auto ruleset_fd = syscall(__NR_landlock_create_ruleset, &ruleset_attributes, ruleset_attributes_size, 0); if (ruleset_fd < 0) return Error::from_syscall("landlock_create_ruleset"sv, errno); @@ -141,14 +482,14 @@ ErrorOr restrict_filesystem_with_landlock(ReadonlySpan paths | LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_MAKE_SOCK | LANDLOCK_ACCESS_FS_MAKE_FIFO; -# ifdef LANDLOCK_ACCESS_FS_REFER +# ifdef LANDLOCK_ACCESS_FS_REFER if (landlock_abi >= 2) path_beneath.allowed_access |= LANDLOCK_ACCESS_FS_REFER; -# endif -# ifdef LANDLOCK_ACCESS_FS_TRUNCATE +# endif +# ifdef LANDLOCK_ACCESS_FS_TRUNCATE if (landlock_abi >= 3) path_beneath.allowed_access |= LANDLOCK_ACCESS_FS_TRUNCATE; -# endif +# endif } path_beneath.parent_fd = path_fd; if (syscall(__NR_landlock_add_rule, ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, &path_beneath, 0) < 0) @@ -157,9 +498,9 @@ ErrorOr restrict_filesystem_with_landlock(ReadonlySpan paths if (syscall(__NR_landlock_restrict_self, ruleset_fd, 0) < 0) return Error::from_syscall("landlock_restrict_self"sv, errno); -#else +# else (void)paths; -#endif +# endif return {}; } @@ -171,5 +512,6 @@ ErrorOr restrict_filesystem_with_landlock(ReadonlySpan readabl TRY(paths.try_append({ readable_path.to_byte_string(), LandlockPath::Access::ReadOnly })); return restrict_filesystem_with_landlock(paths.span()); } +#endif } diff --git a/Libraries/LibSandbox/Sandbox.h b/Libraries/LibSandbox/Sandbox.h index 960ba204a0..26fefb4042 100644 --- a/Libraries/LibSandbox/Sandbox.h +++ b/Libraries/LibSandbox/Sandbox.h @@ -8,12 +8,14 @@ #include #include +#include #include #include #include namespace Sandbox { +#if defined(AK_OS_LINUX) struct LandlockPath { enum class Access { ReadOnly, @@ -24,11 +26,39 @@ struct LandlockPath { ByteString path; Access access { Access::ReadOnly }; }; +#endif + +#if defined(AK_OS_MACOS) +struct SeatbeltPath { + enum class Access { + ReadOnly, + ReadAndExecute, + ReadWrite, + }; + + ByteString path; + Access access { Access::ReadOnly }; + bool is_directory { false }; +}; + +enum class NetworkAccess { + Denied, + Allowed, +}; +#endif [[nodiscard]] ErrorOr install_no_new_privileges(); [[nodiscard]] ErrorOr configure_runtime(); + +#if defined(AK_OS_LINUX) [[nodiscard]] ErrorOr add_landlock_path_if_exists(Vector& paths, StringView path, LandlockPath::Access); [[nodiscard]] ErrorOr restrict_filesystem_with_landlock(ReadonlySpan); [[nodiscard]] ErrorOr restrict_filesystem_with_landlock(ReadonlySpan readable_paths = {}); +#endif + +#if defined(AK_OS_MACOS) +[[nodiscard]] ErrorOr add_seatbelt_path_if_exists(Vector& paths, StringView path, SeatbeltPath::Access); +[[nodiscard]] ErrorOr apply_macos_sandbox(ReadonlySpan, NetworkAccess, ReadonlySpan executable_paths = {}); +#endif } diff --git a/Services/Compositor/CMakeLists.txt b/Services/Compositor/CMakeLists.txt index e6dee92202..837635ceef 100644 --- a/Services/Compositor/CMakeLists.txt +++ b/Services/Compositor/CMakeLists.txt @@ -22,6 +22,8 @@ add_executable(Compositor main.cpp) if (LINUX) target_sources(Compositor PRIVATE SandboxLinux.cpp) +elseif (APPLE) + target_sources(Compositor PRIVATE SandboxMacOS.cpp) else() target_sources(Compositor PRIVATE SandboxUnimplemented.cpp) endif() diff --git a/Services/Compositor/SandboxMacOS.cpp b/Services/Compositor/SandboxMacOS.cpp new file mode 100644 index 0000000000..3257b4fa8d --- /dev/null +++ b/Services/Compositor/SandboxMacOS.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2026-present, the Ladybird developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Compositor { + +ErrorOr apply_sandbox() +{ + TRY(Sandbox::configure_runtime()); + + auto executable_path = TRY(Core::System::current_executable_path()); + auto build_root = LexicalPath::dirname(LexicalPath::dirname(LexicalPath::dirname(LexicalPath::dirname(LexicalPath::dirname(executable_path))))); + + Vector paths; + TRY(Sandbox::add_seatbelt_path_if_exists(paths, executable_path, Sandbox::SeatbeltPath::Access::ReadOnly)); + TRY(Sandbox::add_seatbelt_path_if_exists(paths, LexicalPath::join(build_root, "bin"sv).string(), Sandbox::SeatbeltPath::Access::ReadOnly)); + TRY(Sandbox::add_seatbelt_path_if_exists(paths, LexicalPath::join(build_root, "lib"sv).string(), Sandbox::SeatbeltPath::Access::ReadAndExecute)); + TRY(Sandbox::add_seatbelt_path_if_exists(paths, LexicalPath::join(build_root, "vcpkg_installed"sv).string(), Sandbox::SeatbeltPath::Access::ReadAndExecute)); + + for (auto const& path : TRY(Gfx::FontDatabase::font_directories())) + TRY(Sandbox::add_seatbelt_path_if_exists(paths, path, Sandbox::SeatbeltPath::Access::ReadOnly)); + TRY(Sandbox::add_seatbelt_path_if_exists(paths, TRY(String::formatted("{}/fonts", WebView::s_ladybird_resource_root)), Sandbox::SeatbeltPath::Access::ReadOnly)); + + auto cache_path = Core::StandardPaths::cache_directory(); + auto skia_cache_path = TRY(String::formatted("{}/Ladybird", cache_path)); + TRY(Core::Directory::create(skia_cache_path.to_byte_string(), Core::Directory::CreateDirectories::Yes)); + TRY(Sandbox::add_seatbelt_path_if_exists(paths, skia_cache_path, Sandbox::SeatbeltPath::Access::ReadWrite)); + + char darwin_user_cache_directory[PATH_MAX]; + if (confstr(_CS_DARWIN_USER_CACHE_DIR, darwin_user_cache_directory, sizeof(darwin_user_cache_directory)) > 0) { + StringView darwin_user_cache_directory_view { darwin_user_cache_directory, strlen(darwin_user_cache_directory) }; + TRY(Sandbox::add_seatbelt_path_if_exists(paths, darwin_user_cache_directory_view, Sandbox::SeatbeltPath::Access::ReadWrite)); + if (darwin_user_cache_directory_view.starts_with("/var/"sv)) { + auto private_darwin_user_cache_directory = TRY(String::formatted("/private{}", darwin_user_cache_directory)); + TRY(Sandbox::add_seatbelt_path_if_exists(paths, private_darwin_user_cache_directory, Sandbox::SeatbeltPath::Access::ReadWrite)); + } + } + + return Sandbox::apply_macos_sandbox(paths.span(), Sandbox::NetworkAccess::Denied); +} + +} diff --git a/Services/ImageDecoder/CMakeLists.txt b/Services/ImageDecoder/CMakeLists.txt index be8343e469..d5831cba0f 100644 --- a/Services/ImageDecoder/CMakeLists.txt +++ b/Services/ImageDecoder/CMakeLists.txt @@ -24,6 +24,8 @@ if (LINUX) if (ENABLE_ADDRESS_SANITIZER) target_sources(ImageDecoder PRIVATE LeakSanitizer.cpp) endif() +elseif (APPLE) + target_sources(ImageDecoder PRIVATE SandboxMacOS.cpp) else() target_sources(ImageDecoder PRIVATE SandboxUnimplemented.cpp) endif() diff --git a/Services/ImageDecoder/SandboxMacOS.cpp b/Services/ImageDecoder/SandboxMacOS.cpp new file mode 100644 index 0000000000..135e74b00f --- /dev/null +++ b/Services/ImageDecoder/SandboxMacOS.cpp @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2026-present, the Ladybird developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace ImageDecoder { + +ErrorOr apply_sandbox() +{ + TRY(Sandbox::configure_runtime()); + + Vector paths; + return Sandbox::apply_macos_sandbox(paths.span(), Sandbox::NetworkAccess::Denied); +} + +} diff --git a/Services/RendererSandboxMacOS.cpp b/Services/RendererSandboxMacOS.cpp new file mode 100644 index 0000000000..b4818ddefd --- /dev/null +++ b/Services/RendererSandboxMacOS.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2026-present, the Ladybird developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace RendererSandbox { + +static ErrorOr> canonicalized_path_if_exists(StringView path) +{ + auto path_bytes = path.to_byte_string(); + + char resolved_path[PATH_MAX]; + if (realpath(path_bytes.characters(), resolved_path) == nullptr) { + if (errno == ENOENT || errno == ENOTDIR) + return OptionalNone {}; + return Error::from_syscall("realpath"sv, errno); + } + return ByteString { resolved_path }; +} + +ErrorOr apply_sandbox(Optional config_path) +{ + TRY(Sandbox::configure_runtime()); + + auto executable_path = TRY(Core::System::current_executable_path()); + auto build_root = LexicalPath::dirname(LexicalPath::dirname(LexicalPath::dirname(LexicalPath::dirname(LexicalPath::dirname(executable_path))))); + + Vector paths; + TRY(Sandbox::add_seatbelt_path_if_exists(paths, WebView::s_ladybird_resource_root, Sandbox::SeatbeltPath::Access::ReadOnly)); + if (config_path.has_value()) + TRY(Sandbox::add_seatbelt_path_if_exists(paths, *config_path, Sandbox::SeatbeltPath::Access::ReadOnly)); + TRY(Sandbox::add_seatbelt_path_if_exists(paths, executable_path, Sandbox::SeatbeltPath::Access::ReadOnly)); + TRY(Sandbox::add_seatbelt_path_if_exists(paths, LexicalPath::join(build_root, "bin"sv).string(), Sandbox::SeatbeltPath::Access::ReadOnly)); + TRY(Sandbox::add_seatbelt_path_if_exists(paths, LexicalPath::join(build_root, "lib"sv).string(), Sandbox::SeatbeltPath::Access::ReadAndExecute)); + TRY(Sandbox::add_seatbelt_path_if_exists(paths, LexicalPath::join(build_root, "vcpkg_installed"sv).string(), Sandbox::SeatbeltPath::Access::ReadAndExecute)); + + for (auto const& path : TRY(Gfx::FontDatabase::font_directories())) + TRY(Sandbox::add_seatbelt_path_if_exists(paths, path, Sandbox::SeatbeltPath::Access::ReadOnly)); + + Vector executable_paths; + if (auto cranelift_compiler_path = Core::Environment::get("LADYBIRD_CRANELIFT_COMPILER"sv); cranelift_compiler_path.has_value()) { + TRY(Sandbox::add_seatbelt_path_if_exists(paths, *cranelift_compiler_path, Sandbox::SeatbeltPath::Access::ReadAndExecute)); + if (auto canonicalized_cranelift_compiler_path = TRY(canonicalized_path_if_exists(*cranelift_compiler_path)); canonicalized_cranelift_compiler_path.has_value()) + TRY(executable_paths.try_append(canonicalized_cranelift_compiler_path.release_value())); + } else { + auto default_cranelift_compiler_path = LexicalPath::join(build_root, "bin/cranelift-compiler"sv).string(); + TRY(Sandbox::add_seatbelt_path_if_exists(paths, default_cranelift_compiler_path, Sandbox::SeatbeltPath::Access::ReadAndExecute)); + if (auto canonicalized_cranelift_compiler_path = TRY(canonicalized_path_if_exists(default_cranelift_compiler_path)); canonicalized_cranelift_compiler_path.has_value()) + TRY(executable_paths.try_append(canonicalized_cranelift_compiler_path.release_value())); + } + + auto skia_cache_path = TRY(String::formatted("{}/Ladybird", Core::StandardPaths::cache_directory())); + TRY(Core::Directory::create(skia_cache_path.to_byte_string(), Core::Directory::CreateDirectories::Yes)); + TRY(Sandbox::add_seatbelt_path_if_exists(paths, skia_cache_path, Sandbox::SeatbeltPath::Access::ReadWrite)); + + char darwin_user_cache_directory[PATH_MAX]; + if (confstr(_CS_DARWIN_USER_CACHE_DIR, darwin_user_cache_directory, sizeof(darwin_user_cache_directory)) > 0) { + StringView darwin_user_cache_directory_view { darwin_user_cache_directory, strlen(darwin_user_cache_directory) }; + TRY(Sandbox::add_seatbelt_path_if_exists(paths, darwin_user_cache_directory_view, Sandbox::SeatbeltPath::Access::ReadWrite)); + if (darwin_user_cache_directory_view.starts_with("/var/"sv)) { + auto private_darwin_user_cache_directory = TRY(String::formatted("/private{}", darwin_user_cache_directory)); + TRY(Sandbox::add_seatbelt_path_if_exists(paths, private_darwin_user_cache_directory, Sandbox::SeatbeltPath::Access::ReadWrite)); + } + } + + return Sandbox::apply_macos_sandbox(paths.span(), Sandbox::NetworkAccess::Denied, executable_paths.span()); +} + +} diff --git a/Services/RequestServer/CMakeLists.txt b/Services/RequestServer/CMakeLists.txt index 7953950cb3..723b507bcc 100644 --- a/Services/RequestServer/CMakeLists.txt +++ b/Services/RequestServer/CMakeLists.txt @@ -10,6 +10,8 @@ set(SOURCES if (LINUX) list(APPEND SOURCES SandboxLinux.cpp) +elseif (APPLE) + list(APPEND SOURCES SandboxMacOS.cpp) else() list(APPEND SOURCES SandboxUnimplemented.cpp) endif() diff --git a/Services/RequestServer/ResourceSubstitutionMap.cpp b/Services/RequestServer/ResourceSubstitutionMap.cpp index 9ba7602bdf..63371e09e6 100644 --- a/Services/RequestServer/ResourceSubstitutionMap.cpp +++ b/Services/RequestServer/ResourceSubstitutionMap.cpp @@ -87,4 +87,11 @@ Optional ResourceSubstitutionMap::lookup(URL::URL c return it->value; } +ErrorOr ResourceSubstitutionMap::for_each_substitution(Function(ResourceSubstitution const&)> const& callback) const +{ + for (auto const& entry : m_substitutions) + TRY(callback(entry.value)); + return {}; +} + } diff --git a/Services/RequestServer/ResourceSubstitutionMap.h b/Services/RequestServer/ResourceSubstitutionMap.h index 6b0f87474f..8be6d2787f 100644 --- a/Services/RequestServer/ResourceSubstitutionMap.h +++ b/Services/RequestServer/ResourceSubstitutionMap.h @@ -7,9 +7,11 @@ #pragma once #include +#include #include #include #include +#include #include #include @@ -26,6 +28,7 @@ public: static ErrorOr> load_from_file(StringView path); Optional lookup(URL::URL const&) const; + ErrorOr for_each_substitution(Function(ResourceSubstitution const&)> const&) const; private: ResourceSubstitutionMap() = default; @@ -33,4 +36,6 @@ private: HashMap m_substitutions; }; +extern OwnPtr g_resource_substitution_map; + } diff --git a/Services/RequestServer/SandboxMacOS.cpp b/Services/RequestServer/SandboxMacOS.cpp new file mode 100644 index 0000000000..d429f26c5e --- /dev/null +++ b/Services/RequestServer/SandboxMacOS.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2026-present, the Ladybird developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace RequestServer { + +ErrorOr apply_sandbox(Vector const& certificates) +{ + TRY(Sandbox::configure_runtime()); + + Vector paths; + auto cache_path = TRY(String::formatted("{}/Ladybird", Core::StandardPaths::cache_directory())); + TRY(Core::Directory::create(cache_path.to_byte_string(), Core::Directory::CreateDirectories::Yes)); + + auto executable_path = TRY(Core::System::current_executable_path()); + auto build_root = LexicalPath::dirname(LexicalPath::dirname(LexicalPath::dirname(LexicalPath::dirname(LexicalPath::dirname(executable_path))))); + + TRY(Sandbox::add_seatbelt_path_if_exists(paths, executable_path, Sandbox::SeatbeltPath::Access::ReadOnly)); + TRY(Sandbox::add_seatbelt_path_if_exists(paths, LexicalPath::join(build_root, "bin"sv).string(), Sandbox::SeatbeltPath::Access::ReadOnly)); + TRY(Sandbox::add_seatbelt_path_if_exists(paths, LexicalPath::join(build_root, "lib"sv).string(), Sandbox::SeatbeltPath::Access::ReadAndExecute)); + TRY(Sandbox::add_seatbelt_path_if_exists(paths, LexicalPath::join(build_root, "vcpkg_installed"sv).string(), Sandbox::SeatbeltPath::Access::ReadAndExecute)); + + TRY(Sandbox::add_seatbelt_path_if_exists(paths, "/etc/hosts"sv, Sandbox::SeatbeltPath::Access::ReadOnly)); + TRY(Sandbox::add_seatbelt_path_if_exists(paths, "/etc/resolv.conf"sv, Sandbox::SeatbeltPath::Access::ReadOnly)); + TRY(Sandbox::add_seatbelt_path_if_exists(paths, "/private/etc/hosts"sv, Sandbox::SeatbeltPath::Access::ReadOnly)); + TRY(Sandbox::add_seatbelt_path_if_exists(paths, "/private/etc/resolv.conf"sv, Sandbox::SeatbeltPath::Access::ReadOnly)); + TRY(Sandbox::add_seatbelt_path_if_exists(paths, "/private/etc/ssl"sv, Sandbox::SeatbeltPath::Access::ReadOnly)); + TRY(Sandbox::add_seatbelt_path_if_exists(paths, "/Library/Preferences/com.apple.networkd.plist"sv, Sandbox::SeatbeltPath::Access::ReadOnly)); + + for (auto const& certificate : certificates) { + auto certificate_path = LexicalPath::dirname(certificate); + if (certificate_path.is_empty()) + certificate_path = "."; + + TRY(Sandbox::add_seatbelt_path_if_exists(paths, certificate_path, Sandbox::SeatbeltPath::Access::ReadOnly)); + } + + if (g_resource_substitution_map) { + TRY(g_resource_substitution_map->for_each_substitution([&](auto const& substitution) -> ErrorOr { + TRY(Sandbox::add_seatbelt_path_if_exists(paths, substitution.file_path, Sandbox::SeatbeltPath::Access::ReadOnly)); + return {}; + })); + } + + TRY(Sandbox::add_seatbelt_path_if_exists(paths, cache_path, Sandbox::SeatbeltPath::Access::ReadWrite)); + + return Sandbox::apply_macos_sandbox(paths.span(), Sandbox::NetworkAccess::Allowed); +} + +} diff --git a/Services/WebContent/CMakeLists.txt b/Services/WebContent/CMakeLists.txt index 06d8ae6bc3..7aea6f54a1 100644 --- a/Services/WebContent/CMakeLists.txt +++ b/Services/WebContent/CMakeLists.txt @@ -40,6 +40,8 @@ add_executable(WebContent main.cpp) if (LINUX) target_sources(WebContent PRIVATE ../RendererSandboxLinux.cpp) +elseif (APPLE) + target_sources(WebContent PRIVATE ../RendererSandboxMacOS.cpp) else() target_sources(WebContent PRIVATE ../RendererSandboxUnimplemented.cpp) endif() diff --git a/Services/WebWorker/CMakeLists.txt b/Services/WebWorker/CMakeLists.txt index 418e004b71..0b9390c994 100644 --- a/Services/WebWorker/CMakeLists.txt +++ b/Services/WebWorker/CMakeLists.txt @@ -20,6 +20,8 @@ add_executable(WebWorker main.cpp) if (LINUX) target_sources(WebWorker PRIVATE ../RendererSandboxLinux.cpp) +elseif (APPLE) + target_sources(WebWorker PRIVATE ../RendererSandboxMacOS.cpp) else() target_sources(WebWorker PRIVATE ../RendererSandboxUnimplemented.cpp) endif()