diff options
author | Nick Mathewson <nickm@torproject.org> | 2019-02-05 16:58:56 -0500 |
---|---|---|
committer | Nick Mathewson <nickm@torproject.org> | 2019-02-06 22:03:30 -0500 |
commit | 8ca808f81d28bbae9e9059172a7b7479d20e9594 (patch) | |
tree | fa9eb7ccf4db3015134db06cfadc48d72621e670 /src | |
parent | bfd1d702433fde0c551a1c79d410d87fe22feb30 (diff) | |
download | tor-8ca808f81d28bbae9e9059172a7b7479d20e9594.tar.gz tor-8ca808f81d28bbae9e9059172a7b7479d20e9594.zip |
Code for anonymous mappings via mmap() or CreateFileMapping().
Using an anonymous mmap() is a good way to get pages that we can set
kernel-level flags on, like minherit() or madvise() or mlock().
We're going to use that so that we can make uninheritable locked
pages to store PRNG data.
Diffstat (limited to 'src')
-rw-r--r-- | src/lib/malloc/include.am | 6 | ||||
-rw-r--r-- | src/lib/malloc/map_anon.c | 213 | ||||
-rw-r--r-- | src/lib/malloc/map_anon.h | 37 | ||||
-rw-r--r-- | src/test/test_util.c | 107 |
4 files changed, 361 insertions, 2 deletions
diff --git a/src/lib/malloc/include.am b/src/lib/malloc/include.am index 502cc1c6b7..95d96168e1 100644 --- a/src/lib/malloc/include.am +++ b/src/lib/malloc/include.am @@ -6,7 +6,8 @@ noinst_LIBRARIES += src/lib/libtor-malloc-testing.a endif src_lib_libtor_malloc_a_SOURCES = \ - src/lib/malloc/malloc.c + src/lib/malloc/malloc.c \ + src/lib/malloc/map_anon.c if USE_OPENBSD_MALLOC src_lib_libtor_malloc_a_SOURCES += src/ext/OpenBSD_malloc_Linux.c @@ -18,4 +19,5 @@ src_lib_libtor_malloc_testing_a_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS) src_lib_libtor_malloc_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) noinst_HEADERS += \ - src/lib/malloc/malloc.h + src/lib/malloc/malloc.h \ + src/lib/malloc/map_anon.h diff --git a/src/lib/malloc/map_anon.c b/src/lib/malloc/map_anon.c new file mode 100644 index 0000000000..2fc6e89ea2 --- /dev/null +++ b/src/lib/malloc/map_anon.c @@ -0,0 +1,213 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file map_anon.c + * \brief Manage anonymous mappings. + **/ + +#include "orconfig.h" +#include "lib/malloc/map_anon.h" +#include "lib/malloc/malloc.h" +#include "lib/err/torerr.h" + +#ifdef HAVE_SYS_MMAN_H +#include <sys/mman.h> +#endif +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#ifdef HAVE_MACH_VM_INHERIT_H +#include <mach/vm_inherit.h> +#endif + +#ifdef _WIN32 +#include <windows.h> +#endif + +/** + * Macro to get the high bytes of a size_t, if there are high bytes. + * Windows needs this; other operating systems define a size_t that does + * what it should. + */ +#if SIZEOF_SIZE_T > 4 +#define HIGH_SIZE_T_BYTES(sz) ((sz) >> 32) +#else +#define HIGH_SIZE_T_BYTES(sz) (0) +#endif + +/* Here we define a MINHERIT macro that is minherit() or madvise(), depending + * on what we actually want. + * + * If there's a flag that sets pages to zero after fork, we define FLAG_ZERO + * to be that flag. If there's a flag unmaps pages after fork, we define + * FLAG_NOINHERIT to be that flag. + */ +#if defined(HAVE_MINHERIT) +#define MINHERIT minherit + +#ifdef INHERIT_ZERO +#define FLAG_ZERO INHERIT_ZERO +#endif +#ifdef INHERIT_NONE +#define FLAG_NOINHERIT INHERIT_NONE +#elif defined(VM_INHERIT_NONE) +#define FLAG_NOINHERIT VM_INHERIT_NONE +#endif + +#elif defined(HAVE_MADVISE) + +#define MINHERIT madvise + +#ifdef MADV_WIPEONFORK +#define FLAG_ZERO MADV_WIPEONFORK +#endif +#ifdef MADV_DONTFORK +#define FLAG_NOINHERIT MADV_DONTFORK +#endif + +#endif + +/** + * Helper: try to prevent the <b>sz</b> bytes at <b>mem</b> from being swapped + * to disk. Return 0 on success or if the facility is not available on this + * OS; return -1 on failure. + */ +static int +lock_mem(void *mem, size_t sz) +{ +#ifdef _WIN32 + return VirtualLock(mem, sz) ? 0 : -1; +#elif defined(HAVE_MLOCK) + return mlock(mem, sz); +#else + (void) mem; + (void) sz; + + return 0; +#endif +} + +/** + * Helper: try to prevent the <b>sz</b> bytes at <b>mem</b> from appearing in + * a core dump. Return 0 on success or if the facility is not available on + * this OS; return -1 on failure. + */ +static int +nodump_mem(void *mem, size_t sz) +{ +#if defined(MADV_DONTDUMP) + return madvise(mem, sz, MADV_DONTDUMP); +#else + (void) mem; + (void) sz; + return 0; +#endif +} + +/** + * Helper: try to prevent the <b>sz</b> bytes at <b>mem</b> from being + * accessible in child processes -- ideally by having them set to 0 after a + * fork, and if that doesn't work, by having them unmapped after a fork. + * Return 0 on success or if the facility is not available on this OS; return + * -1 on failure. + */ +static int +noinherit_mem(void *mem, size_t sz) +{ +#ifdef FLAG_ZERO + int r = MINHERIT(mem, sz, FLAG_ZERO); + if (r == 0) + return 0; +#endif +#ifdef FLAG_NOINHERIT + return MINHERIT(mem, sz, FLAG_NOINHERIT); +#else + (void)mem; + (void)sz; + return 0; +#endif +} + +/** + * Return a new anonymous memory mapping that holds <b>sz</b> bytes. + * + * Memory mappings are unlike the results from malloc() in that they are + * handled separately by the operating system, and as such can have different + * kernel-level flags set on them. + * + * The "flags" argument may be zero or more of ANONMAP_PRIVATE and + * ANONMAP_NOINHERIT. + * + * Memory returned from this function must be released with + * tor_munmap_anonymous(). + * + * [Note: OS people use the word "anonymous" here to mean that the memory + * isn't associated with any file. This has *nothing* to do with the kind of + * anonymity that Tor is trying to provide.] + */ +void * +tor_mmap_anonymous(size_t sz, unsigned flags) +{ + void *ptr; +#if defined(_WIN32) + HANDLE mapping = CreateFileMapping(INVALID_HANDLE_VALUE, + NULL, /*attributes*/ + PAGE_READWRITE, + HIGH_SIZE_T_BYTES(sz), + sz & 0xffffffff, + NULL /* name */); + raw_assert(mapping != NULL); + ptr = MapViewOfFile(mapping, FILE_MAP_WRITE, + 0, 0, /* Offset */ + 0 /* Extend to end of mapping */); + raw_assert(ptr); + CloseHandle(mapping); /* mapped view holds a reference */ +#elif defined(HAVE_SYS_MMAN_H) + ptr = mmap(NULL, sz, + PROT_READ|PROT_WRITE, + MAP_ANON|MAP_PRIVATE, + -1, 0); + raw_assert(ptr != MAP_FAILED); + raw_assert(ptr != NULL); +#else + ptr = tor_malloc_zero(sz); +#endif + + if (flags & ANONMAP_PRIVATE) { + int lock_result = lock_mem(ptr, sz); + raw_assert(lock_result == 0); + int nodump_result = nodump_mem(ptr, sz); + raw_assert(nodump_result == 0); + } + + if (flags & ANONMAP_NOINHERIT) { + int noinherit_result = noinherit_mem(ptr, sz); + raw_assert(noinherit_result == 0); + } + + return ptr; +} + +/** + * Release <b>sz</b> bytes of memory that were previously mapped at + * <b>mapping</b> by tor_mmap_anonymous(). + **/ +void +tor_munmap_anonymous(void *mapping, size_t sz) +{ + if (!mapping) + return; + +#if defined(_WIN32) + (void)sz; + UnmapViewOfFile(mapping); +#elif defined(HAVE_SYS_MMAN_H) + munmap(mapping, sz); +#else + (void)sz; + tor_free(mapping); +#endif +} diff --git a/src/lib/malloc/map_anon.h b/src/lib/malloc/map_anon.h new file mode 100644 index 0000000000..cc5797e4ec --- /dev/null +++ b/src/lib/malloc/map_anon.h @@ -0,0 +1,37 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file map_anon.h + * \brief Headers for map_anon.c + **/ + +#ifndef TOR_MAP_ANON_H +#define TOR_MAP_ANON_H + +#include "lib/malloc/malloc.h" +#include <stddef.h> + +/** + * When this flag is specified, try to prevent the mapping from being + * swapped or dumped. + * + * In some operating systems, this flag is not implemented. + */ +#define ANONMAP_PRIVATE (1u<<0) +/** + * When this flag is specified, try to prevent the mapping from being + * inherited after a fork(). In some operating systems, trying to access it + * afterwards will cause its contents to be zero. In others, trying to access + * it afterwards will cause a crash. + * + * In some operating systems, this flag is not implemented at all. + */ +#define ANONMAP_NOINHERIT (1u<<1) + +void *tor_mmap_anonymous(size_t sz, unsigned flags); +void tor_munmap_anonymous(void *mapping, size_t sz); + +#endif /* !defined(TOR_MAP_ANON_H) */ diff --git a/src/test/test_util.c b/src/test/test_util.c index 4e95303f2e..ba78b55936 100644 --- a/src/test/test_util.c +++ b/src/test/test_util.c @@ -40,6 +40,7 @@ #include "lib/time/tvdiff.h" #include "lib/encoding/confline.h" #include "lib/net/socketpair.h" +#include "lib/malloc/map_anon.h" #ifdef HAVE_PWD_H #include <pwd.h> @@ -59,6 +60,12 @@ #ifdef HAVE_UNISTD_H #include <unistd.h> #endif +#ifdef HAVE_SYS_MMAN_H +#include <sys/mman.h> +#endif +#ifdef HAVE_SYS_WAIT_H +#include <sys/wait.h> +#endif #ifdef _WIN32 #include <tchar.h> @@ -6093,6 +6100,104 @@ test_util_log_mallinfo(void *arg) tor_free(mem); } +static void +test_util_map_anon(void *arg) +{ + (void)arg; + char *ptr = NULL; + size_t sz = 16384; + + /* Basic checks. */ + ptr = tor_mmap_anonymous(sz, 0); + tt_ptr_op(ptr, OP_NE, 0); + ptr[sz-1] = 3; + tt_int_op(ptr[0], OP_EQ, 0); + tt_int_op(ptr[sz-2], OP_EQ, 0); + tt_int_op(ptr[sz-1], OP_EQ, 3); + + /* Try again, with a private (non-swappable) mapping. */ + tor_munmap_anonymous(ptr, sz); + ptr = tor_mmap_anonymous(sz, ANONMAP_PRIVATE); + tt_ptr_op(ptr, OP_NE, 0); + ptr[sz-1] = 10; + tt_int_op(ptr[0], OP_EQ, 0); + tt_int_op(ptr[sz/2], OP_EQ, 0); + tt_int_op(ptr[sz-1], OP_EQ, 10); + + /* Now let's test a drop-on-fork mapping. */ + tor_munmap_anonymous(ptr, sz); + ptr = tor_mmap_anonymous(sz, ANONMAP_NOINHERIT); + tt_ptr_op(ptr, OP_NE, 0); + ptr[sz-1] = 10; + tt_int_op(ptr[0], OP_EQ, 0); + tt_int_op(ptr[sz/2], OP_EQ, 0); + tt_int_op(ptr[sz-1], OP_EQ, 10); + + done: + tor_munmap_anonymous(ptr, sz); +} + +static void +test_util_map_anon_nofork(void *arg) +{ + (void)arg; +#if !defined(HAVE_MADVISE) && !defined(HAVE_MINHERIT) + /* The operating system doesn't support this. */ + tt_skip(); + done: + ; +#else + /* We have the right OS support. We're going to try marking the buffer as + * either zero-on-fork or as drop-on-fork, whichever is supported. Then we + * will fork and send a byte back to the parent process. This will either + * crash, or send zero. */ + + char *ptr = NULL; + size_t sz = 16384; + int pipefd[2] = {-1, -1}; + + tor_munmap_anonymous(ptr, sz); + ptr = tor_mmap_anonymous(sz, ANONMAP_NOINHERIT); + tt_ptr_op(ptr, OP_NE, 0); + memset(ptr, 0xd0, sz); + + tt_int_op(0, OP_EQ, pipe(pipefd)); + pid_t child = fork(); + if (child == 0) { + /* We're in the child. */ + close(pipefd[0]); + ssize_t r = write(pipefd[1], &ptr[sz-1], 1); /* This may crash. */ + close(pipefd[1]); + if (r < 0) + exit(1); + exit(0); + } + tt_int_op(child, OP_GT, 0); + /* In the parent. */ + close(pipefd[1]); + pipefd[1] = -1; + char buf[1]; + ssize_t r = read(pipefd[0], buf, 1); +#if defined(INHERIT_ZERO) || defined(MADV_WIPEONFORK) + tt_int_op((int)r, OP_EQ, 1); // child should send us a byte. + tt_int_op(buf[0], OP_EQ, 0); +#else + tt_int_op(r, OP_LE, 0); // child said nothing; it should have crashed. +#endif + int ws; + waitpid(child, &ws, 0); + + done: + tor_munmap_anonymous(ptr, sz); + if (pipefd[0] >= 0) { + close(pipefd[0]); + } + if (pipefd[1] >= 0) { + close(pipefd[1]); + } +#endif +} + #define UTIL_LEGACY(name) \ { #name, test_util_ ## name , 0, NULL, NULL } @@ -6230,5 +6335,7 @@ struct testcase_t util_tests[] = { UTIL_TEST(htonll, 0), UTIL_TEST(get_unquoted_path, 0), UTIL_TEST(log_mallinfo, 0), + UTIL_TEST(map_anon, 0), + UTIL_TEST(map_anon_nofork, 0), END_OF_TESTCASES }; |