mirror of
https://gitlab.torproject.org/tpo/core/tor.git
synced 2024-11-27 22:03: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_routerkeys.c \
|
||||||
src/test/test_routerlist.c \
|
src/test/test_routerlist.c \
|
||||||
src/test/test_routerset.c \
|
src/test/test_routerset.c \
|
||||||
|
src/test/test_sandbox.c \
|
||||||
src/test/test_scheduler.c \
|
src/test/test_scheduler.c \
|
||||||
src/test/test_sendme.c \
|
src/test/test_sendme.c \
|
||||||
src/test/test_shared_random.c \
|
src/test/test_shared_random.c \
|
||||||
|
@ -52,6 +52,7 @@
|
|||||||
#include "core/crypto/onion_fast.h"
|
#include "core/crypto/onion_fast.h"
|
||||||
#include "core/crypto/onion_tap.h"
|
#include "core/crypto/onion_tap.h"
|
||||||
#include "core/or/policies.h"
|
#include "core/or/policies.h"
|
||||||
|
#include "lib/sandbox/sandbox.h"
|
||||||
#include "app/config/statefile.h"
|
#include "app/config/statefile.h"
|
||||||
#include "lib/crypt_ops/crypto_curve25519.h"
|
#include "lib/crypt_ops/crypto_curve25519.h"
|
||||||
#include "feature/nodelist/networkstatus.h"
|
#include "feature/nodelist/networkstatus.h"
|
||||||
@ -732,6 +733,9 @@ struct testgroup_t testgroups[] = {
|
|||||||
{ "routerkeys/", routerkeys_tests },
|
{ "routerkeys/", routerkeys_tests },
|
||||||
{ "routerlist/", routerlist_tests },
|
{ "routerlist/", routerlist_tests },
|
||||||
{ "routerset/" , routerset_tests },
|
{ "routerset/" , routerset_tests },
|
||||||
|
#ifdef USE_LIBSECCOMP
|
||||||
|
{ "sandbox/" , sandbox_tests },
|
||||||
|
#endif
|
||||||
{ "scheduler/", scheduler_tests },
|
{ "scheduler/", scheduler_tests },
|
||||||
{ "sendme/", sendme_tests },
|
{ "sendme/", sendme_tests },
|
||||||
{ "shared-random/", sr_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 routerkeys_tests[];
|
||||||
extern struct testcase_t routerlist_tests[];
|
extern struct testcase_t routerlist_tests[];
|
||||||
extern struct testcase_t routerset_tests[];
|
extern struct testcase_t routerset_tests[];
|
||||||
|
extern struct testcase_t sandbox_tests[];
|
||||||
extern struct testcase_t scheduler_tests[];
|
extern struct testcase_t scheduler_tests[];
|
||||||
extern struct testcase_t sendme_tests[];
|
extern struct testcase_t sendme_tests[];
|
||||||
extern struct testcase_t socks_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