From 138fd8fdcf9700fd6dea54dec4592628d690aaf1 Mon Sep 17 00:00:00 2001 From: Aster Seker Date: Sun, 7 Sep 2025 05:10:44 +0300 Subject: [PATCH] feat(secure_buffer): add lifecycle APIs and hardened zeroing --- CMakeLists.txt | 9 +++- README-RU.md | 5 ++- README.md | 5 ++- include/hmac_cpp/secure_buffer.hpp | 68 +++++++++++++++++++++++++++++- 4 files changed, 81 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 85a0793..7ad1e31 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,9 @@ option(HMACCPP_BUILD_BENCH "Build benchmarks" OFF) option(HMACCPP_BUILD_SHARED "Build hmac_cpp as a shared library" OFF) option(HMACCPP_ENABLE_MLOCK "Pin secret buffers in RAM using mlock/VirtualLock" ON) +include(CheckSymbolExists) +check_symbol_exists(explicit_bzero "strings.h" HAVE_EXPLICIT_BZERO) + if(HMACCPP_BUILD_SHARED) set(BUILD_SHARED_LIBS ON) endif() @@ -45,13 +48,17 @@ target_include_directories(hmac_cpp PUBLIC if(BUILD_SHARED_LIBS) target_compile_definitions(hmac_cpp PRIVATE HMAC_CPP_BUILD) else() - target_compile_definitions(hmac_cpp PUBLIC HMAC_CPP_STATIC) +target_compile_definitions(hmac_cpp PUBLIC HMAC_CPP_STATIC) endif() if(HMACCPP_ENABLE_MLOCK) target_compile_definitions(hmac_cpp PUBLIC HMAC_CPP_ENABLE_MLOCK) endif() +if(HAVE_EXPLICIT_BZERO) + target_compile_definitions(hmac_cpp PUBLIC HAVE_EXPLICIT_BZERO) +endif() + if(MSVC) target_compile_options(hmac_cpp PRIVATE /wd4251) else() diff --git a/README-RU.md b/README-RU.md index cf88565..661b539 100644 --- a/README-RU.md +++ b/README-RU.md @@ -359,8 +359,9 @@ cl /EHsc example.cpp /I _install\\include /link /LIBPATH:_install\\lib hmac_cpp. ## 🔒 Примечания по безопасности -`secure_buffer` очищает память при разрушении. Он не закрепляет страницы в RAM, -не предоставляет защиту страниц и не предотвращает атаки соседних буферов. +`secure_buffer` очищает память при разрушении и обнуляет усечённый хвост при +изменении размера. Он не закрепляет страницы в RAM, не предоставляет защиту +страниц и не предотвращает атаки соседних буферов. --- diff --git a/README.md b/README.md index 05f4509..5d814f7 100644 --- a/README.md +++ b/README.md @@ -382,8 +382,9 @@ cl /EHsc example.cpp /I _install\include /link /LIBPATH:_install\lib hmac_cpp.li ## 🔒 Security notes -`secure_buffer` wipes its memory on destruction. It does not page‑lock buffers, -provide guard pages, or mitigate neighboring memory attacks. +`secure_buffer` wipes its memory on destruction and zeroizes truncated bytes on +resize. It does not page‑lock buffers, provide guard pages, or mitigate +neighboring memory attacks. --- diff --git a/include/hmac_cpp/secure_buffer.hpp b/include/hmac_cpp/secure_buffer.hpp index bafb468..9f109e7 100644 --- a/include/hmac_cpp/secure_buffer.hpp +++ b/include/hmac_cpp/secure_buffer.hpp @@ -1,13 +1,19 @@ #ifndef HMAC_CPP_SECURE_BUFFER_HPP #define HMAC_CPP_SECURE_BUFFER_HPP +#include #include #include +#include #include #include #include #include "hmac_cpp/memlock.hpp" +#if defined(HAVE_EXPLICIT_BZERO) +#include +#endif + // Macro to mark deprecated APIs in a compiler-portable way #ifndef HMACCPP_DEPRECATED #if defined(__clang__) || defined(__GNUC__) @@ -25,10 +31,17 @@ namespace hmac_cpp { /// \param ptr Pointer to the memory to wipe. /// \param len Number of bytes to set to zero. inline void secure_zero(void* ptr, size_t len) { +#if defined(__STDC_LIB_EXT1__) + (void)memset_s(ptr, len, 0, len); +#elif defined(HAVE_EXPLICIT_BZERO) + explicit_bzero(ptr, len); +#else volatile unsigned char* p = static_cast(ptr); while (len--) { *p++ = 0; } + std::atomic_signal_fence(std::memory_order_seq_cst); +#endif } /// \brief Vector-like buffer that zeroizes its contents on destruction. @@ -114,11 +127,64 @@ struct secure_buffer { } /// \brief Zeroize contents on destruction. - ~secure_buffer() { + ~secure_buffer() noexcept { clear(); } + + /// \brief Check whether pages are locked. + bool is_locked() const noexcept { return locked_; } + + /// \brief Clear and deallocate the buffer. + void clear() noexcept { + secure_zero(buf.data(), buf.size() * sizeof(T)); + if (locked_) { + unlock_pages(buf.data(), buf.size() * sizeof(T)); + locked_ = false; + } + buf.clear(); + buf.shrink_to_fit(); + } + + /// \brief Resize the buffer, zeroizing truncated data. + void resize(size_t n) { + T* old_ptr = buf.data(); + size_t old_sz = buf.size(); + if (n < old_sz) { + secure_zero(old_ptr + n, (old_sz - n) * sizeof(T)); + } + buf.resize(n); + if (LockOnAlloc && old_ptr != buf.data()) { + if (locked_) { + unlock_pages(old_ptr, old_sz * sizeof(T)); + } + if (!buf.empty()) { + locked_ = lock_pages(buf.data(), buf.size() * sizeof(T)); + } else { + locked_ = false; + } + } + } + + /// \brief Assign from raw pointer. + void assign(const T* p, size_t n) { secure_zero(buf.data(), buf.size() * sizeof(T)); if (locked_) { unlock_pages(buf.data(), buf.size() * sizeof(T)); } + buf.assign(p, p + n); + if (LockOnAlloc && !buf.empty()) { + locked_ = lock_pages(buf.data(), buf.size() * sizeof(T)); + } else { + locked_ = false; + } + } + + /// \brief Assign from std::string rvalue and zeroize the source. + template::value, int>::type = 0> + void assign(std::string&& s) { + assign(reinterpret_cast(s.data()), s.size()); + if (!s.empty()) { + secure_zero(&s[0], s.size()); + s.clear(); + } } T* data() { return buf.data(); }