X-Git-Url: https://git.novaco.in/?a=blobdiff_plain;f=src%2Fallocators.h;h=484d1b8b15ee2c0a1509f5030c6910c1ef57d87c;hb=HEAD;hp=85d9cfbbb8f827bb1586291faaf6df34686bc997;hpb=6cb6d623479c5dd42d91de7a4d391078d0800e54;p=novacoin.git diff --git a/src/allocators.h b/src/allocators.h index 85d9cfb..484d1b8 100644 --- a/src/allocators.h +++ b/src/allocators.h @@ -1,40 +1,161 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers // Distributed under the MIT/X11 software license, see the accompanying -// file license.txt or http://www.opensource.org/licenses/mit-license.php. +// file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_ALLOCATORS_H #define BITCOIN_ALLOCATORS_H +#include // for OPENSSL_cleanse() + +#include #include +#include +#include +#include +#include -#ifdef WIN32 -#define _WIN32_WINNT 0x0501 -#define WIN32_LEAN_AND_MEAN 1 -#ifndef NOMINMAX -#define NOMINMAX -#endif -#include -// This is used to attempt to keep keying material out of swap -// Note that VirtualLock does not provide this as a guarantee on Windows, -// but, in practice, memory that has been VirtualLock'd almost never gets written to -// the pagefile except in rare circumstances where memory is extremely low. -#define mlock(p, n) VirtualLock((p), (n)); -#define munlock(p, n) VirtualUnlock((p), (n)); -#else -#include -#include -/* This comes from limits.h if it's not defined there set a sane default */ -#ifndef PAGESIZE -#include -#define PAGESIZE sysconf(_SC_PAGESIZE) -#endif -#define mlock(a,b) \ - mlock(((void *)(((size_t)(a)) & (~((PAGESIZE)-1)))),\ - (((((size_t)(a)) + (b) - 1) | ((PAGESIZE) - 1)) + 1) - (((size_t)(a)) & (~((PAGESIZE) - 1)))) -#define munlock(a,b) \ - munlock(((void *)(((size_t)(a)) & (~((PAGESIZE)-1)))),\ - (((((size_t)(a)) + (b) - 1) | ((PAGESIZE) - 1)) + 1) - (((size_t)(a)) & (~((PAGESIZE) - 1)))) -#endif +/** + * Thread-safe class to keep track of locked (ie, non-swappable) memory pages. + * + * Memory locks do not stack, that is, pages which have been locked several times by calls to mlock() + * will be unlocked by a single call to munlock(). This can result in keying material ending up in swap when + * those functions are used naively. This class simulates stacking memory locks by keeping a counter per page. + * + * @note By using a map from each page base address to lock count, this class is optimized for + * small objects that span up to a few pages, mostly smaller than a page. To support large allocations, + * something like an interval tree would be the preferred data structure. + */ +template class LockedPageManagerBase +{ +public: + LockedPageManagerBase(size_t page_size): + page_size(page_size) + { + // Determine bitmask for extracting page from address + assert(!(page_size & (page_size-1))); // size must be power of two + page_mask = ~(page_size - 1); + } + + ~LockedPageManagerBase() + { + assert(this->GetLockedPageCount() == 0); + } + + // For all pages in affected range, increase lock count + void LockRange(void *p, size_t size) + { + std::scoped_lock lock(mutex); + if(!size) return; + const size_t base_addr = reinterpret_cast(p); + const size_t start_page = base_addr & page_mask; + const size_t end_page = (base_addr + size - 1) & page_mask; + for(size_t page = start_page; page <= end_page; page += page_size) + { + Histogram::iterator it = histogram.find(page); + if(it == histogram.end()) // Newly locked page + { + locker.Lock(reinterpret_cast(page), page_size); + histogram.insert(std::make_pair(page, 1)); + } + else // Page was already locked; increase counter + { + it->second += 1; + } + } + } + + // For all pages in affected range, decrease lock count + void UnlockRange(void *p, size_t size) + { + std::scoped_lock lock(mutex); + if(!size) return; + const size_t base_addr = reinterpret_cast(p); + const size_t start_page = base_addr & page_mask; + const size_t end_page = (base_addr + size - 1) & page_mask; + for(size_t page = start_page; page <= end_page; page += page_size) + { + Histogram::iterator it = histogram.find(page); + assert(it != histogram.end()); // Cannot unlock an area that was not locked + // Decrease counter for page, when it is zero, the page will be unlocked + it->second -= 1; + if(it->second == 0) // Nothing on the page anymore that keeps it locked + { + // Unlock page and remove the count from histogram + locker.Unlock(reinterpret_cast(page), page_size); + histogram.erase(it); + } + } + } + + // Get number of locked pages for diagnostics + int GetLockedPageCount() + { + std::scoped_lock lock(mutex); + return histogram.size(); + } + +private: + Locker locker; + std::mutex mutex; + size_t page_size, page_mask; + // map of page base address to lock count + typedef std::map Histogram; + Histogram histogram; +}; + +/** + * OS-dependent memory page locking/unlocking. + * Defined as policy class to make stubbing for test possible. + */ +class MemoryPageLocker +{ +public: + /** Lock memory pages. + * addr and len must be a multiple of the system page size + */ + bool Lock(const void *addr, size_t len); + /** Unlock memory pages. + * addr and len must be a multiple of the system page size + */ + bool Unlock(const void *addr, size_t len); +}; + +/** + * Singleton class to keep track of locked (ie, non-swappable) memory pages, for use in + * std::allocator templates. + * + * Some implementations of the STL allocate memory in some constructors (i.e., see + * MSVC's vector implementation where it allocates 1 byte of memory in the allocator.) + * Due to the unpredictable order of static initializers, we have to make sure the + * LockedPageManager instance exists before any other STL-based objects that use + * secure_allocator are created. So instead of having LockedPageManager also be + * static-intialized, it is created on demand. +*/ +class LockedPageManager: public LockedPageManagerBase +{ +public: + static LockedPageManager& Instance() + { + std::call_once(LockedPageManager::init_flag, LockedPageManager::CreateInstance); + return *LockedPageManager::_instance; + } +private: + LockedPageManager(); + + static void CreateInstance() + { + // Using a local static instance guarantees that the object is initialized + // when it's first needed and also deinitialized after all objects that use + // it are done with it. I can think of one unlikely scenario where we may + // have a static deinitialization order/problem, but the check in + // LockedPageManagerBase's destructor helps us detect if that ever happens. + static LockedPageManager instance; + LockedPageManager::_instance = &instance; + } + + static LockedPageManager* _instance; + static std::once_flag init_flag; +}; // // Allocator that locks its contents from being paged @@ -60,21 +181,21 @@ struct secure_allocator : public std::allocator template struct rebind { typedef secure_allocator<_Other> other; }; - T* allocate(std::size_t n, const void *hint = 0) + T* allocate(std::size_t n, const void *hint = nullptr) { T *p; p = std::allocator::allocate(n, hint); - if (p != NULL) - mlock(p, sizeof(T) * n); + if (p != nullptr) + LockedPageManager::Instance().LockRange(p, sizeof(T) * n); return p; } void deallocate(T* p, std::size_t n) { - if (p != NULL) + if (p != nullptr) { - memset(p, 0, sizeof(T) * n); - munlock(p, sizeof(T) * n); + OPENSSL_cleanse(p, sizeof(T) * n); + LockedPageManager::Instance().UnlockRange(p, sizeof(T) * n); } std::allocator::deallocate(p, n); } @@ -106,14 +227,16 @@ struct zero_after_free_allocator : public std::allocator void deallocate(T* p, std::size_t n) { - if (p != NULL) - memset(p, 0, sizeof(T) * n); + if (p != nullptr) + OPENSSL_cleanse(p, sizeof(T) * n); std::allocator::deallocate(p, n); } }; // This is exactly like std::string, but with a custom allocator. -// (secure_allocator<> is defined in serialize.h) typedef std::basic_string, secure_allocator > SecureString; +// Byte-vector that clears its contents before deletion. +typedef std::vector > CSerializeData; + #endif