diff --git a/contrib/epee/include/mlocker.h b/contrib/epee/include/mlocker.h new file mode 100644 index 000000000..d2fc2ed58 --- /dev/null +++ b/contrib/epee/include/mlocker.h @@ -0,0 +1,87 @@ +// Copyright (c) 2018, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include + +namespace epee +{ + class mlocker + { + public: + mlocker(void *ptr, size_t len); + ~mlocker(); + + static size_t get_page_size(); + static size_t get_num_locked_pages(); + static size_t get_num_locked_objects(); + + static void lock(void *ptr, size_t len); + static void unlock(void *ptr, size_t len); + + private: + static size_t page_size; + static size_t num_locked_objects; + + static boost::mutex &mutex(); + static std::map &map(); + static void lock_page(size_t page); + static void unlock_page(size_t page); + + void *ptr; + size_t len; + }; + + /// Locks memory while in scope + /// + /// Primarily useful for making sure that private keys don't get swapped out + // to disk + template + struct mlocked : public T { + using type = T; + + mlocked(): T() { mlocker::lock(this, sizeof(T)); } + mlocked(const T &t): T(t) { mlocker::lock(this, sizeof(T)); } + mlocked(const mlocked &mt): T(mt) { mlocker::lock(this, sizeof(T)); } + mlocked(const T &&t): T(t) { mlocker::lock(this, sizeof(T)); } + mlocked(const mlocked &&mt): T(mt) { mlocker::lock(this, sizeof(T)); } + mlocked &operator=(const mlocked &mt) { T::operator=(mt); return *this; } + ~mlocked() { mlocker::unlock(this, sizeof(T)); } + }; + + template + T& unwrap(mlocked& src) { return src; } + + template + const T& unwrap(mlocked const& src) { return src; } + + template + using mlocked_arr = mlocked>; +} diff --git a/contrib/epee/include/string_tools.h b/contrib/epee/include/string_tools.h index 8d8603076..aba065cc7 100644 --- a/contrib/epee/include/string_tools.h +++ b/contrib/epee/include/string_tools.h @@ -46,6 +46,7 @@ #include #include "hex.h" #include "memwipe.h" +#include "mlocker.h" #include "span.h" #include "warnings.h" @@ -358,6 +359,12 @@ POP_WARNINGS return hex_to_pod(hex_str, unwrap(s)); } //---------------------------------------------------------------------------- + template + bool hex_to_pod(const std::string& hex_str, epee::mlocked& s) + { + return hex_to_pod(hex_str, unwrap(s)); + } + //---------------------------------------------------------------------------- bool validate_hex(uint64_t length, const std::string& str); //---------------------------------------------------------------------------- inline std::string get_extension(const std::string& str) diff --git a/contrib/epee/src/CMakeLists.txt b/contrib/epee/src/CMakeLists.txt index c4750cea0..0b5e7ae6c 100644 --- a/contrib/epee/src/CMakeLists.txt +++ b/contrib/epee/src/CMakeLists.txt @@ -27,7 +27,7 @@ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. add_library(epee STATIC hex.cpp http_auth.cpp mlog.cpp net_utils_base.cpp string_tools.cpp wipeable_string.cpp memwipe.c - connection_basic.cpp network_throttle.cpp network_throttle-detail.cpp) + connection_basic.cpp network_throttle.cpp network_throttle-detail.cpp mlocker.cpp) if (USE_READLINE AND GNU_READLINE_FOUND) add_library(epee_readline STATIC readline_buffer.cpp) endif() diff --git a/contrib/epee/src/mlocker.cpp b/contrib/epee/src/mlocker.cpp new file mode 100644 index 000000000..5573d591a --- /dev/null +++ b/contrib/epee/src/mlocker.cpp @@ -0,0 +1,182 @@ +// Copyright (c) 2018, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#if defined __GNUC__ && !defined _WIN32 +#define HAVE_MLOCK 1 +#endif + +#include +#if defined HAVE_MLOCK +#include +#endif +#include "misc_log_ex.h" +#include "syncobj.h" +#include "mlocker.h" + +static size_t query_page_size() +{ +#if defined HAVE_MLOCK + long ret = sysconf(_SC_PAGESIZE); + if (ret <= 0) + { + MERROR("Failed to determine page size"); + return 0; + } + MINFO("Page size: " << ret); + return ret; +#else +#warning Missing query_page_size implementation +#endif + return 0; +} + +static void do_lock(void *ptr, size_t len) +{ +#if defined HAVE_MLOCK + int ret = mlock(ptr, len); + if (ret < 0) + MERROR("Error locking page at " << ptr << ": " << strerror(errno)); +#else +#warning Missing do_lock implementation +#endif +} + +static void do_unlock(void *ptr, size_t len) +{ +#if defined HAVE_MLOCK + int ret = munlock(ptr, len); + if (ret < 0) + MERROR("Error unlocking page at " << ptr << ": " << strerror(errno)); +#else +#warning Missing implementation of page size detection +#endif +} + +namespace epee +{ + size_t mlocker::page_size = 0; + size_t mlocker::num_locked_objects = 0; + + boost::mutex &mlocker::mutex() + { + static boost::mutex vmutex; + return vmutex; + } + std::map &mlocker::map() + { + static std::map vmap; + return vmap; + } + + size_t mlocker::get_page_size() + { + CRITICAL_REGION_LOCAL(mutex()); + if (page_size == 0) + page_size = query_page_size(); + return page_size; + } + + mlocker::mlocker(void *ptr, size_t len): ptr(ptr), len(len) + { + lock(ptr, len); + } + + mlocker::~mlocker() + { + unlock(ptr, len); + } + + void mlocker::lock(void *ptr, size_t len) + { + size_t page_size = get_page_size(); + if (page_size == 0) + return; + + CRITICAL_REGION_LOCAL(mutex()); + const size_t first = ((uintptr_t)ptr) / page_size; + const size_t last = (((uintptr_t)ptr) + len - 1) / page_size; + for (size_t page = first; page <= last; ++page) + lock_page(page); + ++num_locked_objects; + } + + void mlocker::unlock(void *ptr, size_t len) + { + size_t page_size = get_page_size(); + if (page_size == 0) + return; + CRITICAL_REGION_LOCAL(mutex()); + const size_t first = ((uintptr_t)ptr) / page_size; + const size_t last = (((uintptr_t)ptr) + len - 1) / page_size; + for (size_t page = first; page <= last; ++page) + unlock_page(page); + --num_locked_objects; + } + + size_t mlocker::get_num_locked_pages() + { + CRITICAL_REGION_LOCAL(mutex()); + return map().size(); + } + + size_t mlocker::get_num_locked_objects() + { + CRITICAL_REGION_LOCAL(mutex()); + return num_locked_objects; + } + + void mlocker::lock_page(size_t page) + { + std::pair::iterator, bool> p = map().insert(std::make_pair(page, 1)); + if (p.second) + { + do_lock((void*)(page * page_size), page_size); + } + else + { + ++p.first->second; + } + } + + void mlocker::unlock_page(size_t page) + { + std::map::iterator i = map().find(page); + if (i == map().end()) + { + MERROR("Attempt to unlock unlocked page at " << (void*)(page * page_size)); + } + else + { + if (!--i->second) + { + map().erase(i); + do_unlock((void*)(page * page_size), page_size); + } + } + } +} diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt index 6d4ebe47d..4b4870c15 100644 --- a/tests/unit_tests/CMakeLists.txt +++ b/tests/unit_tests/CMakeLists.txt @@ -53,6 +53,7 @@ set(unit_tests_sources http.cpp main.cpp memwipe.cpp + mlocker.cpp mnemonics.cpp mul_div.cpp multisig.cpp diff --git a/tests/unit_tests/mlocker.cpp b/tests/unit_tests/mlocker.cpp new file mode 100644 index 000000000..6e6048c6c --- /dev/null +++ b/tests/unit_tests/mlocker.cpp @@ -0,0 +1,186 @@ +// Copyright (c) 2018, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "gtest/gtest.h" + +#include "misc_log_ex.h" +#include "mlocker.h" + +#define BASE(data) (char*)(((uintptr_t)(data.get() + page_size - 1)) / page_size * page_size) + +TEST(mlocker, distinct_1) +{ + const size_t page_size = epee::mlocker::get_page_size(); + ASSERT_TRUE(page_size > 0); + const size_t base_pages = epee::mlocker::get_num_locked_pages(); + const size_t base_objects = epee::mlocker::get_num_locked_objects(); + std::unique_ptr data{new char[8 * page_size]}; + epee::mlocker *m0 = new epee::mlocker(BASE(data), 1); + epee::mlocker *m1 = new epee::mlocker(BASE(data) + 2 * page_size, 1); + epee::mlocker *m2 = new epee::mlocker(BASE(data) + 3 * page_size, 1); + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 3); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 3); + delete m0; + delete m1; + delete m2; + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 0); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 0); +} + +TEST(mlocker, distinct_full_page) +{ + const size_t page_size = epee::mlocker::get_page_size(); + ASSERT_TRUE(page_size > 0); + const size_t base_pages = epee::mlocker::get_num_locked_pages(); + const size_t base_objects = epee::mlocker::get_num_locked_objects(); + std::unique_ptr data{new char[8 * page_size]}; + epee::mlocker *m0 = new epee::mlocker(BASE(data), page_size); + epee::mlocker *m1 = new epee::mlocker(BASE(data) + 2 * page_size, page_size); + epee::mlocker *m2 = new epee::mlocker(BASE(data) + 3 * page_size, page_size); + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 3); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 3); + delete m0; + delete m1; + delete m2; + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 0); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 0); +} + +TEST(mlocker, identical) +{ + const size_t page_size = epee::mlocker::get_page_size(); + ASSERT_TRUE(page_size >= 32); + const size_t base_pages = epee::mlocker::get_num_locked_pages(); + const size_t base_objects = epee::mlocker::get_num_locked_objects(); + std::unique_ptr data{new char[8 * page_size]}; + epee::mlocker *m0 = new epee::mlocker(BASE(data) + page_size, 32); + epee::mlocker *m1 = new epee::mlocker(BASE(data) + page_size, 32); + epee::mlocker *m2 = new epee::mlocker(BASE(data) + page_size, 32); + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 1); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 3); + delete m1; + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 1); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 2); + delete m0; + delete m2; + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 0); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 0); +} + +TEST(mlocker, overlapping_small) +{ + const size_t page_size = epee::mlocker::get_page_size(); + ASSERT_TRUE(page_size >= 64); + const size_t base_pages = epee::mlocker::get_num_locked_pages(); + const size_t base_objects = epee::mlocker::get_num_locked_objects(); + std::unique_ptr data{new char[8 * page_size]}; + epee::mlocker *m0 = new epee::mlocker(BASE(data), 32); + epee::mlocker *m1 = new epee::mlocker(BASE(data) + 16, 32); + epee::mlocker *m2 = new epee::mlocker(BASE(data) + 8, 32); + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 1); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 3); + delete m1; + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 1); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 2); + delete m2; + delete m0; + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 0); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 0); +} + +TEST(mlocker, multi_page) +{ + const size_t page_size = epee::mlocker::get_page_size(); + ASSERT_TRUE(page_size > 0); + const size_t base_pages = epee::mlocker::get_num_locked_pages(); + const size_t base_objects = epee::mlocker::get_num_locked_objects(); + std::unique_ptr data{new char[8 * page_size]}; + epee::mlocker *m0 = new epee::mlocker(BASE(data) + page_size, page_size * 3); + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 3); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 1); + epee::mlocker *m1 = new epee::mlocker(BASE(data) + page_size * 7, page_size); + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 4); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 2); + delete m0; + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 1); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 1); + delete m1; + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 0); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 0); +} + +TEST(mlocker, cross_page) +{ + const size_t page_size = epee::mlocker::get_page_size(); + ASSERT_TRUE(page_size > 32); + const size_t base_pages = epee::mlocker::get_num_locked_pages(); + const size_t base_objects = epee::mlocker::get_num_locked_objects(); + std::unique_ptr data{new char[2 * page_size]}; + epee::mlocker *m0 = new epee::mlocker(BASE(data) + page_size - 1, 2); + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 2); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 1); + delete m0; + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 0); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 0); +} + +TEST(mlocker, redundant) +{ + const size_t page_size = epee::mlocker::get_page_size(); + const size_t base_pages = epee::mlocker::get_num_locked_pages(); + const size_t base_objects = epee::mlocker::get_num_locked_objects(); + std::unique_ptr data{new char[2 * page_size]}; + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 0); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 0); + epee::mlocker *m0 = new epee::mlocker(BASE(data), 32); + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 1); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 1); + epee::mlocker *m1 = new epee::mlocker(BASE(data), 32); + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 1); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 2); + delete m1; + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 1); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 1); + delete m0; + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 0); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 0); +} + +TEST(mlocker, mlocked) +{ + const size_t base_pages = epee::mlocker::get_num_locked_pages(); + const size_t base_objects = epee::mlocker::get_num_locked_objects(); + { + struct Foo { uint64_t u; }; + epee::mlocked l; + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 1); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 1); + } + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 0); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 0); +}