Skip to content

Commit

Permalink
SERVER-20538 Build a general purpose SecureAllocator
Browse files Browse the repository at this point in the history
  • Loading branch information
acmorrow committed Oct 27, 2015
1 parent d8a428d commit 17a0b2a
Show file tree
Hide file tree
Showing 6 changed files with 439 additions and 4 deletions.
25 changes: 25 additions & 0 deletions src/mongo/base/SConscript
Original file line number Diff line number Diff line change
Expand Up @@ -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',
],
)
171 changes: 171 additions & 0 deletions src/mongo/base/secure_allocator.cpp
Original file line number Diff line number Diff line change
@@ -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
181 changes: 181 additions & 0 deletions src/mongo/base/secure_allocator.h
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit 17a0b2a

Please sign in to comment.