tor

The Tor anonymity network
git clone https://git.dasho.dev/tor.git
Log | Files | Refs | README | LICENSE

test_sandbox.c (12318B)


      1 /* Copyright (c) 2021, The Tor Project, Inc. */
      2 /* See LICENSE for licensing information */
      3 
      4 #ifndef _LARGEFILE64_SOURCE
      5 /**
      6 * Temporarily required for O_LARGEFILE flag. Needs to be removed
      7 * with the libevent fix.
      8 */
      9 #define _LARGEFILE64_SOURCE
     10 #endif /* !defined(_LARGEFILE64_SOURCE) */
     11 
     12 #include "orconfig.h"
     13 
     14 #include "lib/sandbox/sandbox.h"
     15 #include "lib/crypt_ops/crypto_rand.h"
     16 #include "ext/equix/include/equix.h"
     17 
     18 #ifdef USE_LIBSECCOMP
     19 
     20 #include <dirent.h>
     21 #ifdef HAVE_FCNTL_H
     22 #include <fcntl.h>
     23 #endif
     24 #ifdef HAVE_SYS_STAT_H
     25 #include <sys/stat.h>
     26 #endif
     27 #ifdef HAVE_UNISTD_H
     28 #include <unistd.h>
     29 #endif
     30 
     31 #include "core/or/or.h"
     32 
     33 #include "test/test.h"
     34 #include "test/log_test_helpers.h"
     35 
     36 typedef struct {
     37  sandbox_cfg_t *cfg;
     38 
     39  char *file_ops_allowed;
     40  char *file_ops_blocked;
     41 
     42  char *file_rename_target_allowed;
     43 
     44  char *dir_ops_allowed;
     45  char *dir_ops_blocked;
     46 } sandbox_data_t;
     47 
     48 /* All tests are skipped when coverage support is enabled (see further below)
     49 * as the sandbox interferes with the use of gcov.  Prevent a compiler warning
     50 * by omitting these definitions in that case. */
     51 #ifndef ENABLE_COVERAGE
     52 static void *
     53 setup_sandbox(const struct testcase_t *testcase)
     54 {
     55  sandbox_data_t *data = tor_malloc_zero(sizeof(*data));
     56 
     57  (void)testcase;
     58 
     59  /* Establish common file and directory names within the test suite's
     60   * temporary directory. */
     61  data->file_ops_allowed = tor_strdup(get_fname("file_ops_allowed"));
     62  data->file_ops_blocked = tor_strdup(get_fname("file_ops_blocked"));
     63 
     64  data->file_rename_target_allowed =
     65    tor_strdup(get_fname("file_rename_target_allowed"));
     66 
     67  data->dir_ops_allowed = tor_strdup(get_fname("dir_ops_allowed"));
     68  data->dir_ops_blocked = tor_strdup(get_fname("dir_ops_blocked"));
     69 
     70  /* Create the corresponding filesystem objects. */
     71  creat(data->file_ops_allowed, S_IRWXU);
     72  creat(data->file_ops_blocked, S_IRWXU);
     73  mkdir(data->dir_ops_allowed, S_IRWXU);
     74  mkdir(data->dir_ops_blocked, S_IRWXU);
     75 
     76  /* Create the sandbox configuration. */
     77  data->cfg = sandbox_cfg_new();
     78 
     79  sandbox_cfg_allow_open_filename(&data->cfg,
     80                                  tor_strdup(data->file_ops_allowed));
     81  sandbox_cfg_allow_open_filename(&data->cfg,
     82                                  tor_strdup(data->dir_ops_allowed));
     83 
     84  sandbox_cfg_allow_chmod_filename(&data->cfg,
     85                                   tor_strdup(data->file_ops_allowed));
     86  sandbox_cfg_allow_chmod_filename(&data->cfg,
     87                                   tor_strdup(data->dir_ops_allowed));
     88  sandbox_cfg_allow_chown_filename(&data->cfg,
     89                                   tor_strdup(data->file_ops_allowed));
     90  sandbox_cfg_allow_chown_filename(&data->cfg,
     91                                   tor_strdup(data->dir_ops_allowed));
     92 
     93  sandbox_cfg_allow_rename(&data->cfg, tor_strdup(data->file_ops_allowed),
     94                           tor_strdup(data->file_rename_target_allowed));
     95 
     96  sandbox_cfg_allow_openat_filename(&data->cfg,
     97                                    tor_strdup(data->dir_ops_allowed));
     98 
     99  sandbox_cfg_allow_opendir_dirname(&data->cfg,
    100                                    tor_strdup(data->dir_ops_allowed));
    101 
    102  sandbox_cfg_allow_stat_filename(&data->cfg,
    103                                  tor_strdup(data->file_ops_allowed));
    104  sandbox_cfg_allow_stat_filename(&data->cfg,
    105                                  tor_strdup(data->dir_ops_allowed));
    106 
    107  /* Activate the sandbox, which will remain in effect until the process
    108   * terminates. */
    109  sandbox_init(data->cfg);
    110 
    111  return data;
    112 }
    113 
    114 static int
    115 cleanup_sandbox(const struct testcase_t *testcase, void *data_)
    116 {
    117  sandbox_data_t *data = data_;
    118 
    119  (void)testcase;
    120 
    121  tor_free(data->dir_ops_blocked);
    122  tor_free(data->dir_ops_allowed);
    123  tor_free(data->file_rename_target_allowed);
    124  tor_free(data->file_ops_blocked);
    125  tor_free(data->file_ops_allowed);
    126 
    127  tor_free(data);
    128 
    129  return 1;
    130 }
    131 
    132 static const struct testcase_setup_t sandboxed_testcase_setup = {
    133  .setup_fn = setup_sandbox,
    134  .cleanup_fn = cleanup_sandbox
    135 };
    136 #endif /* !defined(ENABLE_COVERAGE) */
    137 
    138 static void
    139 test_sandbox_is_active(void *ignored)
    140 {
    141  (void)ignored;
    142 
    143  tt_assert(!sandbox_is_active());
    144 
    145  sandbox_init(sandbox_cfg_new());
    146  tt_assert(sandbox_is_active());
    147 
    148 done:
    149  (void)0;
    150 }
    151 
    152 static void
    153 test_sandbox_open_filename(void *arg)
    154 {
    155  sandbox_data_t *data = arg;
    156  int fd, errsv;
    157 
    158  fd = open(sandbox_intern_string(data->file_ops_allowed), O_RDONLY);
    159  if (fd == -1)
    160    tt_abort_perror("open");
    161  close(fd);
    162 
    163  /* It might be nice to use sandbox_intern_string() in the line below as well
    164   * (and likewise in the test cases that follow) but this would require
    165   * capturing the warning message it logs, and the mechanism for doing so
    166   * relies on system calls that are normally blocked by the sandbox and may
    167   * vary across architectures. */
    168  fd = open(data->file_ops_blocked, O_RDONLY);
    169  errsv = errno;
    170  tt_int_op(fd, OP_EQ, -1);
    171  tt_int_op(errsv, OP_EQ, EPERM);
    172 
    173 done:
    174  if (fd >= 0)
    175    close(fd);
    176 }
    177 
    178 static void
    179 test_sandbox_chmod_filename(void *arg)
    180 {
    181  sandbox_data_t *data = arg;
    182  int rc, errsv;
    183 
    184  if (chmod(sandbox_intern_string(data->file_ops_allowed),
    185            S_IRUSR | S_IWUSR) != 0)
    186    tt_abort_perror("chmod");
    187 
    188  rc = chmod(data->file_ops_blocked, S_IRUSR | S_IWUSR);
    189  errsv = errno;
    190  tt_int_op(rc, OP_EQ, -1);
    191  tt_int_op(errsv, OP_EQ, EPERM);
    192 
    193 done:
    194  (void)0;
    195 }
    196 
    197 static void
    198 test_sandbox_chown_filename(void *arg)
    199 {
    200  sandbox_data_t *data = arg;
    201  int rc, errsv;
    202 
    203  if (chown(sandbox_intern_string(data->file_ops_allowed), -1, -1) != 0)
    204    tt_abort_perror("chown");
    205 
    206  rc = chown(data->file_ops_blocked, -1, -1);
    207  errsv = errno;
    208  tt_int_op(rc, OP_EQ, -1);
    209  tt_int_op(errsv, OP_EQ, EPERM);
    210 
    211 done:
    212  (void)0;
    213 }
    214 
    215 static void
    216 test_sandbox_rename_filename(void *arg)
    217 {
    218  sandbox_data_t *data = arg;
    219  const char *fname_old = sandbox_intern_string(data->file_ops_allowed),
    220    *fname_new = sandbox_intern_string(data->file_rename_target_allowed);
    221  int rc, errsv;
    222 
    223  if (rename(fname_old, fname_new) != 0)
    224    tt_abort_perror("rename");
    225 
    226  rc = rename(fname_new, fname_old);
    227  errsv = errno;
    228  tt_int_op(rc, OP_EQ, -1);
    229  tt_int_op(errsv, OP_EQ, EPERM);
    230 
    231 done:
    232  (void)0;
    233 }
    234 
    235 static void
    236 test_sandbox_openat_filename(void *arg)
    237 {
    238  sandbox_data_t *data = arg;
    239  int flags = O_RDONLY | O_NONBLOCK | O_LARGEFILE | O_DIRECTORY | O_CLOEXEC;
    240  int fd, errsv;
    241 
    242  fd = openat(AT_FDCWD, sandbox_intern_string(data->dir_ops_allowed), flags);
    243  if (fd < 0)
    244    tt_abort_perror("openat");
    245  close(fd);
    246 
    247  fd = openat(AT_FDCWD, data->dir_ops_blocked, flags);
    248  errsv = errno;
    249  tt_int_op(fd, OP_EQ, -1);
    250  tt_int_op(errsv, OP_EQ, EPERM);
    251 
    252 done:
    253  if (fd >= 0)
    254    close(fd);
    255 }
    256 
    257 static void
    258 test_sandbox_opendir_dirname(void *arg)
    259 {
    260  sandbox_data_t *data = arg;
    261  DIR *dir;
    262  int errsv;
    263 
    264  dir = opendir(sandbox_intern_string(data->dir_ops_allowed));
    265  if (dir == NULL)
    266    tt_abort_perror("opendir");
    267  closedir(dir);
    268 
    269  dir = opendir(data->dir_ops_blocked);
    270  errsv = errno;
    271  tt_ptr_op(dir, OP_EQ, NULL);
    272  tt_int_op(errsv, OP_EQ, EPERM);
    273 
    274 done:
    275  if (dir)
    276    closedir(dir);
    277 }
    278 
    279 static void
    280 test_sandbox_stat_filename(void *arg)
    281 {
    282  sandbox_data_t *data = arg;
    283  struct stat st;
    284 
    285  if (stat(sandbox_intern_string(data->file_ops_allowed), &st) != 0)
    286    tt_abort_perror("stat");
    287 
    288  int rc = stat(data->file_ops_blocked, &st);
    289  int errsv = errno;
    290  tt_int_op(rc, OP_EQ, -1);
    291  tt_int_op(errsv, OP_EQ, EPERM);
    292 
    293 done:
    294  (void)0;
    295 }
    296 
    297 /** This is a simplified subset of test_crypto_equix(), running one solve
    298 * and one verify from inside the sandbox. The sandbox restricts mprotect, and
    299 * hashx will experience a failure at runtime which this test case exercises.
    300 * The result of the solve and verify should both still be correct, since we
    301 * expect it to cleanly fall back on an interpreted implementation which has
    302 * no operating system dependencies. */
    303 static void
    304 test_sandbox_crypto_equix(void *arg)
    305 {
    306  (void)arg;
    307 
    308  const char *challenge_literal = "abce";
    309  const size_t challenge_len = strlen(challenge_literal);
    310  const size_t num_sols = 4;
    311  static const equix_solution sols_expected[EQUIX_MAX_SOLS] = {
    312    {{ 0x4fca, 0x72eb, 0x101f, 0xafab, 0x1add, 0x2d71, 0x75a3, 0xc978 }},
    313    {{ 0x17f1, 0x7aa6, 0x23e3, 0xab00, 0x7e2f, 0x917e, 0x16da, 0xda9e }},
    314    {{ 0x70ee, 0x7757, 0x8a54, 0xbd2b, 0x90e4, 0xe31e, 0x2085, 0xe47e }},
    315    {{ 0x62c5, 0x86d1, 0x5752, 0xe1f0, 0x12da, 0x8f33, 0x7336, 0xf161 }},
    316  };
    317 
    318  equix_solutions_buffer output;
    319  equix_ctx *solve_ctx = NULL, *verify_ctx = NULL;
    320 
    321  solve_ctx = equix_alloc(EQUIX_CTX_SOLVE | EQUIX_CTX_TRY_COMPILE);
    322  tt_ptr_op(solve_ctx, OP_NE, NULL);
    323 
    324  equix_result result;
    325  memset(&output, 0xEE, sizeof output);
    326  result = equix_solve(solve_ctx, challenge_literal, challenge_len, &output);
    327  tt_int_op(result, OP_EQ, EQUIX_OK);
    328  tt_int_op(output.count, OP_EQ, num_sols);
    329  tt_int_op(output.flags, OP_EQ, 0); /* EQUIX_SOLVER_DID_USE_COMPILER unset */
    330  tt_mem_op(output.sols, OP_EQ, sols_expected,
    331            num_sols * sizeof(equix_solution));
    332 
    333  verify_ctx = equix_alloc(EQUIX_CTX_VERIFY | EQUIX_CTX_TRY_COMPILE);
    334  tt_ptr_op(verify_ctx, OP_NE, NULL);
    335 
    336  /* Test one of the solutions randomly */
    337  const unsigned sol_i = crypto_rand_int(num_sols);
    338  equix_solution *sol = &output.sols[sol_i];
    339 
    340  result = equix_verify(verify_ctx, challenge_literal,
    341                        challenge_len, sol);
    342  tt_int_op(EQUIX_OK, OP_EQ, result);
    343 
    344 done:
    345  equix_free(solve_ctx);
    346  equix_free(verify_ctx);
    347 }
    348 
    349 #define SANDBOX_TEST_SKIPPED(name) \
    350  { #name, test_sandbox_ ## name, TT_SKIP, NULL, NULL }
    351 
    352 /* Skip all tests when coverage support is enabled, as the sandbox interferes
    353 * with gcov and prevents it from producing any results. */
    354 #ifdef ENABLE_COVERAGE
    355 #define SANDBOX_TEST(name, flags) SANDBOX_TEST_SKIPPED(name)
    356 #define SANDBOX_TEST_IN_SANDBOX(name) SANDBOX_TEST_SKIPPED(name)
    357 #else
    358 #define SANDBOX_TEST(name, flags) \
    359  { #name, test_sandbox_ ## name, flags, NULL, NULL }
    360 #define SANDBOX_TEST_IN_SANDBOX(name) \
    361  { #name, test_sandbox_ ## name, TT_FORK, &sandboxed_testcase_setup, NULL }
    362 #endif /* defined(ENABLE_COVERAGE) */
    363 
    364 struct testcase_t sandbox_tests[] = {
    365  SANDBOX_TEST(is_active, TT_FORK),
    366 
    367 /* When Tor is built with fragile compiler-hardening the sandbox is usually
    368 * unable to filter requests to open files or directories, as doing so would
    369 * interfere with the address sanitizer as it retrieves information about the
    370 * running process via the filesystem.  Skip these tests in that case as the
    371 * corresponding functions are likely to have no effect and this will cause the
    372 * tests to fail. */
    373 #ifdef ENABLE_FRAGILE_HARDENING
    374  SANDBOX_TEST_SKIPPED(open_filename),
    375  SANDBOX_TEST_SKIPPED(openat_filename),
    376  SANDBOX_TEST_SKIPPED(opendir_dirname),
    377 #else
    378  SANDBOX_TEST_IN_SANDBOX(open_filename),
    379  SANDBOX_TEST_IN_SANDBOX(openat_filename),
    380 #endif /* defined(ENABLE_FRAGILE_HARDENING) */
    381 
    382  /* Ok why... Quick answer is #40918. This has been failing on Debian SID
    383   * making us unable to have nightly packages which is a problem as we have
    384   * several relay operators using them and actively reporting us issues with
    385   * them. This test fails due to the sandbox denying it.
    386   *
    387   * We are deprecating C-tor slowly and honestly, the Sandbox feature has
    388   * always been a source of pain and unhappiness. Disable this as finding why,
    389   * fixing it and hoping it doesn't come back will mostly be a waste of our
    390   * time at this point. */
    391  SANDBOX_TEST_SKIPPED(opendir_dirname),
    392 
    393  SANDBOX_TEST_IN_SANDBOX(chmod_filename),
    394  SANDBOX_TEST_IN_SANDBOX(chown_filename),
    395  SANDBOX_TEST_IN_SANDBOX(rename_filename),
    396 
    397 /* Currently the sandbox is unable to filter stat() calls on systems where
    398 * glibc implements this function using either of the legacy "stat" or "stat64"
    399 * system calls, or (in glibc version 2.33 and later) either of the newer
    400 * "newfstatat" or "statx" syscalls.
    401 *
    402 * Skip testing sandbox_cfg_allow_stat_filename() if it seems the likely the
    403 * function will have no effect and the test will therefore not succeed. */
    404 #if !defined(__NR_stat) && !defined(__NR_stat64) && !defined(__NR_newfstatat) \
    405  && !(defined(__i386__) && defined(__NR_statx))
    406  SANDBOX_TEST_IN_SANDBOX(stat_filename),
    407 #else
    408  SANDBOX_TEST_SKIPPED(stat_filename),
    409 #endif
    410 
    411  SANDBOX_TEST_IN_SANDBOX(crypto_equix),
    412  END_OF_TESTCASES
    413 };
    414 
    415 #endif /* defined(USE_SECCOMP) */