Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down
5 changes: 3 additions & 2 deletions README-RU.md
Original file line number Diff line number Diff line change
Expand Up @@ -359,8 +359,9 @@ cl /EHsc example.cpp /I _install\\include /link /LIBPATH:_install\\lib hmac_cpp.

## 🔒 Примечания по безопасности

`secure_buffer` очищает память при разрушении. Он не закрепляет страницы в RAM,
не предоставляет защиту страниц и не предотвращает атаки соседних буферов.
`secure_buffer` очищает память при разрушении и обнуляет усечённый хвост при
изменении размера. Он не закрепляет страницы в RAM, не предоставляет защиту
страниц и не предотвращает атаки соседних буферов.

---

Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

---

Expand Down
68 changes: 67 additions & 1 deletion include/hmac_cpp/secure_buffer.hpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
#ifndef HMAC_CPP_SECURE_BUFFER_HPP
#define HMAC_CPP_SECURE_BUFFER_HPP

#include <atomic>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <vector>
#include <type_traits>
#include <string>
#include "hmac_cpp/memlock.hpp"

#if defined(HAVE_EXPLICIT_BZERO)
#include <strings.h>
#endif

// Macro to mark deprecated APIs in a compiler-portable way
#ifndef HMACCPP_DEPRECATED
#if defined(__clang__) || defined(__GNUC__)
Expand All @@ -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<volatile unsigned char*>(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.
Expand Down Expand Up @@ -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<class U = T, typename std::enable_if<std::is_same<U, uint8_t>::value, int>::type = 0>
void assign(std::string&& s) {
assign(reinterpret_cast<const uint8_t*>(s.data()), s.size());
if (!s.empty()) {
secure_zero(&s[0], s.size());
s.clear();
}
}

T* data() { return buf.data(); }
Expand Down
Loading