RequestServer: Sandbox the process on Linux
Add opt-in Linux sandbox support to RequestServer. Ladybird and test-web pass --enable-sandbox through when requested, and RequestServer only installs the sandbox when that flag is present. Allow reads for resolver and TLS configuration, plus the configured certificate locations. Create and allow writes to the Ladybird cache root, so libcurl alt-svc files and HTTP disk-cache files stay inside the single writable tree. Also allow the systemd-resolved runtime directory when present, since /etc/resolv.conf can point there. Extend LibSandbox with owned Landlock paths, an add-if-exists helper, read/write Landlock access, and reusable seccomp groups for filesystem writes and network syscalls. Include POSIX file locks and socket byte count ioctls needed by libc resolver and cache paths. Reuse the new Landlock helper from Compositor as well, and allow its Mesa shader cache directory so GPU startup can populate shader cache files after sandboxing.
This commit is contained in:
parent
c38d03ba25
commit
ea1ecdad5a
11 changed files with 393 additions and 33 deletions
|
|
@ -7,12 +7,14 @@
|
|||
#include <LibSandbox/Sandbox.h>
|
||||
|
||||
#if defined(AK_OS_LINUX)
|
||||
# include <AK/LexicalPath.h>
|
||||
# include <AK/ScopeGuard.h>
|
||||
# include <AK/String.h>
|
||||
# include <AK/Vector.h>
|
||||
# include <errno.h>
|
||||
# include <fcntl.h>
|
||||
# include <linux/landlock.h>
|
||||
# include <sys/prctl.h>
|
||||
# include <sys/stat.h>
|
||||
# include <sys/syscall.h>
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
|
@ -41,7 +43,36 @@ ErrorOr<void> configure_runtime()
|
|||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<void> restrict_filesystem_with_landlock(ReadonlySpan<StringView> readable_paths)
|
||||
ErrorOr<void> add_landlock_path_if_exists(Vector<LandlockPath>& paths, StringView path, LandlockPath::Access access)
|
||||
{
|
||||
#if defined(AK_OS_LINUX)
|
||||
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);
|
||||
}
|
||||
|
||||
if (!S_ISDIR(statbuf.st_mode))
|
||||
path_bytes = LexicalPath::dirname(path_bytes);
|
||||
|
||||
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 }));
|
||||
#else
|
||||
(void)paths;
|
||||
(void)path;
|
||||
(void)access;
|
||||
#endif
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<void> restrict_filesystem_with_landlock(ReadonlySpan<LandlockPath> paths)
|
||||
{
|
||||
#if defined(AK_OS_LINUX) && 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);
|
||||
|
|
@ -77,12 +108,7 @@ ErrorOr<void> restrict_filesystem_with_landlock(ReadonlySpan<StringView> readabl
|
|||
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);
|
||||
auto ruleset_attributes_size = offsetof(landlock_ruleset_attr, handled_access_net);
|
||||
# else
|
||||
auto ruleset_attributes_size = sizeof(ruleset_attributes);
|
||||
# endif
|
||||
|
|
@ -94,10 +120,8 @@ ErrorOr<void> restrict_filesystem_with_landlock(ReadonlySpan<StringView> readabl
|
|||
close(static_cast<int>(ruleset_fd));
|
||||
};
|
||||
|
||||
for (auto readable_path : readable_paths) {
|
||||
auto path = TRY(String::from_utf8(readable_path));
|
||||
auto path_bytes = path.to_byte_string();
|
||||
auto path_fd = open(path_bytes.characters(), O_PATH | O_CLOEXEC);
|
||||
for (auto const& landlock_path : paths) {
|
||||
auto path_fd = open(landlock_path.path.characters(), O_PATH | O_CLOEXEC);
|
||||
if (path_fd < 0)
|
||||
return Error::from_syscall("open(O_PATH)"sv, errno);
|
||||
|
||||
|
|
@ -105,19 +129,45 @@ ErrorOr<void> restrict_filesystem_with_landlock(ReadonlySpan<StringView> readabl
|
|||
close(path_fd);
|
||||
};
|
||||
|
||||
landlock_path_beneath_attr path_beneath {
|
||||
.allowed_access = LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR,
|
||||
.parent_fd = path_fd,
|
||||
};
|
||||
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::ReadWrite) {
|
||||
path_beneath.allowed_access |= LANDLOCK_ACCESS_FS_WRITE_FILE
|
||||
| LANDLOCK_ACCESS_FS_REMOVE_DIR
|
||||
| LANDLOCK_ACCESS_FS_REMOVE_FILE
|
||||
| LANDLOCK_ACCESS_FS_MAKE_DIR
|
||||
| LANDLOCK_ACCESS_FS_MAKE_REG
|
||||
| LANDLOCK_ACCESS_FS_MAKE_SOCK
|
||||
| LANDLOCK_ACCESS_FS_MAKE_FIFO;
|
||||
# ifdef LANDLOCK_ACCESS_FS_REFER
|
||||
if (landlock_abi >= 2)
|
||||
path_beneath.allowed_access |= LANDLOCK_ACCESS_FS_REFER;
|
||||
# endif
|
||||
# ifdef LANDLOCK_ACCESS_FS_TRUNCATE
|
||||
if (landlock_abi >= 3)
|
||||
path_beneath.allowed_access |= LANDLOCK_ACCESS_FS_TRUNCATE;
|
||||
# endif
|
||||
}
|
||||
path_beneath.parent_fd = path_fd;
|
||||
if (syscall(__NR_landlock_add_rule, ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, &path_beneath, 0) < 0)
|
||||
return Error::from_syscall("landlock_add_rule"sv, errno);
|
||||
}
|
||||
|
||||
if (syscall(__NR_landlock_restrict_self, ruleset_fd, 0) < 0)
|
||||
return Error::from_syscall("landlock_restrict_self"sv, errno);
|
||||
#else
|
||||
(void)paths;
|
||||
#endif
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<void> restrict_filesystem_with_landlock(ReadonlySpan<StringView> readable_paths)
|
||||
{
|
||||
Vector<LandlockPath> paths;
|
||||
for (auto readable_path : readable_paths)
|
||||
TRY(paths.try_append({ readable_path.to_byte_string(), LandlockPath::Access::ReadOnly }));
|
||||
return restrict_filesystem_with_landlock(paths.span());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,14 +6,28 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <AK/ByteString.h>
|
||||
#include <AK/Error.h>
|
||||
#include <AK/Span.h>
|
||||
#include <AK/StringView.h>
|
||||
#include <AK/Vector.h>
|
||||
|
||||
namespace Sandbox {
|
||||
|
||||
struct LandlockPath {
|
||||
enum class Access {
|
||||
ReadOnly,
|
||||
ReadWrite,
|
||||
};
|
||||
|
||||
ByteString path;
|
||||
Access access { Access::ReadOnly };
|
||||
};
|
||||
|
||||
[[nodiscard]] ErrorOr<void> install_no_new_privileges();
|
||||
[[nodiscard]] ErrorOr<void> configure_runtime();
|
||||
[[nodiscard]] ErrorOr<void> add_landlock_path_if_exists(Vector<LandlockPath>& paths, StringView path, LandlockPath::Access);
|
||||
[[nodiscard]] ErrorOr<void> restrict_filesystem_with_landlock(ReadonlySpan<LandlockPath>);
|
||||
[[nodiscard]] ErrorOr<void> restrict_filesystem_with_landlock(ReadonlySpan<StringView> readable_paths = {});
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,7 +62,10 @@ static constexpr unsigned read_only_open_flags = O_CLOEXEC;
|
|||
#define SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(policy, name) \
|
||||
IF_DEFINED_##name(SECCOMP_APPEND_ALLOW_SYSCALL(policy, name), (policy).append(SECCOMP_ALLOW_NOTHING))
|
||||
|
||||
#define IF_DEFINED_accept(if_defined, if_not_defined) if_defined
|
||||
#define IF_DEFINED_accept4(if_defined, if_not_defined) if_defined
|
||||
#define IF_DEFINED_access(if_defined, if_not_defined) if_defined
|
||||
#define IF_DEFINED_bind(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
|
||||
|
|
@ -70,6 +73,7 @@ static constexpr unsigned read_only_open_flags = O_CLOEXEC;
|
|||
#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_connect(if_defined, if_not_defined) if_defined
|
||||
#define IF_DEFINED_dup(if_defined, if_not_defined) if_defined
|
||||
#define IF_DEFINED_dup3(if_defined, if_not_defined) if_defined
|
||||
#define IF_DEFINED_epoll_create1(if_defined, if_not_defined) if_defined
|
||||
|
|
@ -81,10 +85,14 @@ static constexpr unsigned read_only_open_flags = O_CLOEXEC;
|
|||
#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_fdatasync(if_defined, if_not_defined) if_defined
|
||||
#define IF_DEFINED_flock(if_defined, if_not_defined) if_defined
|
||||
#define IF_DEFINED_fstat(if_defined, if_not_defined) if_defined
|
||||
#define IF_DEFINED_fsync(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_getdents64(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
|
||||
|
|
@ -100,6 +108,9 @@ static constexpr unsigned read_only_open_flags = O_CLOEXEC;
|
|||
#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_link(if_defined, if_not_defined) if_defined
|
||||
#define IF_DEFINED_linkat(if_defined, if_not_defined) if_defined
|
||||
#define IF_DEFINED_listen(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
|
||||
|
|
@ -111,14 +122,22 @@ static constexpr unsigned read_only_open_flags = O_CLOEXEC;
|
|||
#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_newfstatat(if_defined, if_not_defined) if_defined
|
||||
#define IF_DEFINED_open(if_defined, if_not_defined) if_defined
|
||||
#define IF_DEFINED_openat(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_pread64(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_readlink(if_defined, if_not_defined) if_defined
|
||||
#define IF_DEFINED_readlinkat(if_defined, if_not_defined) if_defined
|
||||
#define IF_DEFINED_rename(if_defined, if_not_defined) if_defined
|
||||
#define IF_DEFINED_renameat(if_defined, if_not_defined) if_defined
|
||||
#define IF_DEFINED_renameat2(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
|
||||
|
|
@ -129,6 +148,7 @@ static constexpr unsigned read_only_open_flags = O_CLOEXEC;
|
|||
#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_sendfile(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
|
||||
|
|
@ -136,18 +156,38 @@ static constexpr unsigned read_only_open_flags = O_CLOEXEC;
|
|||
#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_shutdown(if_defined, if_not_defined) if_defined
|
||||
#define IF_DEFINED_socket(if_defined, if_not_defined) if_defined
|
||||
#define IF_DEFINED_socketpair(if_defined, if_not_defined) if_defined
|
||||
#define IF_DEFINED_statx(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_unlink(if_defined, if_not_defined) if_defined
|
||||
#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_write(if_defined, if_not_defined) if_defined
|
||||
|
||||
#ifndef __NR_accept
|
||||
# undef IF_DEFINED_accept
|
||||
# define IF_DEFINED_accept(if_defined, if_not_defined) if_not_defined
|
||||
#endif
|
||||
#ifndef __NR_accept4
|
||||
# undef IF_DEFINED_accept4
|
||||
# define IF_DEFINED_accept4(if_defined, if_not_defined) if_not_defined
|
||||
#endif
|
||||
#ifndef __NR_access
|
||||
# undef IF_DEFINED_access
|
||||
# define IF_DEFINED_access(if_defined, if_not_defined) if_not_defined
|
||||
#endif
|
||||
#ifndef __NR_bind
|
||||
# undef IF_DEFINED_bind
|
||||
# define IF_DEFINED_bind(if_defined, if_not_defined) if_not_defined
|
||||
#endif
|
||||
#ifndef __NR_connect
|
||||
# undef IF_DEFINED_connect
|
||||
# define IF_DEFINED_connect(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
|
||||
|
|
@ -192,14 +232,42 @@ static constexpr unsigned read_only_open_flags = O_CLOEXEC;
|
|||
# undef IF_DEFINED_fcntl64
|
||||
# define IF_DEFINED_fcntl64(if_defined, if_not_defined) if_not_defined
|
||||
#endif
|
||||
#ifndef __NR_fdatasync
|
||||
# undef IF_DEFINED_fdatasync
|
||||
# define IF_DEFINED_fdatasync(if_defined, if_not_defined) if_not_defined
|
||||
#endif
|
||||
#ifndef __NR_flock
|
||||
# undef IF_DEFINED_flock
|
||||
# define IF_DEFINED_flock(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_fsync
|
||||
# undef IF_DEFINED_fsync
|
||||
# define IF_DEFINED_fsync(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_getdents64
|
||||
# undef IF_DEFINED_getdents64
|
||||
# define IF_DEFINED_getdents64(if_defined, if_not_defined) if_not_defined
|
||||
#endif
|
||||
#ifndef __NR_link
|
||||
# undef IF_DEFINED_link
|
||||
# define IF_DEFINED_link(if_defined, if_not_defined) if_not_defined
|
||||
#endif
|
||||
#ifndef __NR_linkat
|
||||
# undef IF_DEFINED_linkat
|
||||
# define IF_DEFINED_linkat(if_defined, if_not_defined) if_not_defined
|
||||
#endif
|
||||
#ifndef __NR_listen
|
||||
# undef IF_DEFINED_listen
|
||||
# define IF_DEFINED_listen(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
|
||||
|
|
@ -228,10 +296,42 @@ static constexpr unsigned read_only_open_flags = O_CLOEXEC;
|
|||
# undef IF_DEFINED_newfstatat
|
||||
# define IF_DEFINED_newfstatat(if_defined, if_not_defined) if_not_defined
|
||||
#endif
|
||||
#ifndef __NR_open
|
||||
# undef IF_DEFINED_open
|
||||
# define IF_DEFINED_open(if_defined, if_not_defined) if_not_defined
|
||||
#endif
|
||||
#ifndef __NR_openat
|
||||
# undef IF_DEFINED_openat
|
||||
# define IF_DEFINED_openat(if_defined, if_not_defined) if_not_defined
|
||||
#endif
|
||||
#ifndef __NR_poll
|
||||
# undef IF_DEFINED_poll
|
||||
# define IF_DEFINED_poll(if_defined, if_not_defined) if_not_defined
|
||||
#endif
|
||||
#ifndef __NR_pread64
|
||||
# undef IF_DEFINED_pread64
|
||||
# define IF_DEFINED_pread64(if_defined, if_not_defined) if_not_defined
|
||||
#endif
|
||||
#ifndef __NR_readlink
|
||||
# undef IF_DEFINED_readlink
|
||||
# define IF_DEFINED_readlink(if_defined, if_not_defined) if_not_defined
|
||||
#endif
|
||||
#ifndef __NR_readlinkat
|
||||
# undef IF_DEFINED_readlinkat
|
||||
# define IF_DEFINED_readlinkat(if_defined, if_not_defined) if_not_defined
|
||||
#endif
|
||||
#ifndef __NR_rename
|
||||
# undef IF_DEFINED_rename
|
||||
# define IF_DEFINED_rename(if_defined, if_not_defined) if_not_defined
|
||||
#endif
|
||||
#ifndef __NR_renameat
|
||||
# undef IF_DEFINED_renameat
|
||||
# define IF_DEFINED_renameat(if_defined, if_not_defined) if_not_defined
|
||||
#endif
|
||||
#ifndef __NR_renameat2
|
||||
# undef IF_DEFINED_renameat2
|
||||
# define IF_DEFINED_renameat2(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
|
||||
|
|
@ -264,14 +364,34 @@ static constexpr unsigned read_only_open_flags = O_CLOEXEC;
|
|||
# undef IF_DEFINED_sendmmsg
|
||||
# define IF_DEFINED_sendmmsg(if_defined, if_not_defined) if_not_defined
|
||||
#endif
|
||||
#ifndef __NR_sendfile
|
||||
# undef IF_DEFINED_sendfile
|
||||
# define IF_DEFINED_sendfile(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_socket
|
||||
# undef IF_DEFINED_socket
|
||||
# define IF_DEFINED_socket(if_defined, if_not_defined) if_not_defined
|
||||
#endif
|
||||
#ifndef __NR_shutdown
|
||||
# undef IF_DEFINED_shutdown
|
||||
# define IF_DEFINED_shutdown(if_defined, if_not_defined) if_not_defined
|
||||
#endif
|
||||
#ifndef __NR_statx
|
||||
# undef IF_DEFINED_statx
|
||||
# define IF_DEFINED_statx(if_defined, if_not_defined) if_not_defined
|
||||
#endif
|
||||
#ifndef __NR_unlink
|
||||
# undef IF_DEFINED_unlink
|
||||
# define IF_DEFINED_unlink(if_defined, if_not_defined) if_not_defined
|
||||
#endif
|
||||
#ifndef __NR_unlinkat
|
||||
# undef IF_DEFINED_unlinkat
|
||||
# define IF_DEFINED_unlinkat(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
|
||||
|
|
@ -597,10 +717,51 @@ void SeccompPolicy::allow_readonly_file_opens()
|
|||
|
||||
void SeccompPolicy::allow_filesystem_metadata_queries()
|
||||
{
|
||||
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, access);
|
||||
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, getdents64);
|
||||
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, newfstatat);
|
||||
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, readlink);
|
||||
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, readlinkat);
|
||||
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, statx);
|
||||
}
|
||||
|
||||
void SeccompPolicy::allow_filesystem_writes()
|
||||
{
|
||||
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, open);
|
||||
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, openat);
|
||||
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, unlink);
|
||||
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, unlinkat);
|
||||
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, link);
|
||||
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, linkat);
|
||||
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, fsync);
|
||||
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, fdatasync);
|
||||
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, flock);
|
||||
|
||||
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_GETLK, 0, 1));
|
||||
append(SECCOMP_ALLOW);
|
||||
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_SETLK, 0, 1));
|
||||
append(SECCOMP_ALLOW);
|
||||
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_SETLKW, 0, 1));
|
||||
append(SECCOMP_ALLOW);
|
||||
append(SECCOMP_LOAD_SYSCALL_NR);
|
||||
append(BPF_STMT(BPF_ALU | BPF_ADD | BPF_K, 0));
|
||||
}
|
||||
|
||||
void SeccompPolicy::allow_file_descriptor_operations()
|
||||
{
|
||||
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, read);
|
||||
|
|
@ -612,7 +773,9 @@ void SeccompPolicy::allow_file_descriptor_operations()
|
|||
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, pipe2);
|
||||
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, ftruncate);
|
||||
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, lseek);
|
||||
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, pread64);
|
||||
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, pwrite64);
|
||||
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, sendfile);
|
||||
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, memfd_create);
|
||||
|
||||
append(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_fcntl, 0, 5));
|
||||
|
|
@ -649,10 +812,25 @@ void SeccompPolicy::allow_file_descriptor_operations()
|
|||
append(SECCOMP_ALLOW);
|
||||
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_ioctl, 0, 5));
|
||||
append(SECCOMP_LOAD_ARGUMENT(1));
|
||||
append(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, FIONREAD, 0, 1));
|
||||
append(SECCOMP_ALLOW);
|
||||
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_ioctl, 0, 5));
|
||||
append(SECCOMP_LOAD_ARGUMENT(1));
|
||||
append(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, TCGETS, 0, 1));
|
||||
append(SECCOMP_ALLOW);
|
||||
append(SECCOMP_LOAD_SYSCALL_NR);
|
||||
append(BPF_STMT(BPF_ALU | BPF_ADD | BPF_K, 0));
|
||||
}
|
||||
|
||||
void SeccompPolicy::allow_ipc()
|
||||
{
|
||||
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, eventfd2);
|
||||
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, poll);
|
||||
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, ppoll);
|
||||
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, pselect6);
|
||||
|
|
@ -669,6 +847,17 @@ void SeccompPolicy::allow_ipc()
|
|||
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, getpeername);
|
||||
}
|
||||
|
||||
void SeccompPolicy::allow_network()
|
||||
{
|
||||
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, socket);
|
||||
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, connect);
|
||||
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, bind);
|
||||
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, listen);
|
||||
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, accept);
|
||||
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, accept4);
|
||||
SECCOMP_APPEND_ALLOW_SYSCALL_IF_DEFINED(*this, shutdown);
|
||||
}
|
||||
|
||||
void SeccompPolicy::allow_memory_without_executable_mappings()
|
||||
{
|
||||
append(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_mmap, 0, 5));
|
||||
|
|
|
|||
|
|
@ -19,8 +19,10 @@ public:
|
|||
void deny_readonly_filesystem_probes();
|
||||
void allow_readonly_file_opens();
|
||||
void allow_filesystem_metadata_queries();
|
||||
void allow_filesystem_writes();
|
||||
void allow_file_descriptor_operations();
|
||||
void allow_ipc();
|
||||
void allow_network();
|
||||
void allow_memory_without_executable_mappings();
|
||||
void allow_threads();
|
||||
void allow_signals();
|
||||
|
|
|
|||
|
|
@ -219,10 +219,13 @@ ErrorOr<NonnullRefPtr<WebWorkerClient>> launch_web_worker_process(Web::Bindings:
|
|||
|
||||
ErrorOr<NonnullRefPtr<Requests::RequestClient>> launch_request_server_process()
|
||||
{
|
||||
auto const& browser_options = Application::browser_options();
|
||||
auto const& request_server_options = Application::request_server_options();
|
||||
|
||||
Vector<ByteString> arguments;
|
||||
|
||||
if (browser_options.enable_sandbox == EnableSandbox::Yes)
|
||||
arguments.append("--enable-sandbox"sv);
|
||||
for (auto const& certificate : request_server_options.certificates)
|
||||
arguments.append(ByteString::formatted("--certificate={}", certificate));
|
||||
|
||||
|
|
|
|||
|
|
@ -4,9 +4,10 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/Vector.h>
|
||||
#include <Compositor/Sandbox.h>
|
||||
#include <LibCore/System.h>
|
||||
#include <LibCore/Directory.h>
|
||||
#include <LibCore/Environment.h>
|
||||
#include <LibCore/StandardPaths.h>
|
||||
#include <LibGfx/Font/FontDatabase.h>
|
||||
#include <LibSandbox/Sandbox.h>
|
||||
#include <LibSandbox/Seccomp.h>
|
||||
|
|
@ -19,25 +20,23 @@ ErrorOr<void> apply_sandbox()
|
|||
TRY(Sandbox::install_no_new_privileges());
|
||||
TRY(Sandbox::configure_runtime());
|
||||
|
||||
Vector<String> readable_path_strings;
|
||||
for (auto const& path : TRY(Gfx::FontDatabase::font_directories())) {
|
||||
if (auto result = Core::System::stat(path); result.is_error()) {
|
||||
if (result.error().is_errno() && result.error().code() == ENOENT)
|
||||
continue;
|
||||
return result.release_error();
|
||||
}
|
||||
readable_path_strings.append(path);
|
||||
}
|
||||
readable_path_strings.append(TRY(String::formatted("{}/fonts", WebView::s_ladybird_resource_root)));
|
||||
Vector<Sandbox::LandlockPath> paths;
|
||||
for (auto const& path : TRY(Gfx::FontDatabase::font_directories()))
|
||||
TRY(Sandbox::add_landlock_path_if_exists(paths, path, Sandbox::LandlockPath::Access::ReadOnly));
|
||||
TRY(Sandbox::add_landlock_path_if_exists(paths, TRY(String::formatted("{}/fonts", WebView::s_ladybird_resource_root)), Sandbox::LandlockPath::Access::ReadOnly));
|
||||
|
||||
Vector<StringView> readable_paths;
|
||||
for (auto const& path : readable_path_strings)
|
||||
readable_paths.append(path.bytes_as_string_view());
|
||||
TRY(Sandbox::restrict_filesystem_with_landlock(readable_paths.span()));
|
||||
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));
|
||||
|
||||
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_ipc();
|
||||
policy.allow_gpu_device_operations();
|
||||
|
|
|
|||
|
|
@ -8,6 +8,12 @@ set(SOURCES
|
|||
WebSocketImplCurl.cpp
|
||||
)
|
||||
|
||||
if (LINUX)
|
||||
list(APPEND SOURCES SandboxLinux.cpp)
|
||||
else()
|
||||
list(APPEND SOURCES SandboxUnimplemented.cpp)
|
||||
endif()
|
||||
|
||||
set(GENERATED_SOURCES
|
||||
RequestClientEndpoint.h
|
||||
RequestServerEndpoint.h
|
||||
|
|
@ -31,7 +37,7 @@ target_include_directories(requestserverservice PRIVATE ${CMAKE_CURRENT_BINARY_D
|
|||
target_include_directories(requestserverservice PRIVATE ${LADYBIRD_SOURCE_DIR}/Services/)
|
||||
|
||||
target_link_libraries(RequestServer PRIVATE requestserverservice)
|
||||
target_link_libraries(requestserverservice PUBLIC LibCore LibDNS LibHTTP LibIPC LibMain LibRequests LibTLS LibWebSocket LibURL LibTextCodec CURL::libcurl)
|
||||
target_link_libraries(requestserverservice PUBLIC LibCore LibDNS LibHTTP LibIPC LibMain LibRequests LibSandbox LibTLS LibWebSocket LibURL LibTextCodec CURL::libcurl)
|
||||
target_link_libraries(requestserverservice PRIVATE OpenSSL::Crypto OpenSSL::SSL)
|
||||
|
||||
if (WIN32)
|
||||
|
|
|
|||
17
Services/RequestServer/Sandbox.h
Normal file
17
Services/RequestServer/Sandbox.h
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright (c) 2026-present, the Ladybird developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/ByteString.h>
|
||||
#include <AK/Error.h>
|
||||
#include <AK/Vector.h>
|
||||
|
||||
namespace RequestServer {
|
||||
|
||||
[[nodiscard]] ErrorOr<void> apply_sandbox(Vector<ByteString> const& certificates);
|
||||
|
||||
}
|
||||
58
Services/RequestServer/SandboxLinux.cpp
Normal file
58
Services/RequestServer/SandboxLinux.cpp
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright (c) 2026-present, the Ladybird developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/LexicalPath.h>
|
||||
#include <AK/String.h>
|
||||
#include <LibCore/Directory.h>
|
||||
#include <LibCore/StandardPaths.h>
|
||||
#include <LibSandbox/Sandbox.h>
|
||||
#include <LibSandbox/Seccomp.h>
|
||||
#include <RequestServer/Sandbox.h>
|
||||
|
||||
namespace RequestServer {
|
||||
|
||||
ErrorOr<void> apply_sandbox(Vector<ByteString> const& certificates)
|
||||
{
|
||||
TRY(Sandbox::install_no_new_privileges());
|
||||
TRY(Sandbox::configure_runtime());
|
||||
|
||||
Vector<Sandbox::LandlockPath> 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));
|
||||
|
||||
TRY(Sandbox::add_landlock_path_if_exists(paths, "/etc/ssl"sv, Sandbox::LandlockPath::Access::ReadOnly));
|
||||
TRY(Sandbox::add_landlock_path_if_exists(paths, "/etc/host.conf"sv, Sandbox::LandlockPath::Access::ReadOnly));
|
||||
TRY(Sandbox::add_landlock_path_if_exists(paths, "/etc/hosts"sv, Sandbox::LandlockPath::Access::ReadOnly));
|
||||
TRY(Sandbox::add_landlock_path_if_exists(paths, "/etc/nsswitch.conf"sv, Sandbox::LandlockPath::Access::ReadOnly));
|
||||
TRY(Sandbox::add_landlock_path_if_exists(paths, "/etc/resolv.conf"sv, Sandbox::LandlockPath::Access::ReadOnly));
|
||||
TRY(Sandbox::add_landlock_path_if_exists(paths, "/run/systemd/resolve"sv, Sandbox::LandlockPath::Access::ReadOnly));
|
||||
|
||||
for (auto const& certificate : certificates) {
|
||||
auto certificate_path = LexicalPath::dirname(certificate);
|
||||
if (certificate_path.is_empty())
|
||||
certificate_path = ".";
|
||||
|
||||
TRY(Sandbox::add_landlock_path_if_exists(paths, certificate_path, Sandbox::LandlockPath::Access::ReadOnly));
|
||||
}
|
||||
|
||||
TRY(Sandbox::add_landlock_path_if_exists(paths, cache_path, Sandbox::LandlockPath::Access::ReadWrite));
|
||||
|
||||
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_ipc();
|
||||
policy.allow_network();
|
||||
policy.allow_common_runtime();
|
||||
TRY(policy.install());
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
||||
16
Services/RequestServer/SandboxUnimplemented.cpp
Normal file
16
Services/RequestServer/SandboxUnimplemented.cpp
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright (c) 2026-present, the Ladybird developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <RequestServer/Sandbox.h>
|
||||
|
||||
namespace RequestServer {
|
||||
|
||||
ErrorOr<void> apply_sandbox(Vector<ByteString> const&)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@
|
|||
#include <RequestServer/ConnectionFromClient.h>
|
||||
#include <RequestServer/Resolver.h>
|
||||
#include <RequestServer/ResourceSubstitutionMap.h>
|
||||
#include <RequestServer/Sandbox.h>
|
||||
|
||||
namespace RequestServer {
|
||||
|
||||
|
|
@ -43,6 +44,7 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
|
|||
StringView http_disk_cache_mode;
|
||||
StringView resource_map_path;
|
||||
bool wait_for_debugger = false;
|
||||
bool enable_sandbox = false;
|
||||
|
||||
Core::ArgsParser args_parser;
|
||||
args_parser.add_option(certificates, "Path to a certificate file", "certificate", 'C', "certificate");
|
||||
|
|
@ -50,6 +52,7 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
|
|||
args_parser.add_option(http_disk_cache_mode, "HTTP disk cache mode", "http-disk-cache-mode", 0, "mode");
|
||||
args_parser.add_option(resource_map_path, "Path to JSON file mapping URLs to local files", "resource-map", 0, "path");
|
||||
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)
|
||||
|
|
@ -97,6 +100,9 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
|
|||
disk_cache = cache.release_value();
|
||||
}
|
||||
|
||||
if (enable_sandbox)
|
||||
TRY(RequestServer::apply_sandbox(certificates));
|
||||
|
||||
// Connections are stored on the stack to ensure they are destroyed before static destruction begins. This prevents
|
||||
// crashes from notifiers trying to unregister from already-destroyed thread data during process exit.
|
||||
RequestServer::ConnectionFromClient::ConnectionMap connections;
|
||||
|
|
|
|||
Loading…
Reference in a new issue