mirror of
https://gitlab.torproject.org/tpo/core/tor.git
synced 2024-12-06 19:41:15 +01:00
eb0749d649
For some syscalls the kernel ABI uses 32 bit signed integers. Whether
these 32 bit integer values are sign extended or zero extended to the
native 64 bit register sizes is undefined and dependent on the {arch,
compiler, libc} being used. Instead of trying to detect which cases
zero-extend and which cases sign-extend, this commit uses a masked
equality check on the lower 32 bits of the value.
2178 lines
53 KiB
C
2178 lines
53 KiB
C
/* Copyright (c) 2001 Matej Pfajfar.
|
|
* Copyright (c) 2001-2004, Roger Dingledine.
|
|
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
|
|
* Copyright (c) 2007-2021, The Tor Project, Inc. */
|
|
/* See LICENSE for licensing information */
|
|
|
|
/**
|
|
* \file sandbox.c
|
|
* \brief Code to enable sandboxing.
|
|
**/
|
|
|
|
#include "orconfig.h"
|
|
|
|
#ifndef _LARGEFILE64_SOURCE
|
|
/**
|
|
* Temporarily required for O_LARGEFILE flag. Needs to be removed
|
|
* with the libevent fix.
|
|
*/
|
|
#define _LARGEFILE64_SOURCE
|
|
#endif /* !defined(_LARGEFILE64_SOURCE) */
|
|
|
|
/** Malloc mprotect limit in bytes.
|
|
*
|
|
* 28/06/2017: This value was increased from 16 MB to 20 MB after we introduced
|
|
* LZMA support in Tor (0.3.1.1-alpha). We limit our LZMA coder to 16 MB, but
|
|
* liblzma have a small overhead that we need to compensate for to avoid being
|
|
* killed by the sandbox.
|
|
*/
|
|
#define MALLOC_MP_LIM (20*1024*1024)
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
|
|
#include "lib/sandbox/sandbox.h"
|
|
#include "lib/container/map.h"
|
|
#include "lib/err/torerr.h"
|
|
#include "lib/log/log.h"
|
|
#include "lib/cc/torint.h"
|
|
#include "lib/malloc/malloc.h"
|
|
#include "lib/string/scanf.h"
|
|
|
|
#include "ext/tor_queue.h"
|
|
#include "ext/ht.h"
|
|
#include "ext/siphash.h"
|
|
|
|
#define DEBUGGING_CLOSE
|
|
|
|
#if defined(USE_LIBSECCOMP)
|
|
|
|
#include <sys/mman.h>
|
|
#include <sys/syscall.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/epoll.h>
|
|
#include <sys/prctl.h>
|
|
#include <linux/futex.h>
|
|
#include <sys/file.h>
|
|
|
|
#ifdef ENABLE_FRAGILE_HARDENING
|
|
#include <sys/ptrace.h>
|
|
#endif
|
|
|
|
#include <stdarg.h>
|
|
#include <seccomp.h>
|
|
#include <signal.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <time.h>
|
|
#include <poll.h>
|
|
|
|
#ifdef HAVE_GNU_LIBC_VERSION_H
|
|
#include <gnu/libc-version.h>
|
|
#endif
|
|
#ifdef HAVE_LINUX_NETFILTER_IPV4_H
|
|
#include <linux/netfilter_ipv4.h>
|
|
#endif
|
|
#ifdef HAVE_LINUX_IF_H
|
|
#include <linux/if.h>
|
|
#endif
|
|
#ifdef HAVE_LINUX_NETFILTER_IPV6_IP6_TABLES_H
|
|
#include <linux/netfilter_ipv6/ip6_tables.h>
|
|
#endif
|
|
|
|
#if defined(HAVE_EXECINFO_H) && defined(HAVE_BACKTRACE) && \
|
|
defined(HAVE_BACKTRACE_SYMBOLS_FD) && defined(HAVE_SIGACTION)
|
|
#define USE_BACKTRACE
|
|
#define BACKTRACE_PRIVATE
|
|
#include "lib/err/backtrace.h"
|
|
#endif /* defined(HAVE_EXECINFO_H) && defined(HAVE_BACKTRACE) && ... */
|
|
|
|
#ifdef USE_BACKTRACE
|
|
#include <execinfo.h>
|
|
#endif
|
|
|
|
/**
|
|
* Linux 32 bit definitions
|
|
*/
|
|
#if defined(__i386__)
|
|
|
|
#define REG_SYSCALL REG_EAX
|
|
#define M_SYSCALL gregs[REG_SYSCALL]
|
|
|
|
/**
|
|
* Linux 64 bit definitions
|
|
*/
|
|
#elif defined(__x86_64__)
|
|
|
|
#define REG_SYSCALL REG_RAX
|
|
#define M_SYSCALL gregs[REG_SYSCALL]
|
|
|
|
#elif defined(__arm__)
|
|
|
|
#define M_SYSCALL arm_r7
|
|
|
|
#elif defined(__aarch64__) && defined(__LP64__)
|
|
|
|
#define REG_SYSCALL 8
|
|
#define M_SYSCALL regs[REG_SYSCALL]
|
|
|
|
#endif /* defined(__i386__) || ... */
|
|
|
|
#ifdef M_SYSCALL
|
|
#define SYSCALL_NAME_DEBUGGING
|
|
#endif
|
|
|
|
/**Determines if at least one sandbox is active.*/
|
|
static int sandbox_active = 0;
|
|
/** Holds the parameter list configuration for the sandbox.*/
|
|
static sandbox_cfg_t *filter_dynamic = NULL;
|
|
|
|
#undef SCMP_CMP
|
|
#define SCMP_CMP(a,b,c) ((struct scmp_arg_cmp){(a),(b),(c),0})
|
|
#define SCMP_CMP_STR(a,b,c) \
|
|
((struct scmp_arg_cmp) {(a),(b),(intptr_t)(void*)(c),0})
|
|
#define SCMP_CMP4(a,b,c,d) ((struct scmp_arg_cmp){(a),(b),(c),(d)})
|
|
/* We use a wrapper here because these masked comparisons seem to be pretty
|
|
* verbose. Also, it's important to cast to scmp_datum_t before negating the
|
|
* mask, since otherwise the negation might get applied to a 32 bit value, and
|
|
* the high bits of the value might get masked out improperly. */
|
|
#define SCMP_CMP_MASKED(a,b,c) \
|
|
SCMP_CMP4((a), SCMP_CMP_MASKED_EQ, ~(scmp_datum_t)(b), (c))
|
|
/* Negative constants aren't consistently sign extended or zero extended.
|
|
* Different compilers, libc, and architectures behave differently. For cases
|
|
* where the kernel ABI uses a 32 bit integer, this macro can be used to
|
|
* mask-compare only the lower 32 bits of the value. */
|
|
#define SCMP_CMP_LOWER32_EQ(a,b) \
|
|
SCMP_CMP4((a), SCMP_CMP_MASKED_EQ, 0xFFFFFFFF, (unsigned int)(b))
|
|
|
|
/** Variable used for storing all syscall numbers that will be allowed with the
|
|
* stage 1 general Tor sandbox.
|
|
*/
|
|
static int filter_nopar_gen[] = {
|
|
SCMP_SYS(access),
|
|
SCMP_SYS(brk),
|
|
#ifdef __NR_clock_gettime64
|
|
SCMP_SYS(clock_gettime64),
|
|
#else
|
|
SCMP_SYS(clock_gettime),
|
|
#endif
|
|
SCMP_SYS(close),
|
|
SCMP_SYS(clone),
|
|
SCMP_SYS(dup),
|
|
#ifdef __NR_clone3
|
|
SCMP_SYS(clone3),
|
|
#endif
|
|
SCMP_SYS(epoll_create),
|
|
SCMP_SYS(epoll_wait),
|
|
#ifdef __NR_epoll_pwait
|
|
SCMP_SYS(epoll_pwait),
|
|
#endif
|
|
#ifdef HAVE_EVENTFD
|
|
SCMP_SYS(eventfd2),
|
|
#endif
|
|
#ifdef HAVE_PIPE2
|
|
SCMP_SYS(pipe2),
|
|
#endif
|
|
#ifdef HAVE_PIPE
|
|
SCMP_SYS(pipe),
|
|
#endif
|
|
#ifdef __NR_fchmod
|
|
SCMP_SYS(fchmod),
|
|
#endif
|
|
SCMP_SYS(fcntl),
|
|
SCMP_SYS(fstat),
|
|
#ifdef __NR_fstat64
|
|
SCMP_SYS(fstat64),
|
|
#endif
|
|
SCMP_SYS(fsync),
|
|
SCMP_SYS(futex),
|
|
SCMP_SYS(getdents),
|
|
SCMP_SYS(getdents64),
|
|
SCMP_SYS(getegid),
|
|
#ifdef __NR_getegid32
|
|
SCMP_SYS(getegid32),
|
|
#endif
|
|
SCMP_SYS(geteuid),
|
|
#ifdef __NR_geteuid32
|
|
SCMP_SYS(geteuid32),
|
|
#endif
|
|
SCMP_SYS(getgid),
|
|
#ifdef __NR_getgid32
|
|
SCMP_SYS(getgid32),
|
|
#endif
|
|
SCMP_SYS(getpid),
|
|
#ifdef ENABLE_FRAGILE_HARDENING
|
|
SCMP_SYS(getppid),
|
|
#endif
|
|
#ifdef __NR_getrlimit
|
|
SCMP_SYS(getrlimit),
|
|
#endif
|
|
SCMP_SYS(gettimeofday),
|
|
SCMP_SYS(gettid),
|
|
SCMP_SYS(getuid),
|
|
#ifdef __NR_getuid32
|
|
SCMP_SYS(getuid32),
|
|
#endif
|
|
SCMP_SYS(lseek),
|
|
#ifdef __NR__llseek
|
|
SCMP_SYS(_llseek),
|
|
#endif
|
|
// glob uses this..
|
|
SCMP_SYS(lstat),
|
|
SCMP_SYS(mkdir),
|
|
SCMP_SYS(mlockall),
|
|
#ifdef __NR_mmap
|
|
/* XXXX restrict this in the same ways as mmap2 */
|
|
SCMP_SYS(mmap),
|
|
#endif
|
|
SCMP_SYS(munmap),
|
|
#ifdef __NR_nanosleep
|
|
SCMP_SYS(nanosleep),
|
|
#endif
|
|
#ifdef __NR_prlimit
|
|
SCMP_SYS(prlimit),
|
|
#endif
|
|
#ifdef __NR_prlimit64
|
|
SCMP_SYS(prlimit64),
|
|
#endif
|
|
SCMP_SYS(read),
|
|
SCMP_SYS(rt_sigreturn),
|
|
SCMP_SYS(sched_getaffinity),
|
|
#ifdef __NR_sched_yield
|
|
SCMP_SYS(sched_yield),
|
|
#endif
|
|
SCMP_SYS(sendmsg),
|
|
SCMP_SYS(set_robust_list),
|
|
#ifdef __NR_setrlimit
|
|
SCMP_SYS(setrlimit),
|
|
#endif
|
|
SCMP_SYS(shutdown),
|
|
#ifdef __NR_sigaltstack
|
|
SCMP_SYS(sigaltstack),
|
|
#endif
|
|
#ifdef __NR_sigreturn
|
|
SCMP_SYS(sigreturn),
|
|
#endif
|
|
SCMP_SYS(stat),
|
|
#if defined(__i386__) && defined(__NR_statx)
|
|
SCMP_SYS(statx),
|
|
#endif
|
|
SCMP_SYS(uname),
|
|
SCMP_SYS(wait4),
|
|
SCMP_SYS(write),
|
|
SCMP_SYS(writev),
|
|
SCMP_SYS(exit_group),
|
|
SCMP_SYS(exit),
|
|
|
|
SCMP_SYS(madvise),
|
|
#ifdef __NR_stat64
|
|
// getaddrinfo uses this..
|
|
SCMP_SYS(stat64),
|
|
#endif
|
|
|
|
#ifdef __NR_getrandom
|
|
SCMP_SYS(getrandom),
|
|
#endif
|
|
|
|
#ifdef __NR_sysinfo
|
|
// qsort uses this..
|
|
SCMP_SYS(sysinfo),
|
|
#endif
|
|
/*
|
|
* These socket syscalls are not required on x86_64 and not supported with
|
|
* some libseccomp versions (eg: 1.0.1)
|
|
*/
|
|
#if defined(__i386)
|
|
SCMP_SYS(recv),
|
|
SCMP_SYS(send),
|
|
#endif
|
|
|
|
// socket syscalls
|
|
SCMP_SYS(bind),
|
|
SCMP_SYS(listen),
|
|
SCMP_SYS(connect),
|
|
SCMP_SYS(getsockname),
|
|
#ifdef ENABLE_NSS
|
|
#ifdef __NR_getpeername
|
|
SCMP_SYS(getpeername),
|
|
#endif
|
|
#endif
|
|
SCMP_SYS(recvmsg),
|
|
SCMP_SYS(recvfrom),
|
|
SCMP_SYS(sendto),
|
|
SCMP_SYS(unlink),
|
|
#ifdef __NR_unlinkat
|
|
SCMP_SYS(unlinkat),
|
|
#endif
|
|
SCMP_SYS(poll)
|
|
};
|
|
|
|
/* opendir is not a syscall but it will use either open or openat. We do not
|
|
* want the decision to allow open/openat to be the callers reponsability, so
|
|
* we create a phony syscall number for opendir and sb_opendir will choose the
|
|
* correct syscall. */
|
|
#define PHONY_OPENDIR_SYSCALL -2
|
|
|
|
/* These macros help avoid the error where the number of filters we add on a
|
|
* single rule don't match the arg_cnt param. */
|
|
#define seccomp_rule_add_0(ctx,act,call) \
|
|
seccomp_rule_add((ctx),(act),(call),0)
|
|
#define seccomp_rule_add_1(ctx,act,call,f1) \
|
|
seccomp_rule_add((ctx),(act),(call),1,(f1))
|
|
#define seccomp_rule_add_2(ctx,act,call,f1,f2) \
|
|
seccomp_rule_add((ctx),(act),(call),2,(f1),(f2))
|
|
#define seccomp_rule_add_3(ctx,act,call,f1,f2,f3) \
|
|
seccomp_rule_add((ctx),(act),(call),3,(f1),(f2),(f3))
|
|
#define seccomp_rule_add_4(ctx,act,call,f1,f2,f3,f4) \
|
|
seccomp_rule_add((ctx),(act),(call),4,(f1),(f2),(f3),(f4))
|
|
|
|
static const char *sandbox_get_interned_string(const char *str);
|
|
|
|
/**
|
|
* Function responsible for setting up the rt_sigaction syscall for
|
|
* the seccomp filter sandbox.
|
|
*/
|
|
static int
|
|
sb_rt_sigaction(scmp_filter_ctx ctx, sandbox_cfg_t *filter)
|
|
{
|
|
unsigned i;
|
|
int rc;
|
|
int param[] = { SIGINT, SIGTERM, SIGPIPE, SIGUSR1, SIGUSR2, SIGHUP, SIGCHLD,
|
|
SIGSEGV, SIGILL, SIGFPE, SIGBUS, SIGSYS, SIGIO,
|
|
#ifdef SIGXFSZ
|
|
SIGXFSZ
|
|
#endif
|
|
};
|
|
(void) filter;
|
|
|
|
for (i = 0; i < ARRAY_LENGTH(param); i++) {
|
|
rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigaction),
|
|
SCMP_CMP(0, SCMP_CMP_EQ, param[i]));
|
|
if (rc)
|
|
break;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
#ifdef __NR_time
|
|
/**
|
|
* Function responsible for setting up the time syscall for
|
|
* the seccomp filter sandbox.
|
|
*/
|
|
static int
|
|
sb_time(scmp_filter_ctx ctx, sandbox_cfg_t *filter)
|
|
{
|
|
(void) filter;
|
|
|
|
return seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(time),
|
|
SCMP_CMP(0, SCMP_CMP_EQ, 0));
|
|
}
|
|
#endif /* defined(__NR_time) */
|
|
|
|
/**
|
|
* Function responsible for setting up the accept4 syscall for
|
|
* the seccomp filter sandbox.
|
|
*/
|
|
static int
|
|
sb_accept4(scmp_filter_ctx ctx, sandbox_cfg_t *filter)
|
|
{
|
|
int rc = 0;
|
|
(void)filter;
|
|
|
|
#ifdef __i386__
|
|
rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socketcall),
|
|
SCMP_CMP(0, SCMP_CMP_EQ, 18));
|
|
if (rc) {
|
|
return rc;
|
|
}
|
|
#endif /* defined(__i386__) */
|
|
|
|
rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(accept4),
|
|
SCMP_CMP_MASKED(3, SOCK_CLOEXEC|SOCK_NONBLOCK, 0));
|
|
if (rc) {
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef __NR_mmap2
|
|
/**
|
|
* Function responsible for setting up the mmap2 syscall for
|
|
* the seccomp filter sandbox.
|
|
*/
|
|
static int
|
|
sb_mmap2(scmp_filter_ctx ctx, sandbox_cfg_t *filter)
|
|
{
|
|
int rc = 0;
|
|
(void)filter;
|
|
|
|
rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap2),
|
|
SCMP_CMP(2, SCMP_CMP_EQ, PROT_READ),
|
|
SCMP_CMP(3, SCMP_CMP_EQ, MAP_PRIVATE));
|
|
if (rc) {
|
|
return rc;
|
|
}
|
|
|
|
rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap2),
|
|
SCMP_CMP(2, SCMP_CMP_EQ, PROT_NONE),
|
|
SCMP_CMP(3, SCMP_CMP_EQ, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE));
|
|
if (rc) {
|
|
return rc;
|
|
}
|
|
|
|
rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap2),
|
|
SCMP_CMP(2, SCMP_CMP_EQ, PROT_READ|PROT_WRITE),
|
|
SCMP_CMP(3, SCMP_CMP_EQ, MAP_PRIVATE|MAP_ANONYMOUS));
|
|
if (rc) {
|
|
return rc;
|
|
}
|
|
|
|
rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap2),
|
|
SCMP_CMP(2, SCMP_CMP_EQ, PROT_READ|PROT_WRITE),
|
|
SCMP_CMP(3, SCMP_CMP_EQ,MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK));
|
|
if (rc) {
|
|
return rc;
|
|
}
|
|
|
|
rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap2),
|
|
SCMP_CMP(2, SCMP_CMP_EQ, PROT_READ|PROT_WRITE),
|
|
SCMP_CMP(3, SCMP_CMP_EQ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE));
|
|
if (rc) {
|
|
return rc;
|
|
}
|
|
|
|
rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap2),
|
|
SCMP_CMP(2, SCMP_CMP_EQ, PROT_READ|PROT_WRITE),
|
|
SCMP_CMP(3, SCMP_CMP_EQ, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS));
|
|
if (rc) {
|
|
return rc;
|
|
}
|
|
|
|
rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap2),
|
|
SCMP_CMP(2, SCMP_CMP_EQ, PROT_READ|PROT_EXEC),
|
|
SCMP_CMP(3, SCMP_CMP_EQ, MAP_PRIVATE|MAP_DENYWRITE));
|
|
if (rc) {
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif /* defined(__NR_mmap2) */
|
|
|
|
#ifdef HAVE_GNU_LIBC_VERSION_H
|
|
#ifdef HAVE_GNU_GET_LIBC_VERSION
|
|
#define CHECK_LIBC_VERSION
|
|
#endif
|
|
#endif
|
|
|
|
/* Return true the libc version is greater or equal than
|
|
* <b>major</b>.<b>minor</b>. Returns false otherwise. */
|
|
static int
|
|
is_libc_at_least(int major, int minor)
|
|
{
|
|
#ifdef CHECK_LIBC_VERSION
|
|
const char *version = gnu_get_libc_version();
|
|
if (version == NULL)
|
|
return 0;
|
|
|
|
int libc_major = -1;
|
|
int libc_minor = -1;
|
|
|
|
tor_sscanf(version, "%d.%d", &libc_major, &libc_minor);
|
|
if (libc_major > major)
|
|
return 1;
|
|
else if (libc_major == major && libc_minor >= minor)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
#else /* !defined(CHECK_LIBC_VERSION) */
|
|
(void)major;
|
|
(void)minor;
|
|
return 0;
|
|
#endif /* defined(CHECK_LIBC_VERSION) */
|
|
}
|
|
|
|
/* Return true if we think we're running with a libc that uses openat for the
|
|
* open function on linux. */
|
|
static int
|
|
libc_uses_openat_for_open(void)
|
|
{
|
|
return is_libc_at_least(2, 26);
|
|
}
|
|
|
|
/* Return true if we think we're running with a libc that uses openat for the
|
|
* opendir function on linux. */
|
|
static int
|
|
libc_uses_openat_for_opendir(void)
|
|
{
|
|
// libc 2.27 and above or between 2.15 (inclusive) and 2.22 (exclusive)
|
|
return is_libc_at_least(2, 27) ||
|
|
(is_libc_at_least(2, 15) && !is_libc_at_least(2, 22));
|
|
}
|
|
|
|
/** Allow a single file to be opened. If <b>use_openat</b> is true,
|
|
* we're using a libc that remaps all the opens into openats. */
|
|
static int
|
|
allow_file_open(scmp_filter_ctx ctx, int use_openat, const char *file)
|
|
{
|
|
if (use_openat) {
|
|
return seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(openat),
|
|
SCMP_CMP_LOWER32_EQ(0, AT_FDCWD),
|
|
SCMP_CMP_STR(1, SCMP_CMP_EQ, file));
|
|
} else {
|
|
return seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open),
|
|
SCMP_CMP_STR(0, SCMP_CMP_EQ, file));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Function responsible for setting up the open syscall for
|
|
* the seccomp filter sandbox.
|
|
*/
|
|
static int
|
|
sb_open(scmp_filter_ctx ctx, sandbox_cfg_t *filter)
|
|
{
|
|
int rc;
|
|
sandbox_cfg_t *elem = NULL;
|
|
|
|
int use_openat = libc_uses_openat_for_open();
|
|
|
|
#ifdef ENABLE_FRAGILE_HARDENING
|
|
/* AddressSanitizer uses the "open" syscall to access information about the
|
|
* running process via the filesystem, so that call must be allowed without
|
|
* restriction or the sanitizer will be unable to execute normally when the
|
|
* process terminates. */
|
|
rc = seccomp_rule_add_0(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open));
|
|
if (rc != 0) {
|
|
log_err(LD_BUG,"(Sandbox) failed to add open syscall, received "
|
|
"libseccomp error %d", rc);
|
|
return rc;
|
|
}
|
|
|
|
/* If glibc also uses only the "open" syscall to open files on this system
|
|
* there is no need to consider any additional rules. */
|
|
if (!use_openat)
|
|
return 0;
|
|
#endif
|
|
|
|
// for each dynamic parameter filters
|
|
for (elem = filter; elem != NULL; elem = elem->next) {
|
|
smp_param_t *param = elem->param;
|
|
|
|
if (param != NULL && param->prot == 1 && param->syscall
|
|
== SCMP_SYS(open)) {
|
|
rc = allow_file_open(ctx, use_openat, param->value);
|
|
if (rc != 0) {
|
|
log_err(LD_BUG,"(Sandbox) failed to add open syscall, received "
|
|
"libseccomp error %d", rc);
|
|
return rc;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
sb_chmod(scmp_filter_ctx ctx, sandbox_cfg_t *filter)
|
|
{
|
|
int rc;
|
|
sandbox_cfg_t *elem = NULL;
|
|
|
|
// for each dynamic parameter filters
|
|
for (elem = filter; elem != NULL; elem = elem->next) {
|
|
smp_param_t *param = elem->param;
|
|
|
|
if (param != NULL && param->prot == 1 && param->syscall
|
|
== SCMP_SYS(chmod)) {
|
|
rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(chmod),
|
|
SCMP_CMP_STR(0, SCMP_CMP_EQ, param->value));
|
|
if (rc != 0) {
|
|
log_err(LD_BUG,"(Sandbox) failed to add chmod syscall, received "
|
|
"libseccomp error %d", rc);
|
|
return rc;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
sb_fchmodat(scmp_filter_ctx ctx, sandbox_cfg_t *filter)
|
|
{
|
|
int rc;
|
|
sandbox_cfg_t *elem = NULL;
|
|
|
|
// for each dynamic parameter filters
|
|
for (elem = filter; elem != NULL; elem = elem->next) {
|
|
smp_param_t *param = elem->param;
|
|
|
|
if (param != NULL && param->prot == 1 && param->syscall
|
|
== SCMP_SYS(fchmodat)) {
|
|
rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fchmodat),
|
|
SCMP_CMP_LOWER32_EQ(0, AT_FDCWD),
|
|
SCMP_CMP_STR(1, SCMP_CMP_EQ, param->value));
|
|
if (rc != 0) {
|
|
log_err(LD_BUG,"(Sandbox) failed to add fchmodat syscall, received "
|
|
"libseccomp error %d", rc);
|
|
return rc;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef __i386__
|
|
static int
|
|
sb_chown32(scmp_filter_ctx ctx, sandbox_cfg_t *filter)
|
|
{
|
|
int rc;
|
|
sandbox_cfg_t *elem = NULL;
|
|
|
|
// for each dynamic parameter filters
|
|
for (elem = filter; elem != NULL; elem = elem->next) {
|
|
smp_param_t *param = elem->param;
|
|
|
|
if (param != NULL && param->prot == 1 && param->syscall
|
|
== SCMP_SYS(chown32)) {
|
|
rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(chown32),
|
|
SCMP_CMP_STR(0, SCMP_CMP_EQ, param->value));
|
|
if (rc != 0) {
|
|
log_err(LD_BUG,"(Sandbox) failed to add chown32 syscall, received "
|
|
"libseccomp error %d", rc);
|
|
return rc;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#else
|
|
static int
|
|
sb_chown(scmp_filter_ctx ctx, sandbox_cfg_t *filter)
|
|
{
|
|
int rc;
|
|
sandbox_cfg_t *elem = NULL;
|
|
|
|
// for each dynamic parameter filters
|
|
for (elem = filter; elem != NULL; elem = elem->next) {
|
|
smp_param_t *param = elem->param;
|
|
|
|
if (param != NULL && param->prot == 1 && param->syscall
|
|
== SCMP_SYS(chown)) {
|
|
rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(chown),
|
|
SCMP_CMP_STR(0, SCMP_CMP_EQ, param->value));
|
|
if (rc != 0) {
|
|
log_err(LD_BUG,"(Sandbox) failed to add chown syscall, received "
|
|
"libseccomp error %d", rc);
|
|
return rc;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif /* defined(__i386__) */
|
|
|
|
static int
|
|
sb_fchownat(scmp_filter_ctx ctx, sandbox_cfg_t *filter)
|
|
{
|
|
int rc;
|
|
sandbox_cfg_t *elem = NULL;
|
|
|
|
// for each dynamic parameter filters
|
|
for (elem = filter; elem != NULL; elem = elem->next) {
|
|
smp_param_t *param = elem->param;
|
|
|
|
if (param != NULL && param->prot == 1 && param->syscall
|
|
== SCMP_SYS(fchownat)) {
|
|
rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fchownat),
|
|
SCMP_CMP_LOWER32_EQ(0, AT_FDCWD),
|
|
SCMP_CMP_STR(1, SCMP_CMP_EQ, param->value));
|
|
if (rc != 0) {
|
|
log_err(LD_BUG,"(Sandbox) failed to add fchownat syscall, received "
|
|
"libseccomp error %d", rc);
|
|
return rc;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Function responsible for setting up the rename syscall for
|
|
* the seccomp filter sandbox.
|
|
*/
|
|
static int
|
|
sb_rename(scmp_filter_ctx ctx, sandbox_cfg_t *filter)
|
|
{
|
|
int rc;
|
|
sandbox_cfg_t *elem = NULL;
|
|
|
|
// for each dynamic parameter filters
|
|
for (elem = filter; elem != NULL; elem = elem->next) {
|
|
smp_param_t *param = elem->param;
|
|
|
|
if (param != NULL && param->prot == 1 &&
|
|
param->syscall == SCMP_SYS(rename)) {
|
|
|
|
rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rename),
|
|
SCMP_CMP_STR(0, SCMP_CMP_EQ, param->value),
|
|
SCMP_CMP_STR(1, SCMP_CMP_EQ, param->value2));
|
|
if (rc != 0) {
|
|
log_err(LD_BUG,"(Sandbox) failed to add rename syscall, received "
|
|
"libseccomp error %d", rc);
|
|
return rc;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Function responsible for setting up the renameat syscall for
|
|
* the seccomp filter sandbox.
|
|
*/
|
|
static int
|
|
sb_renameat(scmp_filter_ctx ctx, sandbox_cfg_t *filter)
|
|
{
|
|
int rc;
|
|
sandbox_cfg_t *elem = NULL;
|
|
|
|
// for each dynamic parameter filters
|
|
for (elem = filter; elem != NULL; elem = elem->next) {
|
|
smp_param_t *param = elem->param;
|
|
|
|
if (param != NULL && param->prot == 1 &&
|
|
param->syscall == SCMP_SYS(renameat)) {
|
|
|
|
rc = seccomp_rule_add_4(ctx, SCMP_ACT_ALLOW, SCMP_SYS(renameat),
|
|
SCMP_CMP_LOWER32_EQ(0, AT_FDCWD),
|
|
SCMP_CMP_STR(1, SCMP_CMP_EQ, param->value),
|
|
SCMP_CMP_LOWER32_EQ(2, AT_FDCWD),
|
|
SCMP_CMP_STR(3, SCMP_CMP_EQ, param->value2));
|
|
if (rc != 0) {
|
|
log_err(LD_BUG,"(Sandbox) failed to add renameat syscall, received "
|
|
"libseccomp error %d", rc);
|
|
return rc;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Function responsible for setting up the openat syscall for
|
|
* the seccomp filter sandbox.
|
|
*/
|
|
static int
|
|
sb_openat(scmp_filter_ctx ctx, sandbox_cfg_t *filter)
|
|
{
|
|
int rc;
|
|
sandbox_cfg_t *elem = NULL;
|
|
|
|
// for each dynamic parameter filters
|
|
for (elem = filter; elem != NULL; elem = elem->next) {
|
|
smp_param_t *param = elem->param;
|
|
|
|
if (param != NULL && param->prot == 1 && param->syscall
|
|
== SCMP_SYS(openat)) {
|
|
rc = seccomp_rule_add_3(ctx, SCMP_ACT_ALLOW, SCMP_SYS(openat),
|
|
SCMP_CMP_LOWER32_EQ(0, AT_FDCWD),
|
|
SCMP_CMP_STR(1, SCMP_CMP_EQ, param->value),
|
|
SCMP_CMP(2, SCMP_CMP_EQ, O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_DIRECTORY|
|
|
O_CLOEXEC));
|
|
if (rc != 0) {
|
|
log_err(LD_BUG,"(Sandbox) failed to add openat syscall, received "
|
|
"libseccomp error %d", rc);
|
|
return rc;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
sb_opendir(scmp_filter_ctx ctx, sandbox_cfg_t *filter)
|
|
{
|
|
int rc;
|
|
sandbox_cfg_t *elem = NULL;
|
|
|
|
// for each dynamic parameter filters
|
|
for (elem = filter; elem != NULL; elem = elem->next) {
|
|
smp_param_t *param = elem->param;
|
|
|
|
if (param != NULL && param->prot == 1 && param->syscall
|
|
== PHONY_OPENDIR_SYSCALL) {
|
|
rc = allow_file_open(ctx, libc_uses_openat_for_opendir(), param->value);
|
|
if (rc != 0) {
|
|
log_err(LD_BUG,"(Sandbox) failed to add openat syscall, received "
|
|
"libseccomp error %d", rc);
|
|
return rc;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef ENABLE_FRAGILE_HARDENING
|
|
/**
|
|
* Function responsible for setting up the ptrace syscall for
|
|
* the seccomp filter sandbox.
|
|
*/
|
|
static int
|
|
sb_ptrace(scmp_filter_ctx ctx, sandbox_cfg_t *filter)
|
|
{
|
|
int rc;
|
|
pid_t pid = getpid();
|
|
(void) filter;
|
|
|
|
rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ptrace),
|
|
SCMP_CMP(0, SCMP_CMP_EQ, PTRACE_ATTACH),
|
|
SCMP_CMP(1, SCMP_CMP_EQ, pid));
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ptrace),
|
|
SCMP_CMP(0, SCMP_CMP_EQ, PTRACE_GETREGS),
|
|
SCMP_CMP(1, SCMP_CMP_EQ, pid));
|
|
if (rc)
|
|
return rc;
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* Function responsible for setting up the socket syscall for
|
|
* the seccomp filter sandbox.
|
|
*/
|
|
static int
|
|
sb_socket(scmp_filter_ctx ctx, sandbox_cfg_t *filter)
|
|
{
|
|
int rc = 0;
|
|
int i, j;
|
|
(void) filter;
|
|
|
|
#ifdef __i386__
|
|
rc = seccomp_rule_add_0(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket));
|
|
if (rc)
|
|
return rc;
|
|
#endif
|
|
|
|
rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket),
|
|
SCMP_CMP(0, SCMP_CMP_EQ, PF_FILE),
|
|
SCMP_CMP_MASKED(1, SOCK_CLOEXEC|SOCK_NONBLOCK, SOCK_STREAM));
|
|
if (rc)
|
|
return rc;
|
|
|
|
for (i = 0; i < 2; ++i) {
|
|
const int pf = i ? PF_INET : PF_INET6;
|
|
for (j=0; j < 3; ++j) {
|
|
const int type = (j == 0) ? SOCK_STREAM :
|
|
SOCK_DGRAM;
|
|
const int protocol = (j == 0) ? IPPROTO_TCP :
|
|
(j == 1) ? IPPROTO_IP :
|
|
IPPROTO_UDP;
|
|
rc = seccomp_rule_add_3(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket),
|
|
SCMP_CMP(0, SCMP_CMP_EQ, pf),
|
|
SCMP_CMP_MASKED(1, SOCK_CLOEXEC|SOCK_NONBLOCK, type),
|
|
SCMP_CMP(2, SCMP_CMP_EQ, protocol));
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
#ifdef ENABLE_NSS
|
|
rc = seccomp_rule_add_3(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket),
|
|
SCMP_CMP(0, SCMP_CMP_EQ, PF_INET),
|
|
SCMP_CMP(1, SCMP_CMP_EQ, SOCK_STREAM),
|
|
SCMP_CMP(2, SCMP_CMP_EQ, IPPROTO_IP));
|
|
if (rc)
|
|
return rc;
|
|
#endif /* defined(ENABLE_NSS) */
|
|
|
|
rc = seccomp_rule_add_3(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket),
|
|
SCMP_CMP(0, SCMP_CMP_EQ, PF_UNIX),
|
|
SCMP_CMP_MASKED(1, SOCK_CLOEXEC|SOCK_NONBLOCK, SOCK_STREAM),
|
|
SCMP_CMP(2, SCMP_CMP_EQ, 0));
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = seccomp_rule_add_3(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket),
|
|
SCMP_CMP(0, SCMP_CMP_EQ, PF_UNIX),
|
|
SCMP_CMP_MASKED(1, SOCK_CLOEXEC|SOCK_NONBLOCK, SOCK_DGRAM),
|
|
SCMP_CMP(2, SCMP_CMP_EQ, 0));
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = seccomp_rule_add_3(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket),
|
|
SCMP_CMP(0, SCMP_CMP_EQ, PF_NETLINK),
|
|
SCMP_CMP_MASKED(1, SOCK_CLOEXEC, SOCK_RAW),
|
|
SCMP_CMP(2, SCMP_CMP_EQ, 0));
|
|
if (rc)
|
|
return rc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Function responsible for setting up the socketpair syscall for
|
|
* the seccomp filter sandbox.
|
|
*/
|
|
static int
|
|
sb_socketpair(scmp_filter_ctx ctx, sandbox_cfg_t *filter)
|
|
{
|
|
int rc = 0;
|
|
(void) filter;
|
|
|
|
#ifdef __i386__
|
|
rc = seccomp_rule_add_0(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socketpair));
|
|
if (rc)
|
|
return rc;
|
|
#endif
|
|
|
|
rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socketpair),
|
|
SCMP_CMP(0, SCMP_CMP_EQ, PF_FILE),
|
|
SCMP_CMP(1, SCMP_CMP_EQ, SOCK_STREAM|SOCK_CLOEXEC));
|
|
if (rc)
|
|
return rc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef HAVE_KIST_SUPPORT
|
|
|
|
#include <linux/sockios.h>
|
|
|
|
static int
|
|
sb_ioctl(scmp_filter_ctx ctx, sandbox_cfg_t *filter)
|
|
{
|
|
int rc;
|
|
(void) filter;
|
|
|
|
rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl),
|
|
SCMP_CMP(1, SCMP_CMP_EQ, SIOCOUTQNSD));
|
|
if (rc)
|
|
return rc;
|
|
return 0;
|
|
}
|
|
|
|
#endif /* defined(HAVE_KIST_SUPPORT) */
|
|
|
|
/**
|
|
* Function responsible for setting up the setsockopt syscall for
|
|
* the seccomp filter sandbox.
|
|
*/
|
|
static int
|
|
sb_setsockopt(scmp_filter_ctx ctx, sandbox_cfg_t *filter)
|
|
{
|
|
int rc = 0;
|
|
(void) filter;
|
|
|
|
#ifdef __i386__
|
|
rc = seccomp_rule_add_0(ctx, SCMP_ACT_ALLOW, SCMP_SYS(setsockopt));
|
|
if (rc)
|
|
return rc;
|
|
#endif
|
|
|
|
rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(setsockopt),
|
|
SCMP_CMP(1, SCMP_CMP_EQ, SOL_SOCKET),
|
|
SCMP_CMP(2, SCMP_CMP_EQ, SO_REUSEADDR));
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(setsockopt),
|
|
SCMP_CMP(1, SCMP_CMP_EQ, SOL_SOCKET),
|
|
SCMP_CMP(2, SCMP_CMP_EQ, SO_SNDBUF));
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(setsockopt),
|
|
SCMP_CMP(1, SCMP_CMP_EQ, SOL_SOCKET),
|
|
SCMP_CMP(2, SCMP_CMP_EQ, SO_RCVBUF));
|
|
if (rc)
|
|
return rc;
|
|
|
|
#ifdef HAVE_SYSTEMD
|
|
rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(setsockopt),
|
|
SCMP_CMP(1, SCMP_CMP_EQ, SOL_SOCKET),
|
|
SCMP_CMP(2, SCMP_CMP_EQ, SO_SNDBUFFORCE));
|
|
if (rc)
|
|
return rc;
|
|
#endif /* defined(HAVE_SYSTEMD) */
|
|
|
|
#ifdef IP_TRANSPARENT
|
|
rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(setsockopt),
|
|
SCMP_CMP(1, SCMP_CMP_EQ, SOL_IP),
|
|
SCMP_CMP(2, SCMP_CMP_EQ, IP_TRANSPARENT));
|
|
if (rc)
|
|
return rc;
|
|
#endif /* defined(IP_TRANSPARENT) */
|
|
|
|
#ifdef IPV6_V6ONLY
|
|
rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(setsockopt),
|
|
SCMP_CMP(1, SCMP_CMP_EQ, IPPROTO_IPV6),
|
|
SCMP_CMP(2, SCMP_CMP_EQ, IPV6_V6ONLY));
|
|
if (rc)
|
|
return rc;
|
|
#endif /* defined(IPV6_V6ONLY) */
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Function responsible for setting up the getsockopt syscall for
|
|
* the seccomp filter sandbox.
|
|
*/
|
|
static int
|
|
sb_getsockopt(scmp_filter_ctx ctx, sandbox_cfg_t *filter)
|
|
{
|
|
int rc = 0;
|
|
(void) filter;
|
|
|
|
#ifdef __i386__
|
|
rc = seccomp_rule_add_0(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getsockopt));
|
|
if (rc)
|
|
return rc;
|
|
#endif
|
|
|
|
rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getsockopt),
|
|
SCMP_CMP(1, SCMP_CMP_EQ, SOL_SOCKET),
|
|
SCMP_CMP(2, SCMP_CMP_EQ, SO_ERROR));
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getsockopt),
|
|
SCMP_CMP(1, SCMP_CMP_EQ, SOL_SOCKET),
|
|
SCMP_CMP(2, SCMP_CMP_EQ, SO_ACCEPTCONN));
|
|
if (rc)
|
|
return rc;
|
|
|
|
#ifdef HAVE_SYSTEMD
|
|
rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getsockopt),
|
|
SCMP_CMP(1, SCMP_CMP_EQ, SOL_SOCKET),
|
|
SCMP_CMP(2, SCMP_CMP_EQ, SO_SNDBUF));
|
|
if (rc)
|
|
return rc;
|
|
#endif /* defined(HAVE_SYSTEMD) */
|
|
|
|
#ifdef HAVE_LINUX_NETFILTER_IPV4_H
|
|
rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getsockopt),
|
|
SCMP_CMP(1, SCMP_CMP_EQ, SOL_IP),
|
|
SCMP_CMP(2, SCMP_CMP_EQ, SO_ORIGINAL_DST));
|
|
if (rc)
|
|
return rc;
|
|
#endif /* defined(HAVE_LINUX_NETFILTER_IPV4_H) */
|
|
|
|
#ifdef HAVE_LINUX_NETFILTER_IPV6_IP6_TABLES_H
|
|
rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getsockopt),
|
|
SCMP_CMP(1, SCMP_CMP_EQ, SOL_IPV6),
|
|
SCMP_CMP(2, SCMP_CMP_EQ, IP6T_SO_ORIGINAL_DST));
|
|
if (rc)
|
|
return rc;
|
|
#endif /* defined(HAVE_LINUX_NETFILTER_IPV6_IP6_TABLES_H) */
|
|
|
|
#ifdef HAVE_KIST_SUPPORT
|
|
#include <netinet/tcp.h>
|
|
rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getsockopt),
|
|
SCMP_CMP(1, SCMP_CMP_EQ, SOL_TCP),
|
|
SCMP_CMP(2, SCMP_CMP_EQ, TCP_INFO));
|
|
if (rc)
|
|
return rc;
|
|
#endif /* defined(HAVE_KIST_SUPPORT) */
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef __NR_fcntl64
|
|
/**
|
|
* Function responsible for setting up the fcntl64 syscall for
|
|
* the seccomp filter sandbox.
|
|
*/
|
|
static int
|
|
sb_fcntl64(scmp_filter_ctx ctx, sandbox_cfg_t *filter)
|
|
{
|
|
int rc = 0;
|
|
(void) filter;
|
|
|
|
rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl64),
|
|
SCMP_CMP(1, SCMP_CMP_EQ, F_GETFL));
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl64),
|
|
SCMP_CMP(1, SCMP_CMP_EQ, F_SETFL),
|
|
SCMP_CMP(2, SCMP_CMP_EQ, O_RDWR|O_NONBLOCK));
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl64),
|
|
SCMP_CMP(1, SCMP_CMP_EQ, F_GETFD));
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl64),
|
|
SCMP_CMP(1, SCMP_CMP_EQ, F_SETFD),
|
|
SCMP_CMP(2, SCMP_CMP_EQ, FD_CLOEXEC));
|
|
if (rc)
|
|
return rc;
|
|
|
|
return 0;
|
|
}
|
|
#endif /* defined(__NR_fcntl64) */
|
|
|
|
/**
|
|
* Function responsible for setting up the epoll_ctl syscall for
|
|
* the seccomp filter sandbox.
|
|
*
|
|
* Note: basically allows everything but will keep for now..
|
|
*/
|
|
static int
|
|
sb_epoll_ctl(scmp_filter_ctx ctx, sandbox_cfg_t *filter)
|
|
{
|
|
int rc = 0;
|
|
(void) filter;
|
|
|
|
rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(epoll_ctl),
|
|
SCMP_CMP(1, SCMP_CMP_EQ, EPOLL_CTL_ADD));
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(epoll_ctl),
|
|
SCMP_CMP(1, SCMP_CMP_EQ, EPOLL_CTL_MOD));
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(epoll_ctl),
|
|
SCMP_CMP(1, SCMP_CMP_EQ, EPOLL_CTL_DEL));
|
|
if (rc)
|
|
return rc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Function responsible for setting up the prctl syscall for
|
|
* the seccomp filter sandbox.
|
|
*
|
|
* NOTE: if multiple filters need to be added, the PR_SECCOMP parameter needs
|
|
* to be allowlisted in this function.
|
|
*/
|
|
static int
|
|
sb_prctl(scmp_filter_ctx ctx, sandbox_cfg_t *filter)
|
|
{
|
|
int rc = 0;
|
|
(void) filter;
|
|
|
|
#ifdef ENABLE_FRAGILE_HARDENING
|
|
rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(prctl),
|
|
SCMP_CMP(0, SCMP_CMP_EQ, PR_GET_DUMPABLE));
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(prctl),
|
|
SCMP_CMP(0, SCMP_CMP_EQ, PR_SET_PTRACER));
|
|
if (rc)
|
|
return rc;
|
|
#endif
|
|
|
|
rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(prctl),
|
|
SCMP_CMP(0, SCMP_CMP_EQ, PR_SET_DUMPABLE));
|
|
if (rc)
|
|
return rc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Function responsible for setting up the mprotect syscall for
|
|
* the seccomp filter sandbox.
|
|
*
|
|
* NOTE: does not NEED to be here.. currently only occurs before filter; will
|
|
* keep just in case for the future.
|
|
*/
|
|
static int
|
|
sb_mprotect(scmp_filter_ctx ctx, sandbox_cfg_t *filter)
|
|
{
|
|
int rc = 0;
|
|
(void) filter;
|
|
|
|
rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mprotect),
|
|
SCMP_CMP(2, SCMP_CMP_EQ, PROT_READ));
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mprotect),
|
|
SCMP_CMP(2, SCMP_CMP_EQ, PROT_NONE));
|
|
if (rc)
|
|
return rc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Function responsible for setting up the rt_sigprocmask syscall for
|
|
* the seccomp filter sandbox.
|
|
*/
|
|
static int
|
|
sb_rt_sigprocmask(scmp_filter_ctx ctx, sandbox_cfg_t *filter)
|
|
{
|
|
int rc = 0;
|
|
(void) filter;
|
|
|
|
#ifdef ENABLE_FRAGILE_HARDENING
|
|
rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigprocmask),
|
|
SCMP_CMP(0, SCMP_CMP_EQ, SIG_BLOCK));
|
|
if (rc)
|
|
return rc;
|
|
#endif
|
|
|
|
rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigprocmask),
|
|
SCMP_CMP(0, SCMP_CMP_EQ, SIG_UNBLOCK));
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigprocmask),
|
|
SCMP_CMP(0, SCMP_CMP_EQ, SIG_SETMASK));
|
|
if (rc)
|
|
return rc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Function responsible for setting up the flock syscall for
|
|
* the seccomp filter sandbox.
|
|
*
|
|
* NOTE: does not need to be here, occurs before filter is applied.
|
|
*/
|
|
static int
|
|
sb_flock(scmp_filter_ctx ctx, sandbox_cfg_t *filter)
|
|
{
|
|
int rc = 0;
|
|
(void) filter;
|
|
|
|
rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(flock),
|
|
SCMP_CMP(1, SCMP_CMP_EQ, LOCK_EX|LOCK_NB));
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(flock),
|
|
SCMP_CMP(1, SCMP_CMP_EQ, LOCK_UN));
|
|
if (rc)
|
|
return rc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Function responsible for setting up the futex syscall for
|
|
* the seccomp filter sandbox.
|
|
*/
|
|
static int
|
|
sb_futex(scmp_filter_ctx ctx, sandbox_cfg_t *filter)
|
|
{
|
|
int rc = 0;
|
|
(void) filter;
|
|
|
|
// can remove
|
|
rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(futex),
|
|
SCMP_CMP(1, SCMP_CMP_EQ,
|
|
FUTEX_WAIT_BITSET_PRIVATE|FUTEX_CLOCK_REALTIME));
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(futex),
|
|
SCMP_CMP(1, SCMP_CMP_EQ, FUTEX_WAKE_PRIVATE));
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(futex),
|
|
SCMP_CMP(1, SCMP_CMP_EQ, FUTEX_WAIT_PRIVATE));
|
|
if (rc)
|
|
return rc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Function responsible for setting up the mremap syscall for
|
|
* the seccomp filter sandbox.
|
|
*
|
|
* NOTE: so far only occurs before filter is applied.
|
|
*/
|
|
static int
|
|
sb_mremap(scmp_filter_ctx ctx, sandbox_cfg_t *filter)
|
|
{
|
|
int rc = 0;
|
|
(void) filter;
|
|
|
|
rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mremap),
|
|
SCMP_CMP(3, SCMP_CMP_EQ, MREMAP_MAYMOVE));
|
|
if (rc)
|
|
return rc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef __NR_stat64
|
|
/**
|
|
* Function responsible for setting up the stat64 syscall for
|
|
* the seccomp filter sandbox.
|
|
*/
|
|
static int
|
|
sb_stat64(scmp_filter_ctx ctx, sandbox_cfg_t *filter)
|
|
{
|
|
int rc = 0;
|
|
sandbox_cfg_t *elem = NULL;
|
|
|
|
// for each dynamic parameter filters
|
|
for (elem = filter; elem != NULL; elem = elem->next) {
|
|
smp_param_t *param = elem->param;
|
|
|
|
if (param != NULL && param->prot == 1 && (param->syscall == SCMP_SYS(open)
|
|
|| param->syscall == SCMP_SYS(stat64))) {
|
|
rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(stat64),
|
|
SCMP_CMP_STR(0, SCMP_CMP_EQ, param->value));
|
|
if (rc != 0) {
|
|
log_err(LD_BUG,"(Sandbox) failed to add stat64 syscall, received "
|
|
"libseccomp error %d", rc);
|
|
return rc;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif /* defined(__NR_stat64) */
|
|
|
|
static int
|
|
sb_kill(scmp_filter_ctx ctx, sandbox_cfg_t *filter)
|
|
{
|
|
(void) filter;
|
|
#ifdef __NR_kill
|
|
/* Allow killing anything with signal 0 -- it isn't really a kill. */
|
|
return seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(kill),
|
|
SCMP_CMP(1, SCMP_CMP_EQ, 0));
|
|
#else
|
|
return 0;
|
|
#endif /* defined(__NR_kill) */
|
|
}
|
|
|
|
/**
|
|
* Array of function pointers responsible for filtering different syscalls at
|
|
* a parameter level.
|
|
*/
|
|
static sandbox_filter_func_t filter_func[] = {
|
|
sb_rt_sigaction,
|
|
sb_rt_sigprocmask,
|
|
#ifdef __NR_time
|
|
sb_time,
|
|
#endif
|
|
sb_accept4,
|
|
#ifdef __NR_mmap2
|
|
sb_mmap2,
|
|
#endif
|
|
#ifdef __i386__
|
|
sb_chown32,
|
|
#else
|
|
sb_chown,
|
|
#endif
|
|
sb_fchownat,
|
|
sb_chmod,
|
|
sb_fchmodat,
|
|
sb_open,
|
|
sb_openat,
|
|
sb_opendir,
|
|
#ifdef ENABLE_FRAGILE_HARDENING
|
|
sb_ptrace,
|
|
#endif
|
|
sb_rename,
|
|
sb_renameat,
|
|
#ifdef __NR_fcntl64
|
|
sb_fcntl64,
|
|
#endif
|
|
sb_epoll_ctl,
|
|
sb_prctl,
|
|
sb_mprotect,
|
|
sb_flock,
|
|
sb_futex,
|
|
sb_mremap,
|
|
#ifdef __NR_stat64
|
|
sb_stat64,
|
|
#endif
|
|
|
|
sb_socket,
|
|
sb_setsockopt,
|
|
sb_getsockopt,
|
|
sb_socketpair,
|
|
#ifdef HAVE_KIST_SUPPORT
|
|
sb_ioctl,
|
|
#endif
|
|
sb_kill
|
|
};
|
|
|
|
/**
|
|
* Return the interned (and hopefully sandbox-permitted) string equal
|
|
* to @a str.
|
|
*
|
|
* Return NULL if `str` is NULL, or `str` is not an interned string.
|
|
**/
|
|
const char *
|
|
sandbox_intern_string(const char *str)
|
|
{
|
|
const char *interned = sandbox_get_interned_string(str);
|
|
|
|
if (sandbox_active && str != NULL && interned == NULL) {
|
|
log_warn(LD_BUG, "No interned sandbox parameter found for %s", str);
|
|
}
|
|
|
|
return interned ? interned : str;
|
|
}
|
|
|
|
/**
|
|
* Return true if the sandbox is running and we are missing an interned string
|
|
* equal to @a str.
|
|
*/
|
|
bool
|
|
sandbox_interned_string_is_missing(const char *str)
|
|
{
|
|
return sandbox_active && sandbox_get_interned_string(str) == NULL;
|
|
}
|
|
|
|
/**
|
|
* Try to find and return the interned string equal to @a str.
|
|
*
|
|
* If there is no such string, return NULL.
|
|
**/
|
|
static const char *
|
|
sandbox_get_interned_string(const char *str)
|
|
{
|
|
sandbox_cfg_t *elem;
|
|
|
|
if (str == NULL)
|
|
return NULL;
|
|
|
|
for (elem = filter_dynamic; elem != NULL; elem = elem->next) {
|
|
smp_param_t *param = elem->param;
|
|
|
|
if (param->prot) {
|
|
if (!strcmp(str, (char*)(param->value))) {
|
|
return (char*)param->value;
|
|
}
|
|
if (param->value2 && !strcmp(str, (char*)param->value2)) {
|
|
return (char*)param->value2;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* DOCDOC */
|
|
static int
|
|
prot_strings_helper(strmap_t *locations,
|
|
char **pr_mem_next_p,
|
|
size_t *pr_mem_left_p,
|
|
char **value_p)
|
|
{
|
|
char *param_val;
|
|
size_t param_size;
|
|
void *location;
|
|
|
|
if (*value_p == 0)
|
|
return 0;
|
|
|
|
param_val = (char*) *value_p;
|
|
param_size = strlen(param_val) + 1;
|
|
location = strmap_get(locations, param_val);
|
|
|
|
if (location) {
|
|
// We already interned this string.
|
|
tor_free(param_val);
|
|
*value_p = location;
|
|
return 0;
|
|
} else if (*pr_mem_left_p >= param_size) {
|
|
// copy to protected
|
|
location = *pr_mem_next_p;
|
|
memcpy(location, param_val, param_size);
|
|
|
|
// re-point el parameter to protected
|
|
tor_free(param_val);
|
|
*value_p = location;
|
|
|
|
strmap_set(locations, location, location); /* good real estate advice */
|
|
|
|
// move next available protected memory
|
|
*pr_mem_next_p += param_size;
|
|
*pr_mem_left_p -= param_size;
|
|
return 0;
|
|
} else {
|
|
log_err(LD_BUG,"(Sandbox) insufficient protected memory!");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Protects all the strings in the sandbox's parameter list configuration. It
|
|
* works by calculating the total amount of memory required by the parameter
|
|
* list, allocating the memory using mmap, and protecting it from writes with
|
|
* mprotect().
|
|
*/
|
|
static int
|
|
prot_strings(scmp_filter_ctx ctx, sandbox_cfg_t* cfg)
|
|
{
|
|
int ret = 0;
|
|
size_t pr_mem_size = 0, pr_mem_left = 0;
|
|
char *pr_mem_next = NULL, *pr_mem_base;
|
|
sandbox_cfg_t *el = NULL;
|
|
strmap_t *locations = NULL;
|
|
|
|
// get total number of bytes required to mmap. (Overestimate.)
|
|
for (el = cfg; el != NULL; el = el->next) {
|
|
pr_mem_size += strlen((char*) el->param->value) + 1;
|
|
if (el->param->value2)
|
|
pr_mem_size += strlen((char*) el->param->value2) + 1;
|
|
}
|
|
|
|
// allocate protected memory with MALLOC_MP_LIM canary
|
|
pr_mem_base = (char*) mmap(NULL, MALLOC_MP_LIM + pr_mem_size,
|
|
PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
|
|
if (pr_mem_base == MAP_FAILED) {
|
|
log_err(LD_BUG,"(Sandbox) failed allocate protected memory! mmap: %s",
|
|
strerror(errno));
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
pr_mem_next = pr_mem_base + MALLOC_MP_LIM;
|
|
pr_mem_left = pr_mem_size;
|
|
|
|
locations = strmap_new();
|
|
|
|
// change el value pointer to protected
|
|
for (el = cfg; el != NULL; el = el->next) {
|
|
if (prot_strings_helper(locations, &pr_mem_next, &pr_mem_left,
|
|
&el->param->value) < 0) {
|
|
ret = -2;
|
|
goto out;
|
|
}
|
|
if (prot_strings_helper(locations, &pr_mem_next, &pr_mem_left,
|
|
&el->param->value2) < 0) {
|
|
ret = -2;
|
|
goto out;
|
|
}
|
|
el->param->prot = 1;
|
|
}
|
|
|
|
// protecting from writes
|
|
if (mprotect(pr_mem_base, MALLOC_MP_LIM + pr_mem_size, PROT_READ)) {
|
|
log_err(LD_BUG,"(Sandbox) failed to protect memory! mprotect: %s",
|
|
strerror(errno));
|
|
ret = -3;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Setting sandbox restrictions so the string memory cannot be tampered with
|
|
*/
|
|
// no mremap of the protected base address
|
|
ret = seccomp_rule_add_1(ctx, SCMP_ACT_KILL, SCMP_SYS(mremap),
|
|
SCMP_CMP(0, SCMP_CMP_EQ, (intptr_t) pr_mem_base));
|
|
if (ret) {
|
|
log_err(LD_BUG,"(Sandbox) mremap protected memory filter fail!");
|
|
goto out;
|
|
}
|
|
|
|
// no munmap of the protected base address
|
|
ret = seccomp_rule_add_1(ctx, SCMP_ACT_KILL, SCMP_SYS(munmap),
|
|
SCMP_CMP(0, SCMP_CMP_EQ, (intptr_t) pr_mem_base));
|
|
if (ret) {
|
|
log_err(LD_BUG,"(Sandbox) munmap protected memory filter fail!");
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Allow mprotect with PROT_READ|PROT_WRITE because openssl uses it, but
|
|
* never over the memory region used by the protected strings.
|
|
*
|
|
* PROT_READ|PROT_WRITE was originally fully allowed in sb_mprotect(), but
|
|
* had to be removed due to limitation of libseccomp regarding intervals.
|
|
*
|
|
* There is a restriction on how much you can mprotect with R|W up to the
|
|
* size of the canary.
|
|
*/
|
|
ret = seccomp_rule_add_3(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mprotect),
|
|
SCMP_CMP(0, SCMP_CMP_LT, (intptr_t) pr_mem_base),
|
|
SCMP_CMP(1, SCMP_CMP_LE, MALLOC_MP_LIM),
|
|
SCMP_CMP(2, SCMP_CMP_EQ, PROT_READ|PROT_WRITE));
|
|
if (ret) {
|
|
log_err(LD_BUG,"(Sandbox) mprotect protected memory filter fail (LT)!");
|
|
goto out;
|
|
}
|
|
|
|
ret = seccomp_rule_add_3(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mprotect),
|
|
SCMP_CMP(0, SCMP_CMP_GT, (intptr_t) pr_mem_base + pr_mem_size +
|
|
MALLOC_MP_LIM),
|
|
SCMP_CMP(1, SCMP_CMP_LE, MALLOC_MP_LIM),
|
|
SCMP_CMP(2, SCMP_CMP_EQ, PROT_READ|PROT_WRITE));
|
|
if (ret) {
|
|
log_err(LD_BUG,"(Sandbox) mprotect protected memory filter fail (GT)!");
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
strmap_free(locations, NULL);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Auxiliary function used in order to allocate a sandbox_cfg_t element and set
|
|
* its values according the parameter list. All elements are initialised
|
|
* with the 'prot' field set to false, as the pointer is not protected at this
|
|
* point.
|
|
*/
|
|
static sandbox_cfg_t*
|
|
new_element2(int syscall, char *value, char *value2)
|
|
{
|
|
smp_param_t *param = NULL;
|
|
|
|
sandbox_cfg_t *elem = tor_malloc_zero(sizeof(sandbox_cfg_t));
|
|
param = elem->param = tor_malloc_zero(sizeof(smp_param_t));
|
|
|
|
param->syscall = syscall;
|
|
param->value = value;
|
|
param->value2 = value2;
|
|
param->prot = 0;
|
|
|
|
return elem;
|
|
}
|
|
|
|
static sandbox_cfg_t*
|
|
new_element(int syscall, char *value)
|
|
{
|
|
return new_element2(syscall, value, NULL);
|
|
}
|
|
|
|
#ifdef __i386__
|
|
#define SCMP_chown SCMP_SYS(chown32)
|
|
#elif defined(__aarch64__) && defined(__LP64__)
|
|
#define SCMP_chown SCMP_SYS(fchownat)
|
|
#else
|
|
#define SCMP_chown SCMP_SYS(chown)
|
|
#endif
|
|
|
|
#if defined(__aarch64__) && defined(__LP64__)
|
|
#define SCMP_chmod SCMP_SYS(fchmodat)
|
|
#else
|
|
#define SCMP_chmod SCMP_SYS(chmod)
|
|
#endif
|
|
|
|
#if defined(__aarch64__) && defined(__LP64__)
|
|
#define SCMP_rename SCMP_SYS(renameat)
|
|
#else
|
|
#define SCMP_rename SCMP_SYS(rename)
|
|
#endif
|
|
|
|
#ifdef __NR_stat64
|
|
#define SCMP_stat SCMP_SYS(stat64)
|
|
#else
|
|
#define SCMP_stat SCMP_SYS(stat)
|
|
#endif
|
|
|
|
int
|
|
sandbox_cfg_allow_stat_filename(sandbox_cfg_t **cfg, char *file)
|
|
{
|
|
sandbox_cfg_t *elem = NULL;
|
|
|
|
elem = new_element(SCMP_stat, file);
|
|
|
|
elem->next = *cfg;
|
|
*cfg = elem;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
sandbox_cfg_allow_open_filename(sandbox_cfg_t **cfg, char *file)
|
|
{
|
|
sandbox_cfg_t *elem = NULL;
|
|
|
|
elem = new_element(SCMP_SYS(open), file);
|
|
|
|
elem->next = *cfg;
|
|
*cfg = elem;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
sandbox_cfg_allow_chmod_filename(sandbox_cfg_t **cfg, char *file)
|
|
{
|
|
sandbox_cfg_t *elem = NULL;
|
|
|
|
elem = new_element(SCMP_chmod, file);
|
|
|
|
elem->next = *cfg;
|
|
*cfg = elem;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
sandbox_cfg_allow_chown_filename(sandbox_cfg_t **cfg, char *file)
|
|
{
|
|
sandbox_cfg_t *elem = NULL;
|
|
|
|
elem = new_element(SCMP_chown, file);
|
|
|
|
elem->next = *cfg;
|
|
*cfg = elem;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
sandbox_cfg_allow_rename(sandbox_cfg_t **cfg, char *file1, char *file2)
|
|
{
|
|
sandbox_cfg_t *elem = NULL;
|
|
|
|
elem = new_element2(SCMP_rename, file1, file2);
|
|
|
|
elem->next = *cfg;
|
|
*cfg = elem;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
sandbox_cfg_allow_openat_filename(sandbox_cfg_t **cfg, char *file)
|
|
{
|
|
sandbox_cfg_t *elem = NULL;
|
|
|
|
elem = new_element(SCMP_SYS(openat), file);
|
|
|
|
elem->next = *cfg;
|
|
*cfg = elem;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
sandbox_cfg_allow_opendir_dirname(sandbox_cfg_t **cfg, char *dir)
|
|
{
|
|
sandbox_cfg_t *elem = NULL;
|
|
|
|
elem = new_element(PHONY_OPENDIR_SYSCALL, dir);
|
|
|
|
elem->next = *cfg;
|
|
*cfg = elem;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Function responsible for going through the parameter syscall filters and
|
|
* call each function pointer in the list.
|
|
*/
|
|
static int
|
|
add_param_filter(scmp_filter_ctx ctx, sandbox_cfg_t* cfg)
|
|
{
|
|
unsigned i;
|
|
int rc = 0;
|
|
|
|
// function pointer
|
|
for (i = 0; i < ARRAY_LENGTH(filter_func); i++) {
|
|
rc = filter_func[i](ctx, cfg);
|
|
if (rc) {
|
|
log_err(LD_BUG,"(Sandbox) failed to add syscall %d, received libseccomp "
|
|
"error %d", i, rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Function responsible of loading the libseccomp syscall filters which do not
|
|
* have parameter filtering.
|
|
*/
|
|
static int
|
|
add_noparam_filter(scmp_filter_ctx ctx)
|
|
{
|
|
unsigned i;
|
|
int rc = 0;
|
|
|
|
// add general filters
|
|
for (i = 0; i < ARRAY_LENGTH(filter_nopar_gen); i++) {
|
|
rc = seccomp_rule_add_0(ctx, SCMP_ACT_ALLOW, filter_nopar_gen[i]);
|
|
if (rc != 0) {
|
|
log_err(LD_BUG,"(Sandbox) failed to add syscall index %d (NR=%d), "
|
|
"received libseccomp error %d", i, filter_nopar_gen[i], rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
if (is_libc_at_least(2, 33)) {
|
|
#ifdef __NR_newfstatat
|
|
// Libc 2.33 uses this syscall to implement both fstat() and stat().
|
|
//
|
|
// The trouble is that to implement fstat(fd, &st), it calls:
|
|
// newfstatat(fs, "", &st, AT_EMPTY_PATH)
|
|
// We can't detect this usage in particular, because "" is a pointer
|
|
// we don't control. And we can't just look for AT_EMPTY_PATH, since
|
|
// AT_EMPTY_PATH only has effect when the path string is empty.
|
|
//
|
|
// So our only solution seems to be allowing all fstatat calls, which
|
|
// means that an attacker can stat() anything on the filesystem. That's
|
|
// not a great solution, but I can't find a better one.
|
|
rc = seccomp_rule_add_0(ctx, SCMP_ACT_ALLOW, SCMP_SYS(newfstatat));
|
|
if (rc != 0) {
|
|
log_err(LD_BUG,"(Sandbox) failed to add newfstatat() syscall; "
|
|
"received libseccomp error %d", rc);
|
|
return rc;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Function responsible for setting up and enabling a global syscall filter.
|
|
* The function is a prototype developed for stage 1 of sandboxing Tor.
|
|
* Returns 0 on success.
|
|
*/
|
|
static int
|
|
install_syscall_filter(sandbox_cfg_t* cfg)
|
|
{
|
|
int rc = 0;
|
|
scmp_filter_ctx ctx;
|
|
|
|
ctx = seccomp_init(SCMP_ACT_ERRNO(EPERM));
|
|
if (ctx == NULL) {
|
|
log_err(LD_BUG,"(Sandbox) failed to initialise libseccomp context");
|
|
rc = -1;
|
|
goto end;
|
|
}
|
|
|
|
// protecting sandbox parameter strings
|
|
if ((rc = prot_strings(ctx, cfg))) {
|
|
goto end;
|
|
}
|
|
|
|
// add parameter filters
|
|
if ((rc = add_param_filter(ctx, cfg))) {
|
|
log_err(LD_BUG, "(Sandbox) failed to add param filters!");
|
|
goto end;
|
|
}
|
|
|
|
// adding filters with no parameters
|
|
if ((rc = add_noparam_filter(ctx))) {
|
|
log_err(LD_BUG, "(Sandbox) failed to add param filters!");
|
|
goto end;
|
|
}
|
|
|
|
// loading the seccomp2 filter
|
|
if ((rc = seccomp_load(ctx))) {
|
|
log_err(LD_BUG, "(Sandbox) failed to load: %d (%s)! "
|
|
"Are you sure that your kernel has seccomp2 support? The "
|
|
"sandbox won't work without it.", rc,
|
|
strerror(-rc));
|
|
goto end;
|
|
}
|
|
|
|
// marking the sandbox as active
|
|
sandbox_active = 1;
|
|
|
|
end:
|
|
seccomp_release(ctx);
|
|
return (rc < 0 ? -rc : rc);
|
|
}
|
|
|
|
#ifdef SYSCALL_NAME_DEBUGGING
|
|
#include "lib/sandbox/linux_syscalls.inc"
|
|
|
|
/** Return a string containing the name of a given syscall (if we know it) */
|
|
static const char *
|
|
get_syscall_name(int syscall_num)
|
|
{
|
|
int i;
|
|
for (i = 0; SYSCALLS_BY_NUMBER[i].syscall_name; ++i) {
|
|
if (SYSCALLS_BY_NUMBER[i].syscall_num == syscall_num)
|
|
return SYSCALLS_BY_NUMBER[i].syscall_name;
|
|
}
|
|
|
|
{
|
|
static char syscall_name_buf[64];
|
|
format_dec_number_sigsafe(syscall_num,
|
|
syscall_name_buf, sizeof(syscall_name_buf));
|
|
return syscall_name_buf;
|
|
}
|
|
}
|
|
|
|
/** Return the syscall number from a ucontext_t that we got in a signal
|
|
* handler (if we know how to do that). */
|
|
static int
|
|
get_syscall_from_ucontext(const ucontext_t *ctx)
|
|
{
|
|
return (int) ctx->uc_mcontext.M_SYSCALL;
|
|
}
|
|
#else /* !defined(SYSCALL_NAME_DEBUGGING) */
|
|
static const char *
|
|
get_syscall_name(int syscall_num)
|
|
{
|
|
(void) syscall_num;
|
|
return "unknown";
|
|
}
|
|
static int
|
|
get_syscall_from_ucontext(const ucontext_t *ctx)
|
|
{
|
|
(void) ctx;
|
|
return -1;
|
|
}
|
|
#endif /* defined(SYSCALL_NAME_DEBUGGING) */
|
|
|
|
#ifdef USE_BACKTRACE
|
|
#define MAX_DEPTH 256
|
|
static void *syscall_cb_buf[MAX_DEPTH];
|
|
#endif
|
|
|
|
/**
|
|
* Function called when a SIGSYS is caught by the application. It notifies the
|
|
* user that an error has occurred and either terminates or allows the
|
|
* application to continue execution, based on the DEBUGGING_CLOSE symbol.
|
|
*/
|
|
static void
|
|
sigsys_debugging(int nr, siginfo_t *info, void *void_context)
|
|
{
|
|
ucontext_t *ctx = (ucontext_t *) (void_context);
|
|
const char *syscall_name;
|
|
#ifdef USE_BACKTRACE
|
|
size_t depth;
|
|
int n_fds, i;
|
|
const int *fds = NULL;
|
|
#endif
|
|
|
|
(void) nr;
|
|
|
|
if (info->si_code != SYS_SECCOMP)
|
|
return;
|
|
|
|
if (!ctx)
|
|
return;
|
|
|
|
int syscall = get_syscall_from_ucontext(ctx);
|
|
|
|
#ifdef USE_BACKTRACE
|
|
depth = backtrace(syscall_cb_buf, MAX_DEPTH);
|
|
/* Clean up the top stack frame so we get the real function
|
|
* name for the most recently failing function. */
|
|
clean_backtrace(syscall_cb_buf, depth, ctx);
|
|
#endif /* defined(USE_BACKTRACE) */
|
|
|
|
syscall_name = get_syscall_name(syscall);
|
|
|
|
tor_log_err_sigsafe("(Sandbox) Caught a bad syscall attempt (syscall ",
|
|
syscall_name,
|
|
")\n",
|
|
NULL);
|
|
|
|
#ifdef USE_BACKTRACE
|
|
n_fds = tor_log_get_sigsafe_err_fds(&fds);
|
|
for (i=0; i < n_fds; ++i)
|
|
backtrace_symbols_fd(syscall_cb_buf, (int)depth, fds[i]);
|
|
#endif
|
|
|
|
#if defined(DEBUGGING_CLOSE)
|
|
_exit(1); // exit ok: programming error has led to sandbox failure.
|
|
#endif // DEBUGGING_CLOSE
|
|
}
|
|
|
|
/**
|
|
* Function that adds a handler for SIGSYS, which is the signal thrown
|
|
* when the application is issuing a syscall which is not allowed. The
|
|
* main purpose of this function is to help with debugging by identifying
|
|
* filtered syscalls.
|
|
*/
|
|
static int
|
|
install_sigsys_debugging(void)
|
|
{
|
|
struct sigaction act;
|
|
sigset_t mask;
|
|
|
|
memset(&act, 0, sizeof(act));
|
|
sigemptyset(&mask);
|
|
sigaddset(&mask, SIGSYS);
|
|
|
|
act.sa_sigaction = &sigsys_debugging;
|
|
act.sa_flags = SA_SIGINFO;
|
|
if (sigaction(SIGSYS, &act, NULL) < 0) {
|
|
log_err(LD_BUG,"(Sandbox) Failed to register SIGSYS signal handler");
|
|
return -1;
|
|
}
|
|
|
|
if (sigprocmask(SIG_UNBLOCK, &mask, NULL)) {
|
|
log_err(LD_BUG,"(Sandbox) Failed call to sigprocmask()");
|
|
return -2;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Function responsible of registering the sandbox_cfg_t list of parameter
|
|
* syscall filters to the existing parameter list. This is used for incipient
|
|
* multiple-sandbox support.
|
|
*/
|
|
static int
|
|
register_cfg(sandbox_cfg_t* cfg)
|
|
{
|
|
sandbox_cfg_t *elem = NULL;
|
|
|
|
if (filter_dynamic == NULL) {
|
|
filter_dynamic = cfg;
|
|
return 0;
|
|
}
|
|
|
|
for (elem = filter_dynamic; elem->next != NULL; elem = elem->next)
|
|
;
|
|
|
|
elem->next = cfg;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif /* defined(USE_LIBSECCOMP) */
|
|
|
|
#ifdef USE_LIBSECCOMP
|
|
/**
|
|
* Initialises the syscall sandbox filter for any linux architecture, taking
|
|
* into account various available features for different linux flavours.
|
|
*/
|
|
static int
|
|
initialise_libseccomp_sandbox(sandbox_cfg_t* cfg)
|
|
{
|
|
/* Prevent glibc from trying to open /dev/tty on fatal error */
|
|
setenv("LIBC_FATAL_STDERR_", "1", 1);
|
|
|
|
if (install_sigsys_debugging())
|
|
return -1;
|
|
|
|
if (install_syscall_filter(cfg))
|
|
return -2;
|
|
|
|
if (register_cfg(cfg))
|
|
return -3;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
sandbox_is_active(void)
|
|
{
|
|
return sandbox_active != 0;
|
|
}
|
|
#endif /* defined(USE_LIBSECCOMP) */
|
|
|
|
sandbox_cfg_t*
|
|
sandbox_cfg_new(void)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
int
|
|
sandbox_init(sandbox_cfg_t *cfg)
|
|
{
|
|
#if defined(USE_LIBSECCOMP)
|
|
return initialise_libseccomp_sandbox(cfg);
|
|
|
|
#elif defined(__linux__)
|
|
(void)cfg;
|
|
log_warn(LD_GENERAL,
|
|
"This version of Tor was built without support for sandboxing. To "
|
|
"build with support for sandboxing on Linux, you must have "
|
|
"libseccomp and its necessary header files (e.g. seccomp.h).");
|
|
return 0;
|
|
|
|
#else
|
|
(void)cfg;
|
|
log_warn(LD_GENERAL,
|
|
"Currently, sandboxing is only implemented on Linux. The feature "
|
|
"is disabled on your platform.");
|
|
return 0;
|
|
#endif /* defined(USE_LIBSECCOMP) || ... */
|
|
}
|
|
|
|
#ifndef USE_LIBSECCOMP
|
|
int
|
|
sandbox_cfg_allow_open_filename(sandbox_cfg_t **cfg, char *file)
|
|
{
|
|
(void)cfg; (void)file;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
sandbox_cfg_allow_openat_filename(sandbox_cfg_t **cfg, char *file)
|
|
{
|
|
(void)cfg; (void)file;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
sandbox_cfg_allow_opendir_dirname(sandbox_cfg_t **cfg, char *dir)
|
|
{
|
|
(void)cfg; (void)dir;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
sandbox_cfg_allow_stat_filename(sandbox_cfg_t **cfg, char *file)
|
|
{
|
|
(void)cfg; (void)file;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
sandbox_cfg_allow_chown_filename(sandbox_cfg_t **cfg, char *file)
|
|
{
|
|
(void)cfg; (void)file;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
sandbox_cfg_allow_chmod_filename(sandbox_cfg_t **cfg, char *file)
|
|
{
|
|
(void)cfg; (void)file;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
sandbox_cfg_allow_rename(sandbox_cfg_t **cfg, char *file1, char *file2)
|
|
{
|
|
(void)cfg; (void)file1; (void)file2;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
sandbox_is_active(void)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
#endif /* !defined(USE_LIBSECCOMP) */
|