mirror of
https://gitlab.torproject.org/tpo/core/tor.git
synced 2024-11-28 06:13:31 +01:00
test: Add sandbox unit tests
This commit is contained in:
parent
3bc3a10895
commit
1a10948260
2
changes/issue16803
Normal file
2
changes/issue16803
Normal file
@ -0,0 +1,2 @@
|
||||
o Testing:
|
||||
- Add unit tests for the Linux seccomp sandbox. Resolves issue 16803.
|
@ -231,6 +231,7 @@ src_test_test_SOURCES += \
|
||||
src/test/test_routerkeys.c \
|
||||
src/test/test_routerlist.c \
|
||||
src/test/test_routerset.c \
|
||||
src/test/test_sandbox.c \
|
||||
src/test/test_scheduler.c \
|
||||
src/test/test_sendme.c \
|
||||
src/test/test_shared_random.c \
|
||||
|
@ -52,6 +52,7 @@
|
||||
#include "core/crypto/onion_fast.h"
|
||||
#include "core/crypto/onion_tap.h"
|
||||
#include "core/or/policies.h"
|
||||
#include "lib/sandbox/sandbox.h"
|
||||
#include "app/config/statefile.h"
|
||||
#include "lib/crypt_ops/crypto_curve25519.h"
|
||||
#include "feature/nodelist/networkstatus.h"
|
||||
@ -732,6 +733,9 @@ struct testgroup_t testgroups[] = {
|
||||
{ "routerkeys/", routerkeys_tests },
|
||||
{ "routerlist/", routerlist_tests },
|
||||
{ "routerset/" , routerset_tests },
|
||||
#ifdef USE_LIBSECCOMP
|
||||
{ "sandbox/" , sandbox_tests },
|
||||
#endif
|
||||
{ "scheduler/", scheduler_tests },
|
||||
{ "sendme/", sendme_tests },
|
||||
{ "shared-random/", sr_tests },
|
||||
|
@ -184,6 +184,7 @@ extern struct testcase_t router_tests[];
|
||||
extern struct testcase_t routerkeys_tests[];
|
||||
extern struct testcase_t routerlist_tests[];
|
||||
extern struct testcase_t routerset_tests[];
|
||||
extern struct testcase_t sandbox_tests[];
|
||||
extern struct testcase_t scheduler_tests[];
|
||||
extern struct testcase_t sendme_tests[];
|
||||
extern struct testcase_t socks_tests[];
|
||||
|
348
src/test/test_sandbox.c
Normal file
348
src/test/test_sandbox.c
Normal file
@ -0,0 +1,348 @@
|
||||
/* Copyright (c) 2021, The Tor Project, Inc. */
|
||||
/* See LICENSE for licensing information */
|
||||
|
||||
#ifndef _LARGEFILE64_SOURCE
|
||||
/**
|
||||
* Temporarily required for O_LARGEFILE flag. Needs to be removed
|
||||
* with the libevent fix.
|
||||
*/
|
||||
#define _LARGEFILE64_SOURCE
|
||||
#endif /* !defined(_LARGEFILE64_SOURCE) */
|
||||
|
||||
#include "orconfig.h"
|
||||
|
||||
#include "lib/sandbox/sandbox.h"
|
||||
|
||||
#ifdef USE_LIBSECCOMP
|
||||
|
||||
#include <dirent.h>
|
||||
#ifdef HAVE_FCNTL_H
|
||||
#include <fcntl.h>
|
||||
#endif
|
||||
#ifdef HAVE_SYS_STAT_H
|
||||
#include <sys/stat.h>
|
||||
#endif
|
||||
#ifdef HAVE_UNISTD_H
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "core/or/or.h"
|
||||
|
||||
#include "test/test.h"
|
||||
#include "test/log_test_helpers.h"
|
||||
|
||||
typedef struct {
|
||||
sandbox_cfg_t *cfg;
|
||||
|
||||
char *file_ops_allowed;
|
||||
char *file_ops_blocked;
|
||||
|
||||
char *file_rename_target_allowed;
|
||||
|
||||
char *dir_ops_allowed;
|
||||
char *dir_ops_blocked;
|
||||
} sandbox_data_t;
|
||||
|
||||
/* All tests are skipped when coverage support is enabled (see further below)
|
||||
* as the sandbox interferes with the use of gcov. Prevent a compiler warning
|
||||
* by omitting these definitions in that case. */
|
||||
#ifndef ENABLE_COVERAGE
|
||||
static void *
|
||||
setup_sandbox(const struct testcase_t *testcase)
|
||||
{
|
||||
sandbox_data_t *data = tor_malloc_zero(sizeof(*data));
|
||||
|
||||
(void)testcase;
|
||||
|
||||
/* Establish common file and directory names within the test suite's
|
||||
* temporary directory. */
|
||||
data->file_ops_allowed = tor_strdup(get_fname("file_ops_allowed"));
|
||||
data->file_ops_blocked = tor_strdup(get_fname("file_ops_blocked"));
|
||||
|
||||
data->file_rename_target_allowed =
|
||||
tor_strdup(get_fname("file_rename_target_allowed"));
|
||||
|
||||
data->dir_ops_allowed = tor_strdup(get_fname("dir_ops_allowed"));
|
||||
data->dir_ops_blocked = tor_strdup(get_fname("dir_ops_blocked"));
|
||||
|
||||
/* Create the corresponding filesystem objects. */
|
||||
creat(data->file_ops_allowed, S_IRWXU);
|
||||
creat(data->file_ops_blocked, S_IRWXU);
|
||||
mkdir(data->dir_ops_allowed, S_IRWXU);
|
||||
mkdir(data->dir_ops_blocked, S_IRWXU);
|
||||
|
||||
/* Create the sandbox configuration. */
|
||||
data->cfg = sandbox_cfg_new();
|
||||
|
||||
sandbox_cfg_allow_open_filename(&data->cfg,
|
||||
tor_strdup(data->file_ops_allowed));
|
||||
sandbox_cfg_allow_open_filename(&data->cfg,
|
||||
tor_strdup(data->dir_ops_allowed));
|
||||
|
||||
sandbox_cfg_allow_chmod_filename(&data->cfg,
|
||||
tor_strdup(data->file_ops_allowed));
|
||||
sandbox_cfg_allow_chmod_filename(&data->cfg,
|
||||
tor_strdup(data->dir_ops_allowed));
|
||||
sandbox_cfg_allow_chown_filename(&data->cfg,
|
||||
tor_strdup(data->file_ops_allowed));
|
||||
sandbox_cfg_allow_chown_filename(&data->cfg,
|
||||
tor_strdup(data->dir_ops_allowed));
|
||||
|
||||
sandbox_cfg_allow_rename(&data->cfg, tor_strdup(data->file_ops_allowed),
|
||||
tor_strdup(data->file_rename_target_allowed));
|
||||
|
||||
sandbox_cfg_allow_openat_filename(&data->cfg,
|
||||
tor_strdup(data->dir_ops_allowed));
|
||||
|
||||
sandbox_cfg_allow_opendir_dirname(&data->cfg,
|
||||
tor_strdup(data->dir_ops_allowed));
|
||||
|
||||
sandbox_cfg_allow_stat_filename(&data->cfg,
|
||||
tor_strdup(data->file_ops_allowed));
|
||||
sandbox_cfg_allow_stat_filename(&data->cfg,
|
||||
tor_strdup(data->dir_ops_allowed));
|
||||
|
||||
/* Activate the sandbox, which will remain in effect until the process
|
||||
* terminates. */
|
||||
sandbox_init(data->cfg);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static int
|
||||
cleanup_sandbox(const struct testcase_t *testcase, void *data_)
|
||||
{
|
||||
sandbox_data_t *data = data_;
|
||||
|
||||
(void)testcase;
|
||||
|
||||
tor_free(data->dir_ops_blocked);
|
||||
tor_free(data->dir_ops_allowed);
|
||||
tor_free(data->file_rename_target_allowed);
|
||||
tor_free(data->file_ops_blocked);
|
||||
tor_free(data->file_ops_allowed);
|
||||
|
||||
tor_free(data);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const struct testcase_setup_t sandboxed_testcase_setup = {
|
||||
.setup_fn = setup_sandbox,
|
||||
.cleanup_fn = cleanup_sandbox
|
||||
};
|
||||
#endif /* !defined(ENABLE_COVERAGE) */
|
||||
|
||||
static void
|
||||
test_sandbox_is_active(void *ignored)
|
||||
{
|
||||
(void)ignored;
|
||||
|
||||
tt_assert(!sandbox_is_active());
|
||||
|
||||
sandbox_init(sandbox_cfg_new());
|
||||
tt_assert(sandbox_is_active());
|
||||
|
||||
done:
|
||||
(void)0;
|
||||
}
|
||||
|
||||
static void
|
||||
test_sandbox_open_filename(void *arg)
|
||||
{
|
||||
sandbox_data_t *data = arg;
|
||||
int fd, errsv;
|
||||
|
||||
fd = open(sandbox_intern_string(data->file_ops_allowed), O_RDONLY);
|
||||
if (fd == -1)
|
||||
tt_abort_perror("open");
|
||||
close(fd);
|
||||
|
||||
/* It might be nice to use sandbox_intern_string() in the line below as well
|
||||
* (and likewise in the test cases that follow) but this would require
|
||||
* capturing the warning message it logs, and the mechanism for doing so
|
||||
* relies on system calls that are normally blocked by the sandbox and may
|
||||
* vary across architectures. */
|
||||
fd = open(data->file_ops_blocked, O_RDONLY);
|
||||
errsv = errno;
|
||||
tt_int_op(fd, OP_EQ, -1);
|
||||
tt_int_op(errsv, OP_EQ, EPERM);
|
||||
|
||||
done:
|
||||
if (fd >= 0)
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static void
|
||||
test_sandbox_chmod_filename(void *arg)
|
||||
{
|
||||
sandbox_data_t *data = arg;
|
||||
int rc, errsv;
|
||||
|
||||
if (chmod(sandbox_intern_string(data->file_ops_allowed),
|
||||
S_IRUSR | S_IWUSR) != 0)
|
||||
tt_abort_perror("chmod");
|
||||
|
||||
rc = chmod(data->file_ops_blocked, S_IRUSR | S_IWUSR);
|
||||
errsv = errno;
|
||||
tt_int_op(rc, OP_EQ, -1);
|
||||
tt_int_op(errsv, OP_EQ, EPERM);
|
||||
|
||||
done:
|
||||
(void)0;
|
||||
}
|
||||
|
||||
static void
|
||||
test_sandbox_chown_filename(void *arg)
|
||||
{
|
||||
sandbox_data_t *data = arg;
|
||||
int rc, errsv;
|
||||
|
||||
if (chown(sandbox_intern_string(data->file_ops_allowed), -1, -1) != 0)
|
||||
tt_abort_perror("chown");
|
||||
|
||||
rc = chown(data->file_ops_blocked, -1, -1);
|
||||
errsv = errno;
|
||||
tt_int_op(rc, OP_EQ, -1);
|
||||
tt_int_op(errsv, OP_EQ, EPERM);
|
||||
|
||||
done:
|
||||
(void)0;
|
||||
}
|
||||
|
||||
static void
|
||||
test_sandbox_rename_filename(void *arg)
|
||||
{
|
||||
sandbox_data_t *data = arg;
|
||||
const char *fname_old = sandbox_intern_string(data->file_ops_allowed),
|
||||
*fname_new = sandbox_intern_string(data->file_rename_target_allowed);
|
||||
int rc, errsv;
|
||||
|
||||
if (rename(fname_old, fname_new) != 0)
|
||||
tt_abort_perror("rename");
|
||||
|
||||
rc = rename(fname_new, fname_old);
|
||||
errsv = errno;
|
||||
tt_int_op(rc, OP_EQ, -1);
|
||||
tt_int_op(errsv, OP_EQ, EPERM);
|
||||
|
||||
done:
|
||||
(void)0;
|
||||
}
|
||||
|
||||
static void
|
||||
test_sandbox_openat_filename(void *arg)
|
||||
{
|
||||
sandbox_data_t *data = arg;
|
||||
int flags = O_RDONLY | O_NONBLOCK | O_LARGEFILE | O_DIRECTORY | O_CLOEXEC;
|
||||
int fd, errsv;
|
||||
|
||||
fd = openat(AT_FDCWD, sandbox_intern_string(data->dir_ops_allowed), flags);
|
||||
if (fd < 0)
|
||||
tt_abort_perror("openat");
|
||||
close(fd);
|
||||
|
||||
fd = openat(AT_FDCWD, data->dir_ops_blocked, flags);
|
||||
errsv = errno;
|
||||
tt_int_op(fd, OP_EQ, -1);
|
||||
tt_int_op(errsv, OP_EQ, EPERM);
|
||||
|
||||
done:
|
||||
if (fd >= 0)
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static void
|
||||
test_sandbox_opendir_dirname(void *arg)
|
||||
{
|
||||
sandbox_data_t *data = arg;
|
||||
DIR *dir;
|
||||
int errsv;
|
||||
|
||||
dir = opendir(sandbox_intern_string(data->dir_ops_allowed));
|
||||
if (dir == NULL)
|
||||
tt_abort_perror("opendir");
|
||||
closedir(dir);
|
||||
|
||||
dir = opendir(data->dir_ops_blocked);
|
||||
errsv = errno;
|
||||
tt_ptr_op(dir, OP_EQ, NULL);
|
||||
tt_int_op(errsv, OP_EQ, EPERM);
|
||||
|
||||
done:
|
||||
if (dir)
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
static void
|
||||
test_sandbox_stat_filename(void *arg)
|
||||
{
|
||||
sandbox_data_t *data = arg;
|
||||
struct stat st;
|
||||
|
||||
if (stat(sandbox_intern_string(data->file_ops_allowed), &st) != 0)
|
||||
tt_abort_perror("stat");
|
||||
|
||||
int rc = stat(data->file_ops_blocked, &st);
|
||||
int errsv = errno;
|
||||
tt_int_op(rc, OP_EQ, -1);
|
||||
tt_int_op(errsv, OP_EQ, EPERM);
|
||||
|
||||
done:
|
||||
(void)0;
|
||||
}
|
||||
|
||||
#define SANDBOX_TEST_SKIPPED(name) \
|
||||
{ #name, test_sandbox_ ## name, TT_SKIP, NULL, NULL }
|
||||
|
||||
/* Skip all tests when coverage support is enabled, as the sandbox interferes
|
||||
* with gcov and prevents it from producing any results. */
|
||||
#ifdef ENABLE_COVERAGE
|
||||
#define SANDBOX_TEST(name, flags) SANDBOX_TEST_SKIPPED(name)
|
||||
#define SANDBOX_TEST_IN_SANDBOX(name) SANDBOX_TEST_SKIPPED(name)
|
||||
#else
|
||||
#define SANDBOX_TEST(name, flags) \
|
||||
{ #name, test_sandbox_ ## name, flags, NULL, NULL }
|
||||
#define SANDBOX_TEST_IN_SANDBOX(name) \
|
||||
{ #name, test_sandbox_ ## name, TT_FORK, &sandboxed_testcase_setup, NULL }
|
||||
#endif /* defined(ENABLE_COVERAGE) */
|
||||
|
||||
struct testcase_t sandbox_tests[] = {
|
||||
SANDBOX_TEST(is_active, TT_FORK),
|
||||
|
||||
/* When Tor is built with fragile compiler-hardening the sandbox is unable to
|
||||
* filter requests to open files or directories (on systems where glibc uses
|
||||
* the "open" system call to provide this functionality), as doing so would
|
||||
* interfere with the address sanitizer as it retrieves information about the
|
||||
* running process via the filesystem. Skip these tests in that case as the
|
||||
* corresponding functions are likely to have no effect and this will cause the
|
||||
* tests to fail. */
|
||||
#ifdef ENABLE_FRAGILE_HARDENING
|
||||
SANDBOX_TEST_SKIPPED(open_filename),
|
||||
SANDBOX_TEST_SKIPPED(opendir_dirname),
|
||||
#else
|
||||
SANDBOX_TEST_IN_SANDBOX(open_filename),
|
||||
SANDBOX_TEST_IN_SANDBOX(opendir_dirname),
|
||||
#endif /* defined(ENABLE_FRAGILE_HARDENING) */
|
||||
|
||||
SANDBOX_TEST_IN_SANDBOX(openat_filename),
|
||||
SANDBOX_TEST_IN_SANDBOX(chmod_filename),
|
||||
SANDBOX_TEST_IN_SANDBOX(chown_filename),
|
||||
SANDBOX_TEST_IN_SANDBOX(rename_filename),
|
||||
|
||||
/* Currently the sandbox is unable to filter stat() calls on systems where
|
||||
* glibc implements this function using the legacy "stat" system call, or where
|
||||
* glibc version 2.33 or later is in use and the newer "newfstatat" syscall is
|
||||
* available.
|
||||
*
|
||||
* Skip testing sandbox_cfg_allow_stat_filename() if it seems the likely the
|
||||
* function will have no effect and the test will therefore not succeed. */
|
||||
#if !defined(__NR_newfstatat) && (!defined(__NR_stat) || defined(__NR_stat64))
|
||||
SANDBOX_TEST_IN_SANDBOX(stat_filename),
|
||||
#else
|
||||
SANDBOX_TEST_SKIPPED(stat_filename),
|
||||
#endif
|
||||
END_OF_TESTCASES
|
||||
};
|
||||
|
||||
#endif /* defined(USE_SECCOMP) */
|
Loading…
Reference in New Issue
Block a user