diff options
author | Andrew Morrow <acm@mongodb.com> | 2015-10-09 14:46:48 -0400 |
---|---|---|
committer | Andrew Morrow <acm@mongodb.com> | 2015-10-27 08:05:31 -0400 |
commit | 17a0b2a3be87fb1a1a86db2ee0bf1871e4b8d6fc (patch) | |
tree | 08a3d8696b21b4fae3184220dfebef8e0ec65010 /src/mongo | |
parent | d8a428ded9dd3cc47b945f7efdb26dc50b6adf47 (diff) | |
download | mongo-17a0b2a3be87fb1a1a86db2ee0bf1871e4b8d6fc.tar.gz |
SERVER-20538 Build a general purpose SecureAllocator
Diffstat (limited to 'src/mongo')
-rw-r--r-- | src/mongo/base/SConscript | 25 | ||||
-rw-r--r-- | src/mongo/base/secure_allocator.cpp | 171 | ||||
-rw-r--r-- | src/mongo/base/secure_allocator.h | 181 | ||||
-rw-r--r-- | src/mongo/base/secure_allocator_test.cpp | 57 | ||||
-rw-r--r-- | src/mongo/util/assert_util.cpp | 7 | ||||
-rw-r--r-- | src/mongo/util/log.h | 2 |
6 files changed, 439 insertions, 4 deletions
diff --git a/src/mongo/base/SConscript b/src/mongo/base/SConscript index f5cba35f76d..07d696d48f6 100644 --- a/src/mongo/base/SConscript +++ b/src/mongo/base/SConscript @@ -52,3 +52,28 @@ env.CppUnitTest( 'system_error', ], ) + +env.Library( + target=[ + 'secure_allocator' + ], + source=[ + 'secure_allocator.cpp', + ], + LIBDEPS=[ + '$BUILD_DIR/mongo/base', + '$BUILD_DIR/mongo/util/secure_zero_memory', + ], +) + +env.CppUnitTest( + target=[ + 'secure_allocator_test', + ], + source=[ + 'secure_allocator_test.cpp', + ], + LIBDEPS=[ + 'secure_allocator', + ], +) diff --git a/src/mongo/base/secure_allocator.cpp b/src/mongo/base/secure_allocator.cpp new file mode 100644 index 00000000000..b6ac5d14eaf --- /dev/null +++ b/src/mongo/base/secure_allocator.cpp @@ -0,0 +1,171 @@ +/* Copyright 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kDefault + +#include "mongo/platform/basic.h" + +#include "mongo/base/secure_allocator.h" + +#ifdef _WIN32 +#include <windows.h> +#else +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/types.h> +#endif + +#include "mongo/util/assert_util.h" +#include "mongo/util/log.h" +#include "mongo/util/secure_zero_memory.h" + +namespace mongo { + +/** + * NOTE(jcarey): Why not new/delete? + * + * As a general rule, mlock/virtuallock lack any kind of recursive semantics + * (they free any locks on the underlying page if called once). While some + * platforms do offer those semantics, they're not available globally, so we + * have to flow all allocations through page based allocations. + */ +namespace secure_allocator_details { + +#ifdef _WIN32 + +void* allocate(std::size_t bytes) { + // Flags: + // + // MEM_COMMIT - allocates the memory charges and zeros the underlying + // memory + // MEM_RESERVE - Reserves space in the process's virtual address space + // + // The two flags together give us bytes that are attached to the process + // that we can actually write to. + // + // PAGE_READWRITE - allows read/write access to the page + auto ptr = VirtualAlloc(nullptr, bytes, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + + if (!ptr) { + auto str = errnoWithPrefix("Failed to VirtualAlloc"); + severe() << str; + fassertFailed(28835); + } + + if (VirtualLock(ptr, bytes) == 0) { + auto str = errnoWithPrefix("Failed to VirtualLock"); + severe() << str; + fassertFailed(28828); + } + + return ptr; +} + +void deallocate(void* ptr, std::size_t bytes) { + secureZeroMemory(ptr, bytes); + + if (VirtualUnlock(ptr, bytes) == 0) { + auto str = errnoWithPrefix("Failed to VirtualUnlock"); + severe() << str; + fassertFailed(28829); + } + + // VirtualFree needs to take 0 as the size parameter for MEM_RELEASE + // (that's how the api works). + if (VirtualFree(ptr, 0, MEM_RELEASE) == 0) { + auto str = errnoWithPrefix("Failed to VirtualFree"); + severe() << str; + fassertFailed(28830); + } +} + +#else + +// See https://github.com/libressl-portable/portable/issues/24 for the table +// that suggests this approach. This assumes that MAP_ANONYMOUS and MAP_ANON are +// macro definitions, but that seems plausible on all platforms we care about. + +#if defined(MAP_ANONYMOUS) +#define MONGO_MAP_ANONYMOUS MAP_ANONYMOUS +#else +#if defined(MAP_ANON) +#define MONGO_MAP_ANONYMOUS MAP_ANON +#endif +#endif + +#if !defined(MONGO_MAP_ANONYMOUS) +#error "Could not determine a way to map anonymous memory, required for secure allocation" +#endif + +void* allocate(std::size_t bytes) { + // Flags: + // + // PROT_READ | PROT_WRITE - allows read write access to the page + // + // MAP_PRIVATE - Ensure that the mapping is copy-on-write. Otherwise writes + // made in this process can be seen in children. + // + // MAP_ANONYMOUS - The mapping is not backed by a file. fd must be -1 on + // some platforms, offset is ignored (so 0). + // + // skipping flags like MAP_LOCKED and MAP_POPULATE as linux-isms + auto ptr = + mmap(nullptr, bytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MONGO_MAP_ANONYMOUS, -1, 0); + + if (!ptr) { + auto str = errnoWithPrefix("Failed to mmap"); + severe() << str; + fassertFailed(28831); + } + + if (mlock(ptr, bytes) != 0) { + auto str = errnoWithPrefix("Failed to mlock"); + severe() << str; + fassertFailed(28832); + } + + return ptr; +} + +void deallocate(void* ptr, std::size_t bytes) { + secureZeroMemory(ptr, bytes); + + if (munlock(ptr, bytes) != 0) { + severe() << errnoWithPrefix("Failed to munlock"); + fassertFailed(28833); + } + + if (munmap(ptr, bytes) != 0) { + severe() << errnoWithPrefix("Failed to munmap"); + fassertFailed(28834); + } +} + +#endif + +} // namespace secure_allocator_details +} // namespace mongo diff --git a/src/mongo/base/secure_allocator.h b/src/mongo/base/secure_allocator.h new file mode 100644 index 00000000000..ed10d075968 --- /dev/null +++ b/src/mongo/base/secure_allocator.h @@ -0,0 +1,181 @@ +/* Copyright 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include "mongo/config.h" + +#include <cstddef> +#include <limits> +#include <string> +#include <type_traits> +#include <vector> + +namespace mongo { + +namespace secure_allocator_details { + +void* allocate(std::size_t bytes); +void deallocate(void* ptr, std::size_t bytes); + +} // namespace secure_allocator_details + +/** + * Provides a secure allocator for trivially copyable types. By secure we mean + * memory that will be zeroed on free and locked out of paging while in memory + * (to prevent it from being written to disk). + * + * While this type can be used with any allocator aware container, it should be + * considered whether either of the two named specializations below are + * sufficient (a string and a vector). The allocations out of this container + * are quite expensive, so one should endeavor to use containers which make + * few, contiguous allocations where possible. + * + * Note that this allocator is written without reling on default + * semantics injected via allocator_traits, and so defines all + * optional allocator members, and does not rely on allocator_traits + * to default them in. See http://stackoverflow.com/a/33267132 for a + * rationale for GCC 4.8, our current default compiler. There is also + * evidence that MSVC 2013's _DEBUG STL does not work correctly with + * allocator_traits. + * + * See also: http://howardhinnant.github.io/allocator_boilerplate.html + */ +template <typename T> +struct SecureAllocator { +/** + * We only support trivially copyable types to avoid situations where the + * SecureAllocator is used in containers with complex types that do their + * own allocation. I.e. one could otherwise have a: + * + * std::vector<std::string, SecureAllocator<std::string>> + * + * where the vectors were stored securely, but the strings spilled to the + * heap + * + */ +#ifdef MONGO_CONFIG_HAVE_STD_IS_TRIVIALLY_COPYABLE + static_assert(std::is_trivially_copyable<T>::value, + "SecureAllocator can only be used with trivially copyable types"); +#endif + + // NOTE: The standard doesn't seem to require these, but libstdc++ + // definitly wants them. + using reference = T&; + using const_reference = const T&; + + + // NOTE: These members are defined in the same order as specified + // in the "Allocator Requirements" section of the standard. Please + // retain this ordering. + + using pointer = T*; + using const_pointer = const T*; + using void_pointer = void*; + using const_void_pointer = const void*; + using value_type = T; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + + template <typename U> + struct rebind { + using other = SecureAllocator<U>; + }; + + pointer allocate(size_type n) { + return static_cast<pointer>(secure_allocator_details::allocate(sizeof(value_type) * n)); + } + + pointer allocate(size_type n, const_void_pointer) { + return allocate(n); + } + + void deallocate(pointer ptr, size_type n) { + return secure_allocator_details::deallocate(static_cast<void*>(ptr), + sizeof(value_type) * n); + } + + size_type max_size() { + return std::numeric_limits<size_type>::max(); + } + + SecureAllocator() = default; + + template <typename U> + SecureAllocator(const SecureAllocator<U>& other) {} + + template <typename U, typename... Args> + void construct(U* p, Args&&... args) { + ::new (static_cast<void*>(p)) U(std::forward<Args>(args)...); + } + + template <typename U> + void destroy(U* p) { + p->~U(); + } + + SecureAllocator select_on_container_copy_construction() { + // SecureAllocator is stateless, so just return a default + // constructed instance. + return SecureAllocator(); + } + + // For background: + // + // http://stackoverflow.com/questions/27471053/example-usage-of-propagate-on-container-move-assignment + // + // https://foonathan.github.io/blog/2015/10/05/allocatorawarecontainer-propagation-pitfalls.html + // + // This allocator is stateless, so we can avoid a runtime check + // (even though it would probably be optimized out based on the + // constrexpr-esque nature of our equality comparison operator), + // so we can set all of these to true. + using propagate_on_container_copy_assignment = std::true_type; + using propagate_on_container_move_assignment = std::true_type; + using propagate_on_container_swap = std::true_type; + + using is_always_equal = std::true_type; +}; + +template <typename T, typename U> +bool operator==(const SecureAllocator<T>& lhs, const SecureAllocator<U>& rhs) { + // Note: If you change this, you must re-evaluate the select_ and + // propagate_ methods and typedefs above. + return true; +} + +template <typename T, typename U> +bool operator!=(const SecureAllocator<T>& lhs, const SecureAllocator<U>& rhs) { + return !(lhs == rhs); +} + +template <typename T> +using SecureVector = std::vector<T, SecureAllocator<T>>; + +using SecureString = std::basic_string<char, std::char_traits<char>, SecureAllocator<char>>; + +} // namespace mongo diff --git a/src/mongo/base/secure_allocator_test.cpp b/src/mongo/base/secure_allocator_test.cpp new file mode 100644 index 00000000000..f04b286ef2b --- /dev/null +++ b/src/mongo/base/secure_allocator_test.cpp @@ -0,0 +1,57 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/base/secure_allocator.h" + +#include "mongo/unittest/unittest.h" + +namespace mongo { + +TEST(SecureAllocator, SecureVector) { + SecureVector<int> vec; + + vec.push_back(1); + vec.push_back(2); + + ASSERT_EQUALS(1, vec[0]); + ASSERT_EQUALS(2, vec[1]); + + vec.resize(2000, 3); + ASSERT_EQUALS(3, vec[2]); +} + +TEST(SecureAllocator, SecureString) { + SecureString str; + + str.resize(2000, 'x'); + ASSERT_EQUALS(0, str.compare(SecureString(2000, 'x'))); +} + +} // namespace mongo diff --git a/src/mongo/util/assert_util.cpp b/src/mongo/util/assert_util.cpp index 764afacb140..05b5a433956 100644 --- a/src/mongo/util/assert_util.cpp +++ b/src/mongo/util/assert_util.cpp @@ -257,11 +257,12 @@ std::string causedBy(const Status& e) { return causedBy(e.reason()); } -string errnoWithPrefix(const char* prefix) { +string errnoWithPrefix(StringData prefix) { + const auto suffix = errnoWithDescription(); stringstream ss; - if (prefix) + if (!prefix.empty()) ss << prefix << ": "; - ss << errnoWithDescription(); + ss << suffix; return ss.str(); } diff --git a/src/mongo/util/log.h b/src/mongo/util/log.h index 14fd8615469..b0f53a30b87 100644 --- a/src/mongo/util/log.h +++ b/src/mongo/util/log.h @@ -211,7 +211,7 @@ bool rotateLogs(bool renameFiles); /** output the error # and error message with prefix. handy for use as parm in uassert/massert. */ -std::string errnoWithPrefix(const char* prefix); +std::string errnoWithPrefix(StringData prefix); extern Tee* const warnings; // Things put here go in serverStatus extern Tee* const startupWarningsLog; // Things put here get reported in MMS |