[tor-commits] [tor/main] test: Add sandbox unit tests

nickm at torproject.org nickm at torproject.org
Wed Sep 29 21:29:52 UTC 2021


commit 1a10948260d915d4982415e37dd6495bca9ef545
Author: Simon South <simon at simonsouth.net>
Date:   Thu Sep 2 11:04:23 2021 -0400

    test: Add sandbox unit tests
---
 changes/issue16803      |   2 +
 src/test/include.am     |   1 +
 src/test/test.c         |   4 +
 src/test/test.h         |   1 +
 src/test/test_sandbox.c | 348 ++++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 356 insertions(+)

diff --git a/changes/issue16803 b/changes/issue16803
new file mode 100644
index 0000000000..7d0dd833e2
--- /dev/null
+++ b/changes/issue16803
@@ -0,0 +1,2 @@
+  o Testing:
+    - Add unit tests for the Linux seccomp sandbox. Resolves issue 16803.
diff --git a/src/test/include.am b/src/test/include.am
index 0dc1630044..fb2c03ceb9 100644
--- a/src/test/include.am
+++ b/src/test/include.am
@@ -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 \
diff --git a/src/test/test.c b/src/test/test.c
index 0aa1353ec2..c38d78da30 100644
--- a/src/test/test.c
+++ b/src/test/test.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 },
diff --git a/src/test/test.h b/src/test/test.h
index 0a22043acc..e17bce427c 100644
--- a/src/test/test.h
+++ b/src/test/test.h
@@ -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[];
diff --git a/src/test/test_sandbox.c b/src/test/test_sandbox.c
new file mode 100644
index 0000000000..e5064c58ec
--- /dev/null
+++ b/src/test/test_sandbox.c
@@ -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) */



More information about the tor-commits mailing list