ImageDecoder: Sandbox the process on Linux

Add --enable-sandbox to Ladybird and test-web, pass it through to
ImageDecoder, and make ImageDecoder install its Linux sandbox only when
the option is present.

The Linux implementation enables no_new_privs, configures glibc malloc
to avoid late CPU-count probes in helper threads, applies an empty
Landlock ruleset when available, and installs a seccomp filter for the
helper IPC, shared memory, threading, and decoding syscalls.

Deny plain read-only filesystem probes without granting file access, so
common runtime feature checks can observe the sandbox instead of
terminating the helper during normal decoding.
This commit is contained in:
Andreas Kling 2026-05-20 17:19:06 +02:00 committed by Andreas Kling
parent 39e8b10f14
commit cca5a6dd37
8 changed files with 535 additions and 0 deletions

View file

@ -191,6 +191,7 @@ ErrorOr<void> Application::initialize(Main::Arguments const& arguments)
bool disable_http_memory_cache = false;
bool disable_http_disk_cache = false;
bool disable_content_blocker = false;
bool enable_sandbox = false;
Vector<StringView> content_blocker_list_paths;
Optional<StringView> resource_substitution_map_path;
bool enable_autoplay = false;
@ -266,6 +267,7 @@ ErrorOr<void> Application::initialize(Main::Arguments const& arguments)
args_parser.add_option(disable_http_memory_cache, "Disable HTTP memory cache", "disable-http-memory-cache");
args_parser.add_option(disable_http_disk_cache, "Disable HTTP disk cache", "disable-http-disk-cache");
args_parser.add_option(disable_content_blocker, "Disable content blocker", "disable-content-blocker");
args_parser.add_option(enable_sandbox, "Enable helper process sandboxing", "enable-sandbox");
args_parser.add_option(Core::ArgsParser::Option {
.argument_mode = Core::ArgsParser::OptionArgumentMode::Required,
.help_string = "Path to a content blocker list. May be specified multiple times.",
@ -389,6 +391,7 @@ ErrorOr<void> Application::initialize(Main::Arguments const& arguments)
: OptionalNone()),
.devtools_port = devtools_port,
.enable_content_blocker = disable_content_blocker ? EnableContentBlocker::No : EnableContentBlocker::Yes,
.enable_sandbox = enable_sandbox ? EnableSandbox::Yes : EnableSandbox::No,
.content_blocker_list_paths = move(content_blocker_list_paths_as_byte_strings),
};

View file

@ -146,7 +146,11 @@ ErrorOr<NonnullRefPtr<WebView::WebContentClient>> launch_web_content_process(u64
ErrorOr<NonnullRefPtr<ImageDecoderClient::Client>> launch_image_decoder_process()
{
auto const& browser_options = WebView::Application::browser_options();
Vector<ByteString> arguments;
if (browser_options.enable_sandbox == EnableSandbox::Yes)
arguments.append("--enable-sandbox"sv);
if (auto server = mach_server_name(); server.has_value()) {
arguments.append("--mach-server-name"sv);
arguments.append(server.value());

View file

@ -74,6 +74,11 @@ enum class EnableContentBlocker {
Yes,
};
enum class EnableSandbox {
No,
Yes,
};
struct BrowserOptions {
Vector<URL::URL> urls;
Vector<ByteString> raw_urls;
@ -93,6 +98,7 @@ struct BrowserOptions {
Optional<DNSSettings> dns_settings {};
Optional<u16> devtools_port;
EnableContentBlocker enable_content_blocker { EnableContentBlocker::Yes };
EnableSandbox enable_sandbox { EnableSandbox::No };
Vector<ByteString> content_blocker_list_paths {};
};

View file

@ -19,6 +19,12 @@ endif()
add_executable(ImageDecoder main.cpp)
if (LINUX)
target_sources(ImageDecoder PRIVATE SandboxLinux.cpp)
else()
target_sources(ImageDecoder PRIVATE SandboxUnimplemented.cpp)
endif()
target_include_directories(imagedecoderservice PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/../..)
target_include_directories(imagedecoderservice PRIVATE ${LADYBIRD_SOURCE_DIR}/Services/)

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 ImageDecoder {
[[nodiscard]] ErrorOr<void> apply_sandbox();
}

View file

@ -0,0 +1,479 @@
/*
* Copyright (c) 2026-present, the Ladybird developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/ScopeGuard.h>
#include <ImageDecoder/Sandbox.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/audit.h>
#include <linux/filter.h>
#include <linux/landlock.h>
#include <linux/seccomp.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 <unistd.h>
#if defined(__GLIBC__)
# include <malloc.h>
#endif
namespace ImageDecoder {
namespace {
#if defined(__x86_64__)
static constexpr u32 audit_architecture = AUDIT_ARCH_X86_64;
#elif defined(__aarch64__)
static constexpr u32 audit_architecture = AUDIT_ARCH_AARCH64;
#elif defined(__arm__)
static constexpr u32 audit_architecture = AUDIT_ARCH_ARM;
#elif defined(__i386__)
static constexpr u32 audit_architecture = AUDIT_ARCH_I386;
#elif defined(__riscv) && __riscv_xlen == 64
static constexpr u32 audit_architecture = AUDIT_ARCH_RISCV64;
#else
# error "Add seccomp audit architecture for this Linux architecture"
#endif
#define SECCOMP_LOAD_SYSCALL_NR BPF_STMT(BPF_LD | BPF_W | BPF_ABS, static_cast<unsigned int>(offsetof(seccomp_data, nr)))
#define SECCOMP_LOAD_ARCHITECTURE BPF_STMT(BPF_LD | BPF_W | BPF_ABS, static_cast<unsigned int>(offsetof(seccomp_data, arch)))
#define SECCOMP_LOAD_ARGUMENT(index) BPF_STMT(BPF_LD | BPF_W | BPF_ABS, static_cast<unsigned int>(offsetof(seccomp_data, args[(index)])))
#define SECCOMP_ALLOW BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW)
#define SECCOMP_KILL BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS)
#define SECCOMP_ERRNO(error) BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | ((error) & SECCOMP_RET_DATA))
#define SECCOMP_ALLOW_SYSCALL(name) \
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_##name, 0, 1), SECCOMP_ALLOW
#define SECCOMP_ALLOW_NOTHING BPF_STMT(BPF_ALU | BPF_ADD | BPF_K, 0)
#define SECCOMP_ALLOW_SYSCALL_IF_DEFINED(name) \
IF_DEFINED_##name(SECCOMP_ALLOW_SYSCALL(name), SECCOMP_ALLOW_NOTHING)
#define SECCOMP_DENY_READONLY_OPEN_SYSCALL(name, flags_argument_index) \
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_##name, 0, 5), \
SECCOMP_LOAD_ARGUMENT(flags_argument_index), BPF_STMT(BPF_ALU | BPF_AND | BPF_K, ~read_only_open_flags), \
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0, 0, 1), SECCOMP_ERRNO(EACCES), SECCOMP_LOAD_SYSCALL_NR
#ifdef O_LARGEFILE
static constexpr unsigned read_only_open_flags = O_CLOEXEC | O_LARGEFILE;
#else
static constexpr unsigned read_only_open_flags = O_CLOEXEC;
#endif
#define IF_DEFINED_accept4(if_defined, if_not_defined) if_defined
#define IF_DEFINED_brk(if_defined, if_not_defined) if_defined
#define IF_DEFINED_clock_getres(if_defined, if_not_defined) if_defined
#define IF_DEFINED_clock_gettime(if_defined, if_not_defined) if_defined
#define IF_DEFINED_clock_nanosleep(if_defined, if_not_defined) if_defined
#define IF_DEFINED_clone(if_defined, if_not_defined) if_defined
#define IF_DEFINED_clone3(if_defined, if_not_defined) if_defined
#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_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
#define IF_DEFINED_fcntl64(if_defined, if_not_defined) if_defined
#define IF_DEFINED_fstat(if_defined, if_not_defined) if_defined
#define IF_DEFINED_ftruncate(if_defined, if_not_defined) if_defined
#define IF_DEFINED_futex(if_defined, if_not_defined) if_defined
#define IF_DEFINED_futex_time64(if_defined, if_not_defined) if_defined
#define IF_DEFINED_getcpu(if_defined, if_not_defined) if_defined
#define IF_DEFINED_getegid(if_defined, if_not_defined) if_defined
#define IF_DEFINED_geteuid(if_defined, if_not_defined) if_defined
#define IF_DEFINED_getgid(if_defined, if_not_defined) if_defined
#define IF_DEFINED_getpeername(if_defined, if_not_defined) if_defined
#define IF_DEFINED_getpid(if_defined, if_not_defined) if_defined
#define IF_DEFINED_getrandom(if_defined, if_not_defined) if_defined
#define IF_DEFINED_getrlimit(if_defined, if_not_defined) if_defined
#define IF_DEFINED_getrusage(if_defined, if_not_defined) if_defined
#define IF_DEFINED_getsockname(if_defined, if_not_defined) if_defined
#define IF_DEFINED_getsockopt(if_defined, if_not_defined) if_defined
#define IF_DEFINED_gettid(if_defined, if_not_defined) if_defined
#define IF_DEFINED_gettimeofday(if_defined, if_not_defined) if_defined
#define IF_DEFINED_getuid(if_defined, if_not_defined) if_defined
#define IF_DEFINED_ioctl(if_defined, if_not_defined) if_defined
#define IF_DEFINED_lseek(if_defined, if_not_defined) if_defined
#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_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
#define IF_DEFINED_mremap(if_defined, if_not_defined) if_defined
#define IF_DEFINED_munmap(if_defined, if_not_defined) if_defined
#define IF_DEFINED_nanosleep(if_defined, if_not_defined) if_defined
#define IF_DEFINED_pipe2(if_defined, if_not_defined) if_defined
#define IF_DEFINED_poll(if_defined, if_not_defined) if_defined
#define IF_DEFINED_ppoll(if_defined, if_not_defined) if_defined
#define IF_DEFINED_prctl(if_defined, if_not_defined) if_defined
#define IF_DEFINED_prlimit64(if_defined, if_not_defined) if_defined
#define IF_DEFINED_pselect6(if_defined, if_not_defined) if_defined
#define IF_DEFINED_pwrite64(if_defined, if_not_defined) if_defined
#define IF_DEFINED_read(if_defined, if_not_defined) if_defined
#define IF_DEFINED_recvfrom(if_defined, if_not_defined) if_defined
#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_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
#define IF_DEFINED_rt_sigreturn(if_defined, if_not_defined) if_defined
#define IF_DEFINED_sched_getaffinity(if_defined, if_not_defined) if_defined
#define IF_DEFINED_sched_yield(if_defined, if_not_defined) if_defined
#define IF_DEFINED_sendmmsg(if_defined, if_not_defined) if_defined
#define IF_DEFINED_sendmsg(if_defined, if_not_defined) if_defined
#define IF_DEFINED_sendto(if_defined, if_not_defined) if_defined
#define IF_DEFINED_set_robust_list(if_defined, if_not_defined) if_defined
#define IF_DEFINED_set_tid_address(if_defined, if_not_defined) if_defined
#define IF_DEFINED_setsockopt(if_defined, if_not_defined) if_defined
#define IF_DEFINED_sigaltstack(if_defined, if_not_defined) if_defined
#define IF_DEFINED_socketpair(if_defined, if_not_defined) if_defined
#define IF_DEFINED_sysinfo(if_defined, if_not_defined) if_defined
#define IF_DEFINED_tgkill(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_write(if_defined, if_not_defined) if_defined
#ifndef __NR_accept4
# undef IF_DEFINED_accept4
# define IF_DEFINED_accept4(if_defined, if_not_defined) if_not_defined
#endif
#ifndef __NR_clock_getres
# undef IF_DEFINED_clock_getres
# define IF_DEFINED_clock_getres(if_defined, if_not_defined) if_not_defined
#endif
#ifndef __NR_clock_gettime
# undef IF_DEFINED_clock_gettime
# define IF_DEFINED_clock_gettime(if_defined, if_not_defined) if_not_defined
#endif
#ifndef __NR_clock_nanosleep
# undef IF_DEFINED_clock_nanosleep
# define IF_DEFINED_clock_nanosleep(if_defined, if_not_defined) if_not_defined
#endif
#ifndef __NR_clone3
# undef IF_DEFINED_clone3
# define IF_DEFINED_clone3(if_defined, if_not_defined) if_not_defined
#endif
#ifndef __NR_dup3
# undef IF_DEFINED_dup3
# define IF_DEFINED_dup3(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
#endif
#ifndef __NR_fstat
# undef IF_DEFINED_fstat
# define IF_DEFINED_fstat(if_defined, if_not_defined) if_not_defined
#endif
#ifndef __NR_futex_time64
# undef IF_DEFINED_futex_time64
# define IF_DEFINED_futex_time64(if_defined, if_not_defined) if_not_defined
#endif
#ifndef __NR_getcpu
# undef IF_DEFINED_getcpu
# define IF_DEFINED_getcpu(if_defined, if_not_defined) if_not_defined
#endif
#ifndef __NR_getrandom
# undef IF_DEFINED_getrandom
# define IF_DEFINED_getrandom(if_defined, if_not_defined) if_not_defined
#endif
#ifndef __NR_membarrier
# undef IF_DEFINED_membarrier
# define IF_DEFINED_membarrier(if_defined, if_not_defined) if_not_defined
#endif
#ifndef __NR_memfd_create
# undef IF_DEFINED_memfd_create
# define IF_DEFINED_memfd_create(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
#endif
#ifndef __NR_pipe2
# undef IF_DEFINED_pipe2
# define IF_DEFINED_pipe2(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
#endif
#ifndef __NR_prlimit64
# undef IF_DEFINED_prlimit64
# define IF_DEFINED_prlimit64(if_defined, if_not_defined) if_not_defined
#endif
#ifndef __NR_pselect6
# undef IF_DEFINED_pselect6
# define IF_DEFINED_pselect6(if_defined, if_not_defined) if_not_defined
#endif
#ifndef __NR_pwrite64
# undef IF_DEFINED_pwrite64
# define IF_DEFINED_pwrite64(if_defined, if_not_defined) if_not_defined
#endif
#ifndef __NR_recvmmsg
# undef IF_DEFINED_recvmmsg
# define IF_DEFINED_recvmmsg(if_defined, if_not_defined) if_not_defined
#endif
#ifndef __NR_restart_syscall
# undef IF_DEFINED_restart_syscall
# define IF_DEFINED_restart_syscall(if_defined, if_not_defined) if_not_defined
#endif
#ifndef __NR_rseq
# undef IF_DEFINED_rseq
# define IF_DEFINED_rseq(if_defined, if_not_defined) if_not_defined
#endif
#ifndef __NR_sendmmsg
# undef IF_DEFINED_sendmmsg
# define IF_DEFINED_sendmmsg(if_defined, if_not_defined) if_not_defined
#endif
#ifndef __NR_sigaltstack
# undef IF_DEFINED_sigaltstack
# define IF_DEFINED_sigaltstack(if_defined, if_not_defined) if_not_defined
#endif
#ifndef __NR_sysinfo
# undef IF_DEFINED_sysinfo
# define IF_DEFINED_sysinfo(if_defined, if_not_defined) if_not_defined
#endif
#ifndef __NR_umask
# undef IF_DEFINED_umask
# define IF_DEFINED_umask(if_defined, if_not_defined) if_not_defined
#endif
#define SECCOMP_ALLOW_MMAP_WITHOUT_EXEC(name, protection_argument) \
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_##name, 0, 5), \
SECCOMP_LOAD_ARGUMENT(protection_argument), \
BPF_STMT(BPF_ALU | BPF_AND | BPF_K, PROT_EXEC), \
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0, 0, 1), \
SECCOMP_ALLOW, \
SECCOMP_LOAD_SYSCALL_NR
#define SECCOMP_ALLOW_IOCTL_COMMAND(command) \
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_ioctl, 0, 5), \
SECCOMP_LOAD_ARGUMENT(1), \
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, command, 0, 1), \
SECCOMP_ALLOW, \
SECCOMP_LOAD_SYSCALL_NR, \
BPF_STMT(BPF_ALU | BPF_ADD | BPF_K, 0)
#define SECCOMP_ALLOW_FCNTL_COMMAND(command) \
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_fcntl, 0, 5), \
SECCOMP_LOAD_ARGUMENT(1), \
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, command, 0, 1), \
SECCOMP_ALLOW, \
SECCOMP_LOAD_SYSCALL_NR, \
BPF_STMT(BPF_ALU | BPF_ADD | BPF_K, 0)
ErrorOr<void> install_no_new_privileges()
{
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0)
return Error::from_syscall("prctl(PR_SET_NO_NEW_PRIVS)"sv, errno);
return {};
}
ErrorOr<void> configure_runtime_for_sandbox()
{
#if defined(__GLIBC__) && defined(M_ARENA_MAX)
if (mallopt(M_ARENA_MAX, 4) == 0)
return Error::from_string_literal("mallopt(M_ARENA_MAX) failed");
#endif
return {};
}
ErrorOr<void> restrict_filesystem_with_landlock()
{
#if defined(__NR_landlock_create_ruleset) && 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)
return {};
return Error::from_syscall("landlock_create_ruleset(LANDLOCK_CREATE_RULESET_VERSION)"sv, errno);
}
if (landlock_abi == 0)
return {};
landlock_ruleset_attr ruleset_attributes {};
ruleset_attributes.handled_access_fs = LANDLOCK_ACCESS_FS_EXECUTE
| LANDLOCK_ACCESS_FS_WRITE_FILE
| LANDLOCK_ACCESS_FS_READ_FILE
| LANDLOCK_ACCESS_FS_READ_DIR
| LANDLOCK_ACCESS_FS_REMOVE_DIR
| LANDLOCK_ACCESS_FS_REMOVE_FILE
| LANDLOCK_ACCESS_FS_MAKE_CHAR
| LANDLOCK_ACCESS_FS_MAKE_DIR
| LANDLOCK_ACCESS_FS_MAKE_REG
| LANDLOCK_ACCESS_FS_MAKE_SOCK
| LANDLOCK_ACCESS_FS_MAKE_FIFO
| LANDLOCK_ACCESS_FS_MAKE_BLOCK
| LANDLOCK_ACCESS_FS_MAKE_SYM;
# ifdef LANDLOCK_ACCESS_FS_REFER
if (landlock_abi >= 2)
ruleset_attributes.handled_access_fs |= LANDLOCK_ACCESS_FS_REFER;
# 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)
if (landlock_abi >= 4)
ruleset_attributes.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | LANDLOCK_ACCESS_NET_CONNECT_TCP;
auto ruleset_attributes_size = landlock_abi >= 4
? sizeof(ruleset_attributes)
: offsetof(landlock_ruleset_attr, handled_access_net);
# else
auto ruleset_attributes_size = sizeof(ruleset_attributes);
# 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);
ArmedScopeGuard close_ruleset_fd = [&] {
close(static_cast<int>(ruleset_fd));
};
if (syscall(__NR_landlock_restrict_self, ruleset_fd, 0) < 0)
return Error::from_syscall("landlock_restrict_self"sv, errno);
#endif
return {};
}
ErrorOr<void> install_seccomp_filter()
{
sock_filter filter[] = {
SECCOMP_LOAD_ARCHITECTURE,
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, audit_architecture, 1, 0),
SECCOMP_KILL,
SECCOMP_LOAD_SYSCALL_NR,
// Deny read-only runtime probes without granting filesystem access.
#ifdef __NR_open
SECCOMP_DENY_READONLY_OPEN_SYSCALL(open, 1),
#endif
#ifdef __NR_openat
SECCOMP_DENY_READONLY_OPEN_SYSCALL(openat, 2),
#endif
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(read),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(write),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(close),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(poll),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(ppoll),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(pselect6),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(recvmsg),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(recvfrom),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(recvmmsg),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(sendmsg),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(sendto),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(sendmmsg),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(socketpair),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(getsockopt),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(setsockopt),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(getsockname),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(getpeername),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(fstat),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(dup),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(dup3),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(pipe2),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(ftruncate),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(lseek),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(pwrite64),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(memfd_create),
SECCOMP_ALLOW_MMAP_WITHOUT_EXEC(mmap, 2),
#ifdef __NR_mmap2
SECCOMP_ALLOW_MMAP_WITHOUT_EXEC(mmap2, 2),
#endif
SECCOMP_ALLOW_MMAP_WITHOUT_EXEC(mprotect, 2),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(mremap),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(munmap),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(madvise),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(brk),
SECCOMP_ALLOW_FCNTL_COMMAND(F_GETFD),
SECCOMP_ALLOW_FCNTL_COMMAND(F_SETFD),
SECCOMP_ALLOW_FCNTL_COMMAND(F_GETFL),
SECCOMP_ALLOW_FCNTL_COMMAND(F_SETFL),
SECCOMP_ALLOW_IOCTL_COMMAND(FIONBIO),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(clone),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(clone3),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(futex),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(futex_time64),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(set_tid_address),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(set_robust_list),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(rseq),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(rt_sigaction),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(rt_sigprocmask),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(rt_sigreturn),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(sigaltstack),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(tgkill),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(clock_gettime),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(clock_getres),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(clock_nanosleep),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(nanosleep),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(restart_syscall),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(getpid),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(gettid),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(getuid),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(geteuid),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(getgid),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(getegid),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(getrandom),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(gettimeofday),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(getrlimit),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(prlimit64),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(getrusage),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(sched_getaffinity),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(sched_yield),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(getcpu),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(membarrier),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(sysinfo),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(uname),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(umask),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(prctl),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(exit),
SECCOMP_ALLOW_SYSCALL_IF_DEFINED(exit_group),
SECCOMP_KILL,
};
sock_fprog program {
.len = static_cast<unsigned short>(sizeof(filter) / sizeof(filter[0])),
.filter = filter,
};
#ifdef __NR_seccomp
if (syscall(__NR_seccomp, SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_TSYNC, &program) < 0)
return Error::from_syscall("seccomp(SECCOMP_SET_MODE_FILTER)"sv, errno);
#else
if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &program) < 0)
return Error::from_syscall("prctl(PR_SET_SECCOMP)"sv, errno);
#endif
return {};
}
}
ErrorOr<void> apply_sandbox()
{
TRY(install_no_new_privileges());
TRY(configure_runtime_for_sandbox());
TRY(restrict_filesystem_with_landlock());
TRY(install_seccomp_filter());
return {};
}
}

View file

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

View file

@ -7,6 +7,7 @@
*/
#include <ImageDecoder/ConnectionFromClient.h>
#include <ImageDecoder/Sandbox.h>
#include <LibCore/ArgsParser.h>
#include <LibCore/EventLoop.h>
#include <LibCore/Process.h>
@ -20,14 +21,19 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
Core::ArgsParser args_parser;
StringView mach_server_name;
bool wait_for_debugger = false;
bool enable_sandbox = false;
args_parser.add_option(mach_server_name, "Mach server name", "mach-server-name", 0, "mach_server_name");
args_parser.add_option(wait_for_debugger, "Wait for debugger", "wait-for-debugger");
args_parser.add_option(enable_sandbox, "Enable process sandboxing", "enable-sandbox");
args_parser.parse(arguments);
if (wait_for_debugger)
Core::Process::wait_for_debugger_and_break();
if (enable_sandbox)
TRY(ImageDecoder::apply_sandbox());
auto& event_loop = Core::EventLoop::initialize_for_current_thread();
auto client = TRY(IPC::take_over_accepted_client_from_system_server<ImageDecoder::ConnectionFromClient>(mach_server_name));